This is mostly about the behavior of =>
, not about await
. Normally () => 1
can be thought of as shorthand for () { return 1; }
, but that's actually an oversimplification.
=>
is allowed for void
functions as a convenience so that people can write things like:
bool functionWithSideEffect() {
print('Some side effect');
return true;
}
void foo() => functionWithSideEffect();
int _someValue = 0;
set someValue(int value) => _someValue = value;
even though these aren't legal:
var foo() {
// error: A value of type 'bool' can't be returned from the function 'foo'
// because it has a return type of 'void'.
return functionWithSideEffect();
}
set someValue(int value) {
// error: A value of type 'int' can't be returned from the function
// 'someValue' because it has a return type of 'void'.
return _someValue = value;
}
The crux of your confusion is that () => 1
either could be int Function()
or void Function()
, and type inference picks different things given different constraints.
When you do:
void longRun() async {
return await Future.delayed(
Duration(seconds: 3),
()=>1,
);
}
then type inference works outside-in, propagating the void
return type of longRun
through the unconstrained, returned expression. The Future.delayed
call is inferred to be Future<void>.delayed
and the () => 1
callback is inferred to be void Function()
. (Arguably a void
async
function returning Future<void>
could be treated as error.)
In contrast, when you do:
void longRun() async {
var foo = await Future.delayed(
Duration(seconds: 3),
()=>1,
);
return foo;
}
now type inference runs in the other direction (inside-out): foo
has no explicit type, so it does not constrain the right-hand-side. Therefore () => 1
is instead assumed to be int Function()
, which causes Future.delayed
to be inferred to be Future<int>.delayed
and foo
to be inferred as an int
. Since foo
is an int
, attempting to return it from a function declared to return void
is an error.
version2:
Future<void> longRun() async {
return await Future.delayed(Duration(seconds:2), ()=>1);
}
You've changed longRun
's return type from void
to Future<void>
. async
functions usually should return a Future
but may also return void
to be fire-and-forget. The only difference from version 1 is that callers can now wait (fire a callback) when longRun
completes. () => 1
is still inferred to be void Function()
.
version3:
Future<int> longRun() async {
return await Future.delayed(Duration(seconds:2), ()=>1);
}
Same as version2 except longRun
returns Future<int>
instead of Future<void>
. Now () => 1
is inferred to be int Function()
.
version4:
Future longRun() async {
return await Future.delayed(Duration(seconds:2), ()=>1);
}
longRun
's return type is now Future
, which means Future<dynamic>
. () => 1
is inferred to be dynamic Function()
.
version5:
Future longRun() async {
await Future.delayed(Duration(seconds:2), ()=>1);
}
Same as version4 except there is no explicit return
statement. Now the Future.delayed
expression is no longer constrained by longRun
's return type, and inference will flow inside-out; () => 1
is assumed to be int Function()
.
version6:
void longRun() async {
await Future.delayed(Duration(seconds:2), ()=>1);
}
Same as version5 except that longRun
returns void
and is a fire-and-forget function. Callers cannot be notified when longRun
completes.
(Note that in the above, when I write () => 1
is inferred to be int Function()
or void Function()
, it really should be FutureOr<int> Function()
or FutureOr<void> Function()
, but that's not the important part.)
Edit:
Pre-emptively addressing some potential follow-up questions:
Why is:
void longRun() async {
int f() {
return 1;
}
return await Future<void>.delayed(
Duration(seconds: 3),
f,
);
}
okay, but:
void longRun() async {
return await Future<void>.delayed(
Duration(seconds: 3),
// info: Don't assign to void.
() {
return 1;
},
);
}
generates an analysis complaint? Both versions are legal because substituting a Future<U>
for a Future<T>
is legal when U
is a subtype of T
. When T
is void
, U
can be anything; the value can just be ignored. (In Dart, partly for historical reasons when it didn't always have a void
type, void
actually means that the value cannot be used and not that there is no value. void x = 42;
is legal, but attempting to read the value of x
would be an error. This is why the "Don't assign to void" analysis complaint isn't categorized as an error.)
In the second version that uses an anonymous function, the tighter locality of the return
statement allows the analyzer to perform more extensive checks.