-
Notifications
You must be signed in to change notification settings - Fork 213
Remove dynamic type #3192
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Comments
This is a language change request. Moving it to the language repository. |
I don't use I see no downsides except for the fact that this is a very breaking change. I fully support this change, but I find it hard to believe that the Dart team would go through this route, as they are pretty conservative about breaking changes. |
Same here. I've stopped using dynamic forever ago. The only use-case I can think of today is mocking, which currently often relies on dynamic invocation with noSuchMethod. There's also how dart:convert does a dynamic invocation of So removing it could be quite breaking. Although not impossible. |
I am also in favor of this change. As far as I know, the |
A stepping stone to removing dynamic from the language would be to ban dynamic calls in the recommended (or core) lint rule sets. You can upvote here: dart-lang/core#750 😁 |
I like it. I have been using strict type checks and explicit casting from the beginning using the Dart language. |
Is it the What if the only expression that could have type Dynamism would then be an opt-in feature, with a clear syntactic marker everywhere it's used. The alternative is that there are existing features which cannot be retained. Like It might encourage introducing new, simple, interfaces and force existing code to implement those, in order for existing dynamic code to keep working. (Not saying we cannot remove |
This would be better than what we have today and less breaking, so it may be an alternative path. From there we can think if it's really valuable to remove
Honestly, I would also ask to remove |
It is even the existence of
In my opinion,
For
|
You never need a cast, you can do an That's just cumbersome, in the cases where you do know that the value will definitely have the assumed type. Implicit downcast from |
Using |
I think the idea of having dynamic be more explicit is a good one. Changing type inference to instead rely on Object? instead of dynamic would be great. There are also a bunch of APIs which promote dynamic. |
For me, it is the accidental use. If you'd have to be very very explicit every time you want to do a dynamic invocation it wouldn't be a problem anymore.
Wondering if you have any examples where Flutter is using dynamic. We try very hard not to and I'd be open to removing any remaining instances. |
@goderbauer here is the report of |
Honestly I grouped flutter and Dart SDK together. It could be that Flutter doesn't really use it, I don't remember. The sdk uses it quite often I think. In part with error handling or json |
Mainly JSON, because JSON is inherently untyped, and Dart 1 used Some async error handling uses But there are places in the SDK libraries where some raw type gets instantiated with |
Could be related: dart-lang/sdk#50874 |
Well, yes, I guess that when there's a breaking change, one would try to fit in good counter measures as much as possible. I guess that rarely you'd release, say, "Dart 4" just because of a single feature like this one. If anything, I wish a potential "Dart 4" version would remove
I can't see how this is insurmountable. By definition, a breaking change un-supports ..stuff! Folks would need to adapt to it. SDK / Flutter / core libraries included. ... But I sincerely can't wait to do so! If there's a fair amount of benefits that indirectly affect or bring other features, I'd quickly transition to this. As many others, I don't use |
I don't think dynamic should go away entirely, but it should be made safer. A form of structural typing can be introduced to make dynamic a bit more ergonomic. For example, toJson in // Interface that can be explicitly or implicitly implemented by a type
// as long as it contains all matching members.
typedef interface ToJson {
Object toJson();
}
// ... json innards in dart:convert when handling an unknown type:
// assume object is declared as `dynamic object;`.
if (object is ToJson) {
object = object.toJson();
} Downstream consumers can choose to use an implements clause for ToJson. But otherwise it can be omitted. class MySerializableClass implements ToJson {
Map<String, dynamic> jsonEncode() => {"foo": "bar"};
} Then warn when dynamic is used unexpectedly. Tell the developer to use an explicit type or a structural interface instead. The difference between |
This sounds like a method to prepare for the breaking change, but not to prevent a breaking change. Here, one would continue to support something that is officially not recommended. You could throw out the support for calling |
(I'd rather just stop calling |
Flutter has various usages of dynamic, notably: https://github.com/flutter/flutter/blob/5d4a1f1f5fe1d0d0191c691eb60b6814654d6929/packages/flutter/lib/src/animation/tween.dart#L339 Which is intended to avoid having to make N different implementations of Tween to support all kinds of numeric-like classes (Offset for example, maybe Vector2/3/4 from vector_math). Could be supplanted by structural interfaces. |
Discussion in dart-lang/language#3192 indicates a general move away from dynamic.
And doing |
if (myDynamic is Foo) { // with promotion
myDynamic.fooMethod();
} There's no |
No, because you often do not know that a value is dynamic because of type-inference. Consider: final value = await http.get(...);
print(value.entries());
It's fairly common for codebases to do: if ( value is! Foo) {
throw MyCustomError();
}
If you don't like exceptions, that's a different topic. You could use an error result ; and Dart could benefit from a You're also free to use a custom extension on Object {
T? cast<T>() {
final that = this;
if (that is T) return that;
return null;
}
} Then |
Rust and Swift both have an |
The linter will tell you. If you remove |
Thats not what dynamic is. Dynamic is akin to "no type" since you can do dynamic access. Object? Is the super type of everything, so it can be considered the union type of everything. The latter is much safer in that you can't accedentally call a non-existent method Sadly, there are parts of the language rhat do use dynamic as a union type, as the original design was inherently dynamic, and can't be represented in the current type system, which is why we need some form of union type in the language. |
Well, actually … the Dart type system being sound means that the actual value of an expression will have a runtime type that is a subtype of the static type of the expression. No more or less. What is being talked about here is (type) safety, that not having static type errors implies not having runtime type errors. Dart is not safe. It's closer than it has been, but covariant generics is still the biggest exception.
Not in any official way, no. |
This wasn't referring to Before, on new projets, you used to be able to do: List list = [].map();
// no toList(), Dart does an implicit cast of Iterable to List.
// This will throw at runtime instead of emitting a compilation error That's not the case anymore. It's off by default. So in that sense, implicit-cast is officially disabled. To me the topic of discouraging |
Ack. Dart removed general implicit downcasts from the language, so now the only implicit downcast is from |
@lrhn: |
If dart can support type parameters for |
It would at least required something like #283 or #2532, or else we would be very limited. Alternatively, we could have |
The feature doesn't require any generalizations. It can support |
If you're going to do that, might as well have actual union type syntax, with Honestly I wouldn't mind if it was more of a static hint for dynamic, but it's outside the scope of this issue. |
Not sure how we diverged from "remove dynamic" to "let's make dynamic a pseudo union type". To me this boils down to: We don't have to remove it quite yet, as that's a big breaking change. |
If you discourage
If I remember the discussion of ~15 years ago correctly, this was the main justification for Remember, back then, dart was a dynamically typed language. The types were optional, so you couldn't rely on static types anyway. Dart 1 handled the types in a way that today looks quite peculiar: because VM was the only implementation, it relied on VM's runtime magic to figure out what the actual types were (in runtime!) and even optimize for them (by using on-the-fly compilation). Why the justification for |
@tatumizer
|
Heck if we want to talk about unions, we have sealed classes and pattern matching now. That's union-like. It's not untagged unions, but that's definitely something. For instance, you can do: sealed class Json {
factory Json.string(String str) = JsonString;
factory Json.boolean(bool bool) = JsonBool;
...
} And return that instead of I'd sure love untagged unions. They'd be less verbose and less memory intensive in those scenarios. But we have alternatives. |
These are not the "alternatives". They are workarounds. Why do you want a workaround for a feature that is itself a workaround? |
No, they are not workarounds. At least in this specific case, they are alternatives. Something being less ergonomic or more verbose doesn't mean that it's an workaround. |
No, they are! If you have an opinion opposite to the first comment in the cited thread, then just go to stackoverflow and argue against it there - it might have a greater circulation. How about that? :-) ( I fail to see how replacing |
I don't agree or disagree with what @lrhn is saying in the mentioned link. It's a well-stablished fact. The point in discussion here is not "what are the differences between With The other 1% is when you want to treat an object as a structural type (in this case we are, indeed, talking about workarounds, not alternatives), something that we can kind of do with Edit: Also, the main problem of this issue is not that by removing |
This discussion is nothing new. Typescript went through the same thing. It started with To me that thread is the same thing: The community pretty much is already there. Most Dart devs would swear at you if you wrote |
Using void f(Object? obj) {
//...
}
main() {
// who checks your types?
f(0);
f([1]);
f({0: 0});
} Notice that no lints will ever be able to catch the errors here - you can pass just anything, like today for Officially recommending this half-measure as a replacement for dynamic is not an option for a language that advertises itself as typesafe. |
(Cont-d) f(Object? obj) {
// I know that obj is either int or String
if (obj is int) {
// ...
} else if (obj is String) {
// ...
} else throw "...";
} The compiler forces you to check type (with promotion) or to use the (Interestingly, with |
No, the code is being checked. The static type of the parameter is
Which errors? Your function has no definition, so we can't know which potential errors you are talking about. Either way, if the code used
It is at least safer than
It doesn't always explicitly say it, because many times
It doesn't mean the function "uses just", but that the object has at least those members, having potentially more, something you will have to explicitly check or cast.
I agree that untagged union types is something nice.
Yes, it is effectively duck typing, and this is what this issue is about. We don't want the unsafety inherent to duck typing. The alternative is to have structural typing, which is not the subject of this issue.
Which is the point of this issue. |
I actually wouldn't say that duck typing is inherently unsafe. Dynamic access is unsafe - it's just that we can use it to emulate duck typing. Hell, I think I saw a package support rxdart's Value stream without depending on the package by using this. Proper duck typing can be statically typed, as I believe golang does. Honestly, Im tempted to want duck typing every time I use jsonEncode :/ That said, it does seem clear that dynamic should still exist, but it is my opinion that it should not be allowed for return types or closure arguments. Any usage of it should be extremely deliberate |
According to Wikipedia,
If I understand correctly, duck typing is inherently dynamic, and, thus, unsafe. The static version of duck typing is structural typing. |
I see. Makes sense. So I had the right idea but the wrong terminology. |
Absolutely agreed, |
Proposal
Dart should remove the
dynamic
type for stricter type-safety, better performance and less support for bad practices.Justification
The use of
dynamic
is a bad practice, often even referred to as such in the documentation:dynamic
: Indicates that you want to disable static checking. Usually you should useObject
orObject?
instead." (https://dart.dev/language/built-in-types)However, there are ways to bypass
dynamic
calls and implicit casts for your own projects using lints (e.g. with strong-mode).But this does not solve the problems that the support for
dynamic
has for the Dart language in general.Firstly, it results in worse performance of the compiled code, since more inefficient runtime checks are required. (I'm not an expert on Dart compilers, so correct me if I'm wrong on this point).
Secondly, the support of
dynamic
prevents other features that many developers would like to use in a type-safe object-oriented language. For example,private
,protected
andpublic
modifiers are not possible because they could not supportdynamic
types except through very inefficient runtime checks (dart-lang/sdk#33383).In general, Dart tries to be as type-safe as possible. But on the other hand there is this relict
dynamic
, which stands against it.dynamic
generally feels like a concession to JavaScript developers to get more of them to switch to Dart/Flutter. Developers who have moved from Java/Kotlin/Swift/C++,... are just disturbed bydynamic
and it also doesn't bring any benefits except more opportunities to apply bad practices.Impact
All code that currently uses
dynamic
would have to be changed toObject?
or subtypes and implicit casts would be required. Also, method calls would no longer be possible without knowing the type of an object.Mitigation
Since this would be a null-safety level change, it would have to be made optional at first and fully introduced with the next major release.
The text was updated successfully, but these errors were encountered: