You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Certain kinds of function have a return mechanism which is implicit:
A function whose body has the modifier async will return an object typable as Future<T>, where T is the future value type of the function (which is computed based on the declared return type).
A function whose body has the modifier sync* will return an object typable as Iterable<T>, where T is the element type of the function (computed from the declared return type).
A function whose body has the modifier async* will return an object typable as Stream<T>, where T is the element type of the function (computed from the declared return type).
The given future/iterable/stream is created as part of the built-in semantics of those functions, and the developer who writes such a function doesn't have an opportunity to specify the kind of object which is being returned.
Consequently, it is a compile-time error for such functions to have a return type that fails to satisfy a certain constraint. For example, it is an error for T f() async {...} if T isn't a supertype of Future<Never>.
This issue is a proposal that we also allow such functions to have a return type which is an extension type that implements a type that satisfies the constraint, and whose extension type erasure also satisfies the constraint. The future value type resp. element type of the function is computed from the extension type erasure.
In other words, everything is unchanged, except that we're also allowed to return an extension type whose erasure is OK, if it also "admits" to being a future (or iterable, or stream) by having a Future (Iterable, Stream) type as a superinterface. For example:
extension typeE1(int _) {}
extension typeE2<X>(Future<X> _) {}
extension typeE3<X>(Future<X> _) implementsFuture<void> {}
E1f1() async {...} // Error, the erasure `int` does not satisfy the constraint.E2<int> f2() async {...} // Error, `E2` does not implement `Future`.E3<List<X>> f3<X>(X x) async {...} // OK.
Here is an example where this little snippet of expressive power could be helpful:
import'dart:async';
import'dart:math';
extension typeSafeFuture<T>(Future<T> _it) implementsFuture<T> {
SafeFuture<R> then<R>(
FutureOr<R> onValue(T value), {
TFunction(Object, StackTrace)? onError,
}) =>SafeFuture(_it.then(onValue, onError: onError));
}
Future<int> f1() async {
// ... async stuff ...returnFuture.error("Failed Future!");
}
SafeFuture<int> f2() async { // Currently a compile-time error, will be OK.// ... async stuff ...returnFuture.error("Failed SafeFuture!");
}
voidmain() async {
if (Random().nextBool()) {
var fut =f1();
print('Using a regular Future');
await fut.then((_) =>42, onError: print); // No error, but throws.
} else {
var sfut =f2();
int i;
print('Using a SafeFuture');
// await sfut.then((_) => 42, onError: print); // Compile-time error.
i =await sfut.then((_) =>42, onError: (o, s) =>24); // OK!print('Done, got $i');
}
}
The point is that a SafeFuture requires a fully typed onError in the signature of its then method, which will eliminate the run-time type error that the regular Future incurs.
(OK, we might want a then and a thenNoStackTrace to allow an onError that doesn't receive the stack trace, but that's just something we can play around with, the point is that we can modify and/or add members to Future, and we can use that enhanced interface of futures all over the place because all async functions can return it, if we wish to do that.)
A similar technique could also be used to provide an invariant future type (EvenSaferFuture ;-), which is needed in order to avoid a covariance related run-time type error (e.g., if we have a Future<int> with static type Future<num> and pass an onError that has return type num).
We can just go ahead and do this in most cases, but when it comes to async functions it creates a need for an inconvenient wrapper function:
// Workaround which is available today.SafeFuture<int> f2() {
Future<int> inner() async {
// ... async stuff ...returnFuture.error("Failed SafeFuture!");
}
returnSafeFuture(inner());
}
This proposal allows us to avoid this wrapper function which saves developer time and execution time. It is type safe, because the return statements will rely on the representation type of the return type which is actually what we have at run time
The text was updated successfully, but these errors were encountered:
Oh, I actually forgot that we got so close to the same thing in dart-lang/sdk#58667. You could say that this is just a follow-up which was already mentioned in the discussion of that issue:
If we wish to generalize the rules in the future along the lines discussed here then we can handle that in a new issue.
I think it's worth revisiting this idea now. Over the past few months (#3257 was written in August 2023) I've tried out various usages of extension types, and the fact that we can't have something like SafeFuture<int> f() async {...} sticks out as an unnecessary restriction. (And the obvious workaround has a run-time performance cost, not just a developer inconvenience cost.)
Also note that we support switch (myFuture) { case SafeFuture(): ... } , which is yet another way to achieve a typing of a given future as an extension type without running a constructor. In that context we will have a lint to warn developers in the case where SafeFuture doesn't implement Future, which is an even more permissive approach than the one which is being proposed for returns here.
Certain kinds of function have a return mechanism which is implicit:
async
will return an object typable asFuture<T>
, whereT
is the future value type of the function (which is computed based on the declared return type).sync*
will return an object typable asIterable<T>
, whereT
is the element type of the function (computed from the declared return type).async*
will return an object typable asStream<T>
, whereT
is the element type of the function (computed from the declared return type).The given future/iterable/stream is created as part of the built-in semantics of those functions, and the developer who writes such a function doesn't have an opportunity to specify the kind of object which is being returned.
Consequently, it is a compile-time error for such functions to have a return type that fails to satisfy a certain constraint. For example, it is an error for
T f() async {...}
ifT
isn't a supertype ofFuture<Never>
.This issue is a proposal that we also allow such functions to have a return type which is an extension type that implements a type that satisfies the constraint, and whose extension type erasure also satisfies the constraint. The future value type resp. element type of the function is computed from the extension type erasure.
In other words, everything is unchanged, except that we're also allowed to return an extension type whose erasure is OK, if it also "admits" to being a future (or iterable, or stream) by having a
Future
(Iterable
,Stream
) type as a superinterface. For example:Here is an example where this little snippet of expressive power could be helpful:
The point is that a
SafeFuture
requires a fully typedonError
in the signature of itsthen
method, which will eliminate the run-time type error that the regularFuture
incurs.(OK, we might want a
then
and athenNoStackTrace
to allow anonError
that doesn't receive the stack trace, but that's just something we can play around with, the point is that we can modify and/or add members toFuture
, and we can use that enhanced interface of futures all over the place because all async functions can return it, if we wish to do that.)A similar technique could also be used to provide an invariant future type (
EvenSaferFuture
;-), which is needed in order to avoid a covariance related run-time type error (e.g., if we have aFuture<int>
with static typeFuture<num>
and pass anonError
that has return typenum
).We can just go ahead and do this in most cases, but when it comes to
async
functions it creates a need for an inconvenient wrapper function:This proposal allows us to avoid this wrapper function which saves developer time and execution time. It is type safe, because the return statements will rely on the representation type of the return type which is actually what we have at run time
The text was updated successfully, but these errors were encountered: