Implications of the effect hierarchy
One implication of the subset-superset relationship is that code which is generic over effects will not be able to use all functionality of the superset in the subset case. Though it will need to use the syntax of the superset.
Take for examle the following code. It takes two async closures, awaits them, and sums them:
#![allow(unused)] fn main() { // Sum the output of two async functions: ~async fn sum<T>( lhs: impl ~async FnMut() -> T, rhs: impl ~async FnMut() -> T ) -> T { let lhs = lhs().await; let rhs = rhs().await; lhs + rhs } }
One of the benefits of async execution is that we gain ad-hoc concurrency, so
we might be tempted to perform the comptutation of lhs
and rhs
concurrently,
and summing the output once both have completed. However this should not be
possible solely using effect polymorphism since the generated code needs to work
in both async and non-async contexts.
#![allow(unused)] fn main() { // Sum the output of two async functions: ~async fn sum<T>( lhs: impl ~async FnMut() -> T, rhs: impl ~async FnMut() -> T ) -> T { let (lhs, rhs) = (lhs(), rhs()).join().await; // ^^^^^^^ // error: cannot call an `async fn` from a `~async` context // hint: instead of calling `join` await the items sequentially // or consider writing an overload instead lhs + rhs } }
And this is not unique to async
: in maybe-const
contexts we cannot call
functions from the super-context ("base Rust") since those cannot work during
const
execution. This leads to the following implication: Conditional effect
implementations require the syntactic annotations of the super-context, but
cannot call functions which exclusively work in the super-context.