What is changing?
The flutter framework is upgrading the bundled release of the Dart SDK from 2.0.0-dev.16.0, most likely landing with 2.0.0-dev.23.0 . As part of this roll, some packages will need to roll forward as well. The current version of the pull request making this change is here:
What will break?
The most significant changes are as follows.
- "void" can now be used as a type argument. This is not a breaking change, but an awesome feature related to the breaking change. This is very useful for creating async methods that return no value. Previously, Future<Null> was the norm, but now you can use Future<void>. This not only looks better to those new to Dart, but has the added benefit that void types are less usable than Null types, which better fulfills the promise that if you say, "this future has no value," that value will not be used. This goes hand in hand with the next item...
- The type "void" used to be usable as dynamic. Previously, void was a surprisingly normal subtype of Object, with no methods. It was therefore possible to pass the result of void functions anywhere dynamic was accepted. (At runtime, void methods usually return null, so this is counterintuitive, but often inconsequential).
We are fixing this, however, to match what users intend. It will only be legal to assign void values to other void values, and in fact, some expression e of type `void` will only be usable in a handful of forms:
x ?? e;
x ? e : e;
acceptsVoid(e);
e as T;
return e;
We have found some bugs by landing this change in other dart code:
// here, sideEffect() is always called because voidMethod() returns null!
member?.voidMethod() ?? sideEffect();
// here, the sort methods mutate the lists but return nothing. Should have used unorderedEquals!
expect(listA.sort(), listB.sort());
Why ever pass void into a function? It may seem strange, but it is useful for sequencing. Consider Mockito's nicely readable API: verify(mock.voidMethod()).called(1). This is only allowed where the parameter's type is void, either by definition or through inference on a type parameter. You may have at one point written:
verifyInOrder(<dynamic>[e, e, e]);
and the type parameter can either be removed and left to inference, or, in certain cases of inference failure, changed to void.
Why allow casting void? It can be useful to inspect the returned value of a purportedly void function, for instance, to see if there was an override with asynchrony. This should not be useful to the majority of dart users.
Follow-up question regarding the returns of void values
In our experiments, we found it is natural to write
f() => g();
where g() also returns void. Unfortunately, if f is a method or a top-level function, it doesn't get type inference but rather gets an implicit dynamic. We found this to be too common in real code to disallow it outright, so it is legal to return a void value from a dynamic function.
If you think this is risky or surprising, please give us feedback about what you'd like to happen, and we'll see what we can do going forward.
- void is also now a Top type. This means that (...) -> T is a subtype of (...) -> void, for any type T. One thing to watch for, however, is implicit downcasts -- a Future<void> may be downcast to a Future<T> if you don't have implicit downcasts turned off. This will also fail at runtime with the VM in checked mode (except, in this case, if T is Object or dynamic).
I will update here when this change lands. Please reach out to me here or offline with any concerns, and/or with help resolving any issues after this change has landed.