-
Notifications
You must be signed in to change notification settings - Fork 12.8k
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
Add AsyncFn
family of traits
#119305
Add AsyncFn
family of traits
#119305
Conversation
(rustbot has picked a reviewer for you, use r? to override) |
I'm nominating this for T-libs-api discussion -- not sure if what process is needed to introduce these traits as unstable :) r? libs-api |
also cc @rust-lang/wg-async |
Exciting work. Big thanks to @compiler-errors for pushing this forward. @rustbot labels +I-lang-nominated Nominating this for T-lang. At the end of the day T-lang will need to be satisfied with the design here, so we should discuss it at this point at least for visibility. And given that this is adding new lang items, we may want to charter this as a T-lang experiment. @rustbot labels +I-async-nominated Nominating this for WG-async. Async closures have been a recent topic of discussion for WG-async and have been identified as a priority. The async WG will at the end of the day want to be happy with the design, so we too should discuss it. |
Discussed in the @rust-lang/lang meeting and we agreed to accept this as an experiment (I will server as the liaison). All things being equal the goal we would like as much interop and generalization between "async functions" and "functions returning traits" as we can get, but I clarified that @compiler-errors and I were already aligned on that but that there are some interesting complications that make that non-trivial, and that the intent is to make concrete progress towards an MVP, with an eye for future compatibility. |
@rustbot labels -I-lang-nominated |
Hey @rust-lang/libs, I did not remove the I-libs-api-nominated tag. My take is that this is more of a lang question than a libs one, even though it involves a trait, and I would expect lang to be primary decision maker with libs-api in a "consulting role" (i.e., free to raise objections, but not requiring active checkboxes). If you all would like a more active role, though, happy to discuss. |
Regarding the |
I'm gonna pause this for a second -- still trying to decide whether this is better than just introducing @rustbot author |
After a lot of thought and experimentation, I've decided it's best to first introduce A desugaring that is backed by @rustbot ready |
@bors r+ rollup |
…=oli-obk Add `AsyncFn` family of traits I'm proposing to add a new family of `async`hronous `Fn`-like traits to the standard library for experimentation purposes. ## Why do we need new traits? On the user side, it is useful to be able to express `AsyncFn` trait bounds natively via the parenthesized sugar syntax, i.e. `x: impl AsyncFn(&str) -> String` when experimenting with async-closure code. This also does not preclude `AsyncFn` becoming something else like a trait alias if a more fundamental desugaring (which can take many[^1] different[^2] forms) comes around. I think we should be able to play around with `AsyncFn` well before that, though. I'm also not proposing stabilization of these trait names any time soon (we may even want to instead express them via new syntax, like `async Fn() -> ..`), but I also don't think we need to introduce an obtuse bikeshedding name, since `AsyncFn` just makes sense. ## The lending problem: why not add a more fundamental primitive of `LendingFn`/`LendingFnMut`? Firstly, for `async` closures to be as flexible as possible, they must be allowed to return futures which borrow from the async closure's captures. This can be done by introducing `LendingFn`/`LendingFnMut` traits, or (equivalently) by adding a new generic associated type to `FnMut` which allows the return type to capture lifetimes from the `&mut self` argument of the trait. This was proposed in one of [Niko's blog posts](https://smallcultfollowing.com/babysteps/blog/2023/05/09/giving-lending-and-async-closures/). Upon further experimentation, for the purposes of closure type- and borrow-checking, I've come to the conclusion that it's significantly harder to teach the compiler how to handle *general* lending closures which may borrow from their captures. This is, because unlike `Fn`/`FnMut`, the `LendingFn`/`LendingFnMut` traits don't form a simple "inheritance" hierarchy whose top trait is `FnOnce`. ```mermaid flowchart LR Fn FnMut FnOnce LendingFn LendingFnMut Fn -- isa --> FnMut FnMut -- isa --> FnOnce LendingFn -- isa --> LendingFnMut Fn -- isa --> LendingFn FnMut -- isa --> LendingFnMut ``` For example: ``` fn main() { let s = String::from("hello, world"); let f = move || &s; let x = f(); // This borrows `f` for some lifetime `'1` and returns `&'1 String`. ``` That trait hierarchy means that in general for "lending" closures, like `f` above, there's not really a meaningful return type for `<typeof(f) as FnOnce>::Output` -- it can't return `&'static str`, for example. ### Special-casing this problem: By splitting out these traits manually, and making sure that each trait has its own associated future type, we side-step the issue of having to answer the questions of a general `LendingFn`/`LendingFnMut` implementation, since the compiler knows how to generate built-in implementations for first-class constructs like async closures, including the required future types for the (by-move) `AsyncFnOnce` and (by-ref) `AsyncFnMut`/`AsyncFn` trait implementations. [^1]: For example, with trait transformers, we may eventually be able to write: `trait AsyncFn = async Fn;` [^2]: For example, via the introduction of a more fundamental "`LendingFn`" trait, plus a [special desugaring with augmented trait aliases](https://rust-lang.zulipchat.com/#narrow/stream/213817-t-lang/topic/Lending.20closures.20and.20Fn*.28.29.20-.3E.20impl.20Trait/near/408471480).
Rollup of 8 pull requests Successful merges: - rust-lang#119305 (Add `AsyncFn` family of traits) - rust-lang#119389 (Provide more context on recursive `impl` evaluation overflow) - rust-lang#120062 (llvm: change data layout bug to an error and make it trigger more) - rust-lang#120099 (linker: Refactor library linking methods in `trait Linker`) - rust-lang#120201 (Bump some deps with syn 1.0 dependencies) - rust-lang#120230 (Assert that a single scope is passed to `for_scope`) - rust-lang#120278 (Remove --fatal-warnings on wasm targets) - rust-lang#120292 (coverage: Dismantle `Instrumentor` and flatten span refinement) r? `@ghost` `@rustbot` modify labels: rollup
…iaskrgr Rollup of 10 pull requests Successful merges: - rust-lang#119305 (Add `AsyncFn` family of traits) - rust-lang#119389 (Provide more context on recursive `impl` evaluation overflow) - rust-lang#119895 (Remove `track_errors` entirely) - rust-lang#120230 (Assert that a single scope is passed to `for_scope`) - rust-lang#120278 (Remove --fatal-warnings on wasm targets) - rust-lang#120292 (coverage: Dismantle `Instrumentor` and flatten span refinement) - rust-lang#120315 (On E0308 involving `dyn Trait`, mention trait objects) - rust-lang#120317 (pattern_analysis: Let `ctor_sub_tys` return any Iterator they want) - rust-lang#120318 (pattern_analysis: Reuse most of the `DeconstructedPat` `Debug` impl) - rust-lang#120325 (rustc_data_structures: use either instead of itertools) r? `@ghost` `@rustbot` modify labels: rollup
Rollup merge of rust-lang#119305 - compiler-errors:async-fn-traits, r=oli-obk Add `AsyncFn` family of traits I'm proposing to add a new family of `async`hronous `Fn`-like traits to the standard library for experimentation purposes. ## Why do we need new traits? On the user side, it is useful to be able to express `AsyncFn` trait bounds natively via the parenthesized sugar syntax, i.e. `x: impl AsyncFn(&str) -> String` when experimenting with async-closure code. This also does not preclude `AsyncFn` becoming something else like a trait alias if a more fundamental desugaring (which can take many[^1] different[^2] forms) comes around. I think we should be able to play around with `AsyncFn` well before that, though. I'm also not proposing stabilization of these trait names any time soon (we may even want to instead express them via new syntax, like `async Fn() -> ..`), but I also don't think we need to introduce an obtuse bikeshedding name, since `AsyncFn` just makes sense. ## The lending problem: why not add a more fundamental primitive of `LendingFn`/`LendingFnMut`? Firstly, for `async` closures to be as flexible as possible, they must be allowed to return futures which borrow from the async closure's captures. This can be done by introducing `LendingFn`/`LendingFnMut` traits, or (equivalently) by adding a new generic associated type to `FnMut` which allows the return type to capture lifetimes from the `&mut self` argument of the trait. This was proposed in one of [Niko's blog posts](https://smallcultfollowing.com/babysteps/blog/2023/05/09/giving-lending-and-async-closures/). Upon further experimentation, for the purposes of closure type- and borrow-checking, I've come to the conclusion that it's significantly harder to teach the compiler how to handle *general* lending closures which may borrow from their captures. This is, because unlike `Fn`/`FnMut`, the `LendingFn`/`LendingFnMut` traits don't form a simple "inheritance" hierarchy whose top trait is `FnOnce`. ```mermaid flowchart LR Fn FnMut FnOnce LendingFn LendingFnMut Fn -- isa --> FnMut FnMut -- isa --> FnOnce LendingFn -- isa --> LendingFnMut Fn -- isa --> LendingFn FnMut -- isa --> LendingFnMut ``` For example: ``` fn main() { let s = String::from("hello, world"); let f = move || &s; let x = f(); // This borrows `f` for some lifetime `'1` and returns `&'1 String`. ``` That trait hierarchy means that in general for "lending" closures, like `f` above, there's not really a meaningful return type for `<typeof(f) as FnOnce>::Output` -- it can't return `&'static str`, for example. ### Special-casing this problem: By splitting out these traits manually, and making sure that each trait has its own associated future type, we side-step the issue of having to answer the questions of a general `LendingFn`/`LendingFnMut` implementation, since the compiler knows how to generate built-in implementations for first-class constructs like async closures, including the required future types for the (by-move) `AsyncFnOnce` and (by-ref) `AsyncFnMut`/`AsyncFn` trait implementations. [^1]: For example, with trait transformers, we may eventually be able to write: `trait AsyncFn = async Fn;` [^2]: For example, via the introduction of a more fundamental "`LendingFn`" trait, plus a [special desugaring with augmented trait aliases](https://rust-lang.zulipchat.com/#narrow/stream/213817-t-lang/topic/Lending.20closures.20and.20Fn*.28.29.20-.3E.20impl.20Trait/near/408471480).
I'm proposing to add a new family of
async
hronousFn
-like traits to the standard library for experimentation purposes.Why do we need new traits?
On the user side, it is useful to be able to express
AsyncFn
trait bounds natively via the parenthesized sugar syntax, i.e.x: impl AsyncFn(&str) -> String
when experimenting with async-closure code.This also does not preclude
AsyncFn
becoming something else like a trait alias if a more fundamental desugaring (which can take many1 different2 forms) comes around. I think we should be able to play around withAsyncFn
well before that, though.I'm also not proposing stabilization of these trait names any time soon (we may even want to instead express them via new syntax, like
async Fn() -> ..
), but I also don't think we need to introduce an obtuse bikeshedding name, sinceAsyncFn
just makes sense.The lending problem: why not add a more fundamental primitive of
LendingFn
/LendingFnMut
?Firstly, for
async
closures to be as flexible as possible, they must be allowed to return futures which borrow from the async closure's captures. This can be done by introducingLendingFn
/LendingFnMut
traits, or (equivalently) by adding a new generic associated type toFnMut
which allows the return type to capture lifetimes from the&mut self
argument of the trait. This was proposed in one of Niko's blog posts.Upon further experimentation, for the purposes of closure type- and borrow-checking, I've come to the conclusion that it's significantly harder to teach the compiler how to handle general lending closures which may borrow from their captures. This is, because unlike
Fn
/FnMut
, theLendingFn
/LendingFnMut
traits don't form a simple "inheritance" hierarchy whose top trait isFnOnce
.For example:
That trait hierarchy means that in general for "lending" closures, like
f
above, there's not really a meaningful return type for<typeof(f) as FnOnce>::Output
-- it can't return&'static str
, for example.Special-casing this problem:
By splitting out these traits manually, and making sure that each trait has its own associated future type, we side-step the issue of having to answer the questions of a general
LendingFn
/LendingFnMut
implementation, since the compiler knows how to generate built-in implementations for first-class constructs like async closures, including the required future types for the (by-move)AsyncFnOnce
and (by-ref)AsyncFnMut
/AsyncFn
trait implementations.Footnotes
For example, with trait transformers, we may eventually be able to write:
trait AsyncFn = async Fn;
↩For example, via the introduction of a more fundamental "
LendingFn
" trait, plus a special desugaring with augmented trait aliases. ↩