# Async fundamentals ere long ago (2021) ## 2021-12-20 ### Problem with `with` clauses ```rust fn deserialize_and_print_later( deserializer: &mut Deserializer, bytes: &[u8], ) where in('static) T: Deserialize + Debug { thread::spawn(|| { thread::sleep(1); let object = T::deserialize(deserializer)?; println!("{:?}", object); }); } ``` Not good enough. ```rust trait ContextFree: Sealed {} // This is stronger than we need, but works in // all cases. fn deserialize_and_print_later( deserializer: &mut Deserializer, bytes: &[u8], ) where with(ContextFree) T: Deserialize + Debug { ... } ``` ```rust // This is exactly what we need. fn deserialize_and_print_later( deserializer: &mut Deserializer, bytes: &[u8], ) where with(Send + 'static) T: Deserialize + Debug { ... } ``` ```rust dyn Debug // desugars to dyn with(ContextFree) Debug + 'static // or you could write.. dyn with(Bound) Debug + '_ ``` ```rust where T: with(Send) AsyncTrait<async_fn: Send> + Send ``` `* Send`? set interpretation * `with(cap1, cap2)` vs `with(cap1) with(cap2)` * `with(Bound)` means "all capabilities meet `Bound`" * default for an impl is empty set * `with(_: Send)` * `with(Send)` * `with(cap1: Type)` or `with(cap1: impl Trait)` :alternative-universe.gif: * What if we ONLY HAD `Send * Bar` and no `+`... * then you could do * `where T: Send * Debug` // in either order * `where T: Send, T: Debug` // distinct thing * Meaning: * `T: Foo * Send` means "all RPITIT in Foo are Send" Some form of opt-in for non-RPITIT? ```rust trait Foo { // too strong! This makes `Foo * Send` != `Send` type Bar: if (Self: Send) Send } trait Foo { // not this, but like this, to mean that `T: Foo + Baz` where `Baz` is an auto trait // means `T: Foo<Bar: Baz> + Baz #[selfish] type Bar; } ``` ## 2021-12-16 ### year end blog post ```rust async fn main() { for await b in bottles() { send_request(b).await; } } async fn* bottles() yields String { for i in (0 .. 99).rev() { yield format!("{i} crabcakes on the wall"); } } ``` What are the pieces we need to make this beautiful code work * Async fundamentals * Async functions * Portability * async fn main * tcp stream or something * Generator working group * Async iterator trait * Generator syntax * Await patterns * Polish / tooling * Beautiful diagram * draw.io!!!! #### where we were ```rust #[tokio::main] async fn main() { for await b in bottles() { send_request(b).await; } } impl Stream for BottlesStream { fn foo(self: Pin<&mut Self>) { /* OMG I AM DEAD */ } } ``` ```rust Pattern = ... | await Pattern | ? Pattern for await? x in fallible_io_thing() { } for? entry in dir.entry() { /* instead of let entry = entry? */ } let await x = y; // equivalent to y.await; let (await x, y) = foo(); // returns (impl Future, usize) match foo() { (await x, y) } match foo() { Some(await x) None } match foo() { Some(await x) if something } ``` ```rust for x.await? in y { } ``` ### tale of two dyns #### goals * supporting `async fn` calls; * `impl Trait` in argument and return position, so long as `Trait` is dyn safe; * supporting "by value" `self` methods; * supporting cloning of trait objects[^caveat]; * supporting trait objects that are "partially dyn safe". (what other things do we want for dyn that we are not trying to get now) #### use cases * embedded 1 * library uses async fn in a trait and dyn trait * embedded user of library is able to invoke it without calling box to box futures * by e.g. inlining OR the enum solution? * tight loop with reuse * reuse: invoke `foo.bar()` a lot for the same foo * something something uses `&mut` and avoids alloc * regular code * uses box, it's easy, it can clone things * async drop * I drop something * embedded async drop * also in * tight loop up to N bytes * can handle this by making your own trait and doing the erased serde * question mark: * measure the size of futures? #### "standard vtable format" * `dyn Trait` it has a vtable that includes * all the regular fns like today * for these cases... * argument position... * what we need * define how the caller will "package up" the argument in the way the vtable expects * how the vtable will "unpackage it" for the underlying fn * cases * Impl trait in argument position * vtable expects a dynx * dynx where the pointer type is * & * &mut * or Box * depending on our analysis of the trait * By value self in argument position * vtable takes a `*mut T`, effectively `&move` * iow, callee owns the `T` but not the memory the `T` resides in * vtable shim does `let x = *x` and then calls underlying fn with `x` * perhaps some more efficient version * return position... * what we need * define how the callee will "package up" the argument in the way the vtable expects * how will the caller "unpackage it" * By value self in return position * vtable: always takes an out pointer * compiles `*outptr = underlying_fn()` * no normal caller :) * because `foo()` fails to type check if return type is `?Sized` * but there is an intrinsic that we add * `std::ptr::write` basically * takes a `F: FnOnce() -> T` and a `*mut T` (or whatever) and calls the function, writing its result into the raw pointer * now we can write `Box<T: Clone>` impl to use this intrinsic * Eh! Problem `trait Clone: Sized` means we can't have `dyn Clone: Clone`, wtf do we do * Impl trait in return position * vtable returns a dynx * callee can use box * for the case of "inline wrappers" etc * we could customize the mapping * for each of the above: #### Impl trait in arg ```rust fn foo(x: impl Len) { fn helper(x: &dyn Len) { ... } helper(&x) } ``` you wrote ```rust #[dynify] fn foo(x: impl Len) { ... } ``` ```rust fn foo(x: impl Len) { fn bar(x: dynx<'_> Len) { ... } bar(dynx!(x)) } ``` ```rust fn foo(x: impl Len) { foo::monomorphized(&x as &dyn Len) } impl fn#foo { fn monomorphized(x: impl Len) { } #[inline(always)] fn inline(x: impl Len) { .. } } foo(...); foo::monomorphized(...); ``` ```rust #[salsa::jar] struct Jar( foo, bar, ); #[salsa::memoized] fn foo(x: impl Len) { foo::monomorphized(&x as &dyn Len) } #[salsa::memoized] fn bar(x: impl Len) { foo::monomorphized(&x as &dyn Len) } impl fn#foo { fn monomorphized(x: impl Len) { } #[inline(always)] fn inline(x: impl Len) { .. } } #### but ```rust fn foo(x: impl AsyncIterator<next: Send> + Send) // if it would be next::Output fn foo(x: impl AsyncIterator<next(): Send> + Send) fn foo(x: impl AsyncIterator<next(T1, T2): Send> + Send) ``` ```rust fn foo(x: Box<dyn AsyncIterator<next: Send> + Send>) ``` ## 2021-12-13 ### RPITIT #### Bounding function types: What about `const` bounds? ```rust trait Foo { fn foo(&self); } fn foo<T>(x: T) where T: Foo, const T::foo, T::foo: const, {} ``` things to think about - in favor of the associated type being return - but what doesn't work - can't impl traits or add assoc types for fn types - can't do const as a trait as above (Seems nice) - if you have `-> Vec<impl Trait>` or `-> (impl Trait, impl Trait)` , feels weird - whereas `::Output` is very clear about what it is ### dyn Parts of the plan, in rough order (can stop at any point) * Allow `&dyn Len: Len` and so on (see below) * ABI hackery to make by value self work: * `-> Self` via out pointer * Allow `self` via "in pointer" and some stuff * `impl Trait` in argument and return position: subject to **vtable strategy** (can be "always `dynX`") and **caller strategy** * caller strategy could be something like `dynX<Box>` * TODO: outline ways we could do this now, maybe with newtypes * Allow strategies to choose arbitrary pointer types by abstracting with `dynX` * Allow "slicing" traits with `dynX &Trait` * Make `dynX` the newer, easier `dyn` #### `&dyn Len: Len` We can get pretty far with these, I think (may require opt-in) ```rust trait Len { fn len(&self) -> usize; } // Generated impl<P, T> Len for P where P: Deref<Target = T>, T: Len + ?Sized, { fn len(&self) -> usize { Deref::deref(self).len() } } ``` [playground](https://play.rust-lang.org/?version=stable&mode=debug&edition=2021&gist=aa35f5729c35cd68dd81fe4be83e9276) ```rust trait Iterator { type Item; fn next(&mut self) -> Option<Item>; } // Generated impl<P, T> Iterator for P where P: DerefMut<Target = T>, T: Iterator + ?Sized, { fn len(&mut self) -> Option<Item> { T::next(&mut *self) } ``` #### `DerefOwn` * Allows moving out of a smart pointer * Including partial moves * But requires more steps than Deref/DerefMut ```rust trait DerefOwn: DerefMut { // This type manages the allocation, but does not // own the contents after a deref_own. type Residual; // Safety: The returned residual must outlive // the returned OwnedRef. // This will be enforced by the compiler. unsafe fn deref_own(self) -> (Self::Residual, OwnedRef<'static, Self::Target>); fn into_inner(self) -> Self::Target where Self: Sized, Self::Target: Sized { let x = self; deref_own!(x); x.into_inner() } } ``` [playground](https://play.rust-lang.org/?version=stable&mode=debug&edition=2021&gist=376974034ee30ed479b5e2315942b1ad) ## 2021-12-09 * RPITIT implementation * `'static` hack for lifetimes inherited from parent * We need to move these desugarings to something you would actually write * In particular, I'm concerned about `where T: Foo<'a>` when `'a` does not appear in the bounds -- when we use `'static`, that becomes `T: Foo<'static>'`, but that is not true * Desugared type alias has a conceptually different set of generics from the function; we should just copy them ### New RFC * You can write `fn foo() -> impl Trait` in traits and in impls * it desugars to * `type foo<...>: Trait` in a trait * `type foo<...> = impl Trait` in an impl * You can write `fn foo() -> type` in an impl * but do you have to write `type foo = type` too? * if defined using impl trait in the trait, add it? * what happens if you put `type foo = impl Trait` and `fn foo() -> type`? * impl has to match signature from *outside* the defining scope * how many lifetime parameters does the associated type get? * specifically for async fn, change the desugaring to add an extra fn parameter `'x` which is * introduce shorthand for `T::foo` where `foo` is a GAT * expands to `for<'a> T::foo<'a>` ```rust trait Foo { fn foo(&self) -> impl Debug; } // Error: signature of `foo` is -> impl Debug not -> () impl Foo for () { type foo = impl Debug; fn foo(&self) -> () { () } } // Error: two definitions of `foo` impl Foo for () { type foo = (); fn foo(&self) -> impl Debug{ () } } // OK: Not refined impl Foo for () { fn foo(&self) -> impl Debug{ () } } // OK: Refines to `()` impl Foo for () { type foo = (); fn foo(&self) -> () { () } } // OK: Refines to `()` impl Foo for () { type foo = (); fn foo(&self) -> Self::foo { () } } fn bar() { let x: <() as Foo>::foo = ...; // ^^^^^^^^^^^^^^^^ let x: _ = <() as Foo>::foo(); // ^ ?? } ``` ```rust trait Foo { type bar<'x>; fn bar<'a, 'b, 'c>(x: &'a u32, y: &'b u32) -> Self::bar<'c> where 'a: 'c, 'b: 'c; } impl Foo for () { type bar<'x> fn bar<'a, 'b, 'c>(...) -> Self::bar<'c> where 'a: 'c, 'b: 'c // ^^^^^^^^^^^^^^ early bound (b/c 'c appears in where clauses) {} } ``` ```rust fn foo<T: Foo>() where <T as Foo>::bar: Send {} ``` ### Dyn * [dyn trait design outline](https://hackmd.io/ZgcyvcklRba50m53pg3wkA) * supporting clone for `Box<dyn Clone>` * extend `Clone` with an unsafe method * `unsafe fn clone_to_raw(&self, *mut ())` * extend `impl<T: Clone> Clone for Box<T>` to be * `impl<T: Clone + ?Sized> Clone for Box<T>` * you call `clone_to_raw` * another way to think about this * in the vtable for a `-> Self` fn, we put a fn that expects a `*mut Self` and "does the right thing" * need some intrinsic to call it * Rc's copy-on-write methods e.g. `Rc::make_mut` * could make that work for `T: ?Sized` ```rust struct DynX<trait B> { x: *mut dyn B } ``` * if you want to support `-> impl Trait` with dynamic dispatch * you want to return a `DynX<Trait>` * in other words: * "some pointer to something that implements Trait" * "and has a vtable slot for how to drop that pointer" * vtable interface: * in procedural macro, there wasn't one vtable interface * it was defined by this erased trait that carried the methods we wanted ### The dyner design Introduce a `DynX<Trait>` type into the standard library: * by some magic it holds a `*mut Trait` and knows how to drop itself The vtable for an impl of a trait: * If method has `-> impl Trait`, vtable expects to return a `DynX<Trait>` * each impl will require some "glue" to take the actual return value and make it a `DynX<Trait>` * for example, if it returns some future type `F` * that may need to be `Box::pin`'d to get a pointer `Pin<Box<F>>` * and then we attach the vtable to get a `DynX<Future>` * at the point where we generate the vtable for a given type: * there is a point in compilation where we are coercing from a known type to a dyn type * and that is where we do whatever adaptation we have to do * how do we determine that adaptation? * for now just assume there is an oracle * `Oracle(Trait) = adaptation` * somehow can be overriden on a per-impl basis * some fixed set of strategies you can pick from * If method has a `impl Trait` argument, table expects a `DynX<Trait>` as well * as above, there is some glue, but this glue has to be the same for all instances of the trait * has to be customized per trait or perhaps some "wrapper" type * we could probably do some kind of `&move` thing * so long as trait doesn't have a `'static` bound * If method returns `-> Self`, we .. do something like we descirbed earlier so that you can invoke this with a custom return pointer * the vtable has a method takes an explicit `*mut Self` * the vtable can do whatever ABI needs to make this work * need an intrinsic to call a fn whose return type is `?Sized` and you supply the pointer where the result goes * at monomorphization time, if return type is sized, this is just a regular call (`std::ptr::write(...)`) * if it is unsized (e.g., dyn) we invoke the vtable method * we do the right for `[T]` whtaever that is (memcpy?) Are the idea of `&Trait` bounds important? You write `dyn trait` on your trait and we enforce: * it will add `impl<T> Trait for &T` and friends (not really needed, because we are passing a dynx now) * you only use `impl Trait` with other dyn trait traits * niko wants this for soundness ```rust= trait MyTrait { fn adapt( &self, x: impl(Box::pin) FnOnce(), ) -> impl Iterator<Item = u32>; } impl MyTrait for MyType { fn adapt( &self, x: impl FnOnce(), ) -> impl(Box::pin) Iterator<Item = u32>; } ``` * individual impls can't "fail" to produce a vtable * would be a post-monomorphization failure * but they could "panic" in ## 2021-12-06 tmandry `with` blog post draft: https://hackmd.io/LkzoeLaaQV2MMh512WSG7A?view https://github.com/nikomatsakis/dyner/blob/main/src/dynerx.rs ### what is the "built in" story * you will create a `dyner Trait` from some "pointer to T where T: Trait" * "pointer" is anything that supports `into_raw` and `from_raw` * e.g. could be `Box<T>` or `Rc<T>` or `&T` or `&mut T` * `dyner Trait: Sized` * `dyner Trait: Trait` * but wait: for `Rc<T>` or `T`, you can only get `&self` * for `&mut T`, can only get `&self` or `&mut self` * for some traits, that's okay * so `&'a T` can become a `dyner Debug + 'a` * but for other traits, not so ok * but a `&'a T` cannot be made into a `dyner Iterator` * so what do we do? * answer: introduce `&Trait` and `&mut Trait` bounds * intuitively these are either "views" on to Trait that select a subset of methods * or perhaps they are equivalent to `Deref<Target: Trait>` and `DerefMut<Target: Trait>` * but in any case you can make a `dyner &Iterator` from `&T` * whichever way: * `dyner &Iterator: Deref<Target = dyner Iterator>` * observation: `&dynx Debug` has "double indirection" * if you have an `&impl Debug`, and you want to use it in dyn form *without* double indirection * you could do `dyner Debug + Copy` (for this case) impl/dynx symmetry: ```rust fn foo(x: impl Debug) { } // you can do `foo(22)` or `foo(&22)` fn bar(x: dynx Debug) { } // would like if you could do `bar(22)` or `bar(&22)` // ^^^^^^^ but this requires some kind of "auto-ref" ``` observation: each trait has a "natural pointer type" * if only `&` or `*const`, then `&` * if only `&mut` or `*mut`, then `&mut` * else `Box` ```rust fn foo(x: &impl Debug + ?Sized) { bar(x) } fn bar<T: Debug>(x: &T) { ... } ``` Questions: * How do you create a `dyner`? Coercion? Is there an explicit form? * If the answer is coercion, what can you create a `dyner` from: * from the pointer? (most explicit, need to have this option) * from the pointee? (requires auto-ref of some kind) * maybe sometimes one, sometimes the other? If we limit ourselves to pointer, then, if you start with this code: ```rust fn foo(x: impl Debug) { ... } foo(22) ``` and you change `foo` to use `dyner`, you start to get errors: ```rust fn foo(x: dyner Debug) { ... } foo(22) // ERROR ``` arguably that is the wrong code, and what you wanted is ```rust fn foo(x: impl Debug) { bar(&x) fn bar(x: dynx Debug) { ... } } foo(22) // ERROR ``` maybe we just want to make that more syntactically pleasant. Niko's conclusions: * Trying to make a super smart coercion will be a leaky abstraction and wind up both unsatisfying and confusing * We need another mechanism to avoid monomorphization costs (e.g., dyn type parameters) * We should have coercions *from* a pointer * we might want an explicit form like `expr.dyner`, plausibly that could do auto-ref though Challenge is that we sort of want a few constructors. Function of both the trait and the pointer. | (pointer) | Trait has &self | Trait has &mut self | Other | | --- | --- | --- | --- | | `Box` | `Trait` | `Trait` | `Trait` | | `&T` | `Trait` | `&Trait` | `&Trait` | | `Rc<T>` | `Trait` | `&Trait` | `&Trait` | | `&mut T` | `Trait` | `Trait` | `&mut Trait` | The set of traits that the pointer must implement depends on the `Bounds` in `dyner Bounds`: * Must always implement `IntoRaw` (a new trait afawk) * If `Bounds` includes has an element with a `self: &Self` method: * must implement `Deref` * If `Bounds` includes has an element with a `self: &mut Self` method: * must implement `DerefMut` * If `Bounds` includes has an element with a `self` method: * must implement `IntoInner` (a new trait afawk) * or `DerefMove` * If `Bounds` includes has an element with a `Box<Self>` method: * must be `Box<?>` * If `Bounds` includes has an element with `Pin<&Self>` method: * must implement `Pinned` (where `Pinned` is an unsafe trait implemented by `Pin<P>`) * must implement `Deref` * If `Bounds` includes has an element with `Pin<&mut Self>` method: * must implement `Pinned` (where `Pinned` is an unsafe trait implemented by `Pin<P>`) * must implement `DerefMut` * If `Bounds` includes has an element with `Pin<Box<Self>>` method: * must implement `Pinned` (where `Pinned` is an unsafe trait implemented by `Pin<P>`) * must implement `DerefMut` * must be `Pin<Box<?>>` less is more design: * `dyn trait Trait` * automatically provides the `impl Trait for &dyn Trait` impls * automatically provides the `impl Trait for &mut dyn Trait` impls * automatically provides the `impl Trait for Box<Trait>` impls * errors if Trait is not dyn safe * Question: * how does it handle `-> impl Trait`? * Answer: Trait must also be `dyn` * the associated type for `dyn Trait` is hardcoded to `Box<Trait>` * generates the vtable shim; we know that `Box<Trait>: Trait` because `dyn trait` guaranteed that * how does it handle `impl Trait` in argument position * Answer: Trait must also be dyn * can invoke with `&x`/`&mut`/`Box::new` of the parameter depending: * if the trait has only borrowing methods and does not require `'static`, can use `&` or `&mut` * else must use `Box` * what about `-> Self`? (notably `Clone`) * uses `Box` * final methods can be handled and have no restrictions * or even defaulted -- we just say that the default is what gets invoked * probably with some opt-in * replaces `where Self: Sized` ### niko noodles post meeting * challenging cases * impl trait in APIT: * in general case need something like `Box` * how to make this "no-std compatible" * what type does the thing in the vtable expect? * something like the dyner version would be good * it has a `*mut dyn Trait` and a vtable extension that lets it be dropped * what about `-> Self`? * we want to return the same type we got in -- well, probably? * so if you have `Box<dyn Trait>` you want `Box<dyn Trait>` afterwards? * is that true? I often have `&T` and I invoke clone to get `T` * maybe with `&dyn Trait` I want `Box<dyn Trait>` * then conceivably implement Clone for `&dyn Trait` where `Trait: Clone` that returns `Box<dyn Trait>` * vtable would include a wrapper that ## 2021-12-02 https://dyner.netlify.app/faq#does-dyner-have-a-theme-song [with clause post](https://hackmd.io/LkzoeLaaQV2MMh512WSG7A?view) ### Built-in `dyner` What is dyner... * you make a type `DynFoo` that represents a particular "configuration" of dynamic dispatch * how to "box" `-> impl Trait` * how to "box" the dyn value itself (if you do box it) * this type encapsulates the pointer + the pointee (so it is sized) * i,e., you don't do `Box<DynFoo>`, you just do `DynFoo` (or `&DynFoo` etc) * if you make a `DynFoo` from an `&impl Foo` you get a `Ref<DynFoo>`, which only supports `Deref` trait * side note: can prob get rid of that "bit twiddling" to track whether to free * maybe in the future * which functions to "devirtualize" * instead of `where Self: Sized`, making a 'default' that runs * ergonomic easy path * possible other paths ```rust dyn trait Foo { } ``` ```rust dyn<Box> Foo &dyn<Box> Foo ``` ```rust trait SomeTrait { fn link(self: Rc<Self>); } x: Rc<dyn SomeTrait> x.link(); x: exists<R: SomeTrait> Rc<Rc<R>> x: dyn<R: SomeTrait> Rc<Rc<R>> fn foo<dyn T: SomeTrait>(x: &T, y: &T) { .... } ``` --- let's use virtual ```rust virtual trait Foo { } let x: virtual Foo = Box::new(some_foo); let x = DynFoo::boxed(some_foo); let x = virtual Foo::boxed(some_foo); let x = virtual some_foo; fn foo(x: virtual FnMut<Item = u32>) {} ``` today I write: ```rust foo(|| ...) fn foo(x: impl FnMut<Item = u32>) {} ``` to make it dynamic today I have to add `&mut` both places ```rust foo(&mut || ...) fn foo(x: &mut dyn FnMut<Item = u32>) {} ``` this would be nicer.. ```rust foo(|| ...) // compiler autorefs // foo(&mut || ...) // relying on: // &mut dyn FnMut<Item = u32>: dyn FnMut<Item = u32> fn foo(x: dyn FnMut<Item = u32> + '_) {} ``` ```rust fn foo(x: impl Debug) {} ``` https://pol.is/report/r2p6mj8dka3kpsnymv6aj https://rust-lang.github.io/async-fundamentals-initiative/evaluation/challenges/dyn_traits.html https://rust-lang.github.io/async-fundamentals-initiative/design-discussions/dyn_trait.html https://rust-lang.github.io/async-fundamentals-initiative/design-discussions/dyn_async_trait.html ```rust fn foo(x: impl FnMut<Item = u32>) { fn bar(x: &mut dyn FnMut<Item = u32>) { ... } bar(&mux x) } ``` ```rust trait VtableFoo { fn vtable_foo(&mut self, x: DynRef<Item = u32>) } fn foo(&mut self, x: impl FnMut<Item = u32>) { self.vtable_foo(DynRef::from_mut_ref(&mut x)) } ``` ```rust dynX Foo dynX<Rc> Foo &dynX<Rc> Foo // Defaults to allocating a Box; erases that knowledge let x: dynX Future = /*box*/ some_async_fn(); // Defaults to <unknown container> fn foo(x: dynX Future) {} // Can also support fn foo(x: dynX<Rc> Future) {} ``` `dyn &Future` `dyn &mut Future` `&dyn &Future` => `&dyn Future` `&mut dyn &mut Future` => `&mut dyn Future` `dyn &Future: Deref<Target = dyn Future>` ```rust trait MyCollection { fn push(&mut self); fn len(&self); } fn foo<T: &MyCollection>(t: T) { } fn foo<T: Deref<Target: MyCollection>>(t: T) { } dyn &mut Iterator<Item = u32> X = dyn DerefMut<Target: Iterator<Item = u32>> &mut X => &mut dyn Iterator<Item = u32>> // deref coercion ``` ## 2021-11-29 ### stakeholder meeting retrospective? * publish minutes "as is" / or try to write a summary * email people that we plan to, give them a chance to raise any concerns * defer: write a blog post that tells about the lessons and experience and summarizes * https://hackmd.io/SwLsaDDmTSCLKQfE2DYk1Q ### RPITIT / async fn How Niko is thinking about things #### Step -1 What is missing from the impl trait in traits RFC? ```rust trait Iterator { fn next(&mut self) -> Option<impl Sized>; } ``` ```rust trait AsyncTrigger { fn start(&mut self) -> impl Future<Output = ()>; } ``` ```rust= fn foo(x: impl AsyncTrigger) where /* typeof x.start() */: Send, // <-- thing we want conceptually x::start: Send, // <-- shorthand for<'me> x::start<'me>: Send, // <-- longhand { tokio::task::spawn(async move { x.start().await; // type of x.start() is some "impl future" }); } ``` #### Step 0 ```rust trait LendingIterator { type Item<'me>; fn next(&mut self) -> Self::Item<'me>; } impl<T> LendingIterator for Vec<T> { type Item<'me> = &'me T; // Error: not WF, `T: 'me` not known fn next(&mut self) -> Self::Item<'me> { self.iter() } } ``` #### Step 1 ```rust trait LendingIterator { type Item<'me> where Self: 'me; fn next(&mut self) -> Self::Item<'me>; // ^^^^^^^^^ implied bound of Self: 'me } impl<T> LendingIterator for Vec<T> { // compiles! type Item<'me> = &'me T where Vec<T>: 'me; // => T: 'me fn next(&mut self) -> Self::Item<'me> { self.iter() } } ``` Status for GATs as of a few weeks ago: * Algorithm for finding which where clauses ought to be included by default on GATs * Current compromise: * force user to write where clauses explicitly * thus backwards compatible to make them appear by default OR make them not required #### Step 2 ```rust trait LendingIterator { fn next(&mut self) -> impl Sized + '_; // desugars to: fn next(&mut self) -> Self::next<'_>; type next<'me>: Sized where // <-- talk about impl details here Self: 'me; } ``` #### Step 3 ```rust trait LendingIterable { fn iter(&self) -> impl LendingIterator + '_; } ``` But what if you want to know "what kind of item is going to be producted by the iterable that iter returns"... `IntoIterator` has `IntoIter` (type of iterator that is produced) and `Item` (type of item yielded by that iterator). ```rust trait Iterable { final type Item<'me> = Self::iter<'me>::Item; // <-- error! fn iter(&self) -> impl Iterator + '_; } impl<A: Debug> Iterable for A { default type Item<'me> = ...; // if there is no specializing impl, then ITem will be this fn iter(&self) -> impl Iterator + '_; } ``` #### Step 3.5 ```rust trait Iterable { final type Item<'me> = Self::iter<'me>::Item where Self: 'me; fn iter(&self) -> impl Iterator + '_; // implied bound: where Self: '_ } ``` #### Step 4 ```rust struct MustBeEq<T: Eq>(T); // no error: type Foo<T> = MustBeEq<T>; // use it with something that is not Eq, I get an error at that point ``` ```rust struct MustBeEq<T: Eq>(T); type Foo<T> where T: Ord = MustBeEq<T>; // ^^^^^^^^^^^^ unenforced ``` ```rust struct MustBeEq<T: Eq>(T); type Foo<T> = MustBeEq<T> where // <-- default MustBeEq<T>: OK; // in libcore trait OK { } impl<T: ?Sized> OK for T { } ``` #### Step 5 ```rust trait Iterable { final type Item<'me> = Self::iter<'me>::Item; // compiler default: // where Self::iter<'me>::Item: OK // from which we can infer that Self: 'me fn iter(&self) -> impl Iterator + '_; // desugars to: fn iter(&self) -> Self::iter<'_>; type iter<'me> where &'me Self: OK; // => Self: 'me } ``` What does this need? * OK trait and our handling of WF would have to be tweaked * callee gets to assume its where clauses are well formed * not just its input arguments * tweak to "implied bounds" ```rust= struct Foo<'me, T>(&'me T); // inferred where clausE: where T: 'me fn bar<'me, T>() where Foo<'me, T>: OK { // Today: error unless you add // where T: 'me // // Under Niko's proposal above: compiles fine } ``` ```rust struct Foo<T: Eq> { } // T: Eq => Foo<T> WF // // can I say the other direction? // // Foo<T> WF => T: Eq -- semver question // // but we would add the `=>` direction for outlives bounds, so that's a little wacky ``` #### foo ```rust= fn foo<'a, T>(x: &'a T) -> impl Sized where T: Foo<'a>, { } type Foo<'a, T> = impl Sized where T: Foo<'a> // <-- weird; // T: Foo<'static> // not true fn foo<'a, T>(x: &'a T) -> Foo<'static, T> where T: Foo<'a>, { } type Foo<'a, 'b, T> = impl Sized where T: Foo<'a> // <-- weird; // T: Foo<'static> // not true fn foo<'a, T, 'b>(....) -> Foo<'static, 'b, T> where T: Foo<'a>, { } ``` ### ```rust trait LendingIterable { final type Item<'me> = Self::iter<'me>::Item /* implied: where Self::iter<'me>::Item: OK */ ; fn iter(&self) -> impl LendingIterator + '_; } ``` ### existential lifetimes in impl trait https://github.com/rust-lang/rust/issues/60670 ### with types? ## 2021-11-19 ### Stakeholders retrospective * Call on people to give feedback * Got a little sidetracked * Need to talk about overall roadmap ## 2021-11-18 * [ ] Stakeholder meeting * [ ] "dyn adaptation" * [ ] Can we make it implementable by macros outside the crate defining the trait? * [ ] What's the ergonomic path? * [ ] `dynX` idea * [ ] What are the problems facing it? * [ ] Idea for conditional bounds (Niko) * [ ] How to help * [Stakeholder explainer](https://hackmd.io/_fnM79SsSIWo86bdZXuNPw) * `DynTrait<'lt>` * before: this always encloses a `Box` * after: * can initialize from box * or from &T or &mut T * and can avoid giving access to the methods that would not be accessible in the latter case --- ```rust trait Foo { fn foo(&self) -> impl Debug; } trait Foo { type foo<'me>: Debug; fn foo(&self) -> Self::foo<'me>; } impl Foo for Bar { // because foo was declared in trait as RPITIT... fn foo(&self) -> XXX { } // ...it becomes... type foo<'me> = XXX; fn foo(&self) -> Self::foo<'me> { // so you don't have to put impl trait if you don't want to } } fn method<T>() where T: Foo<foo: Send>, for<'a> T: Foo<foo<'a>: Send> T: Foo<foo<..>: Send>, // ^^^^ ``` * `'_` in where clause == forall * turbofish rules for these GATs: * if there are APIT, cannot use turbofish * if you do not supply any lifetimes: use `'_` for all of them * if you *do*, there cannot be any late-bound lifetimes (else error) (this rule isn't actually needed for this idea) * else must supply all * must supply all type parameters * for other GATs: ```rust struct Foo<T: FnOnce()> { } struct Foo<T: FnOnce(), const F: T> { } impl<T: FnOnce(), const F: T> Foo<T, F> { fn foo() { F(); } } ``` ```rust const fn<T: FnOnce(C), C>(f: T) -> fn(C) { assert!(sizeof::<T>() == 0); } const fn<T: FnOnce(C)>(const f: T) -> fn(C) { assert!(sizeof::<T>() == 0); } // "Non-stateful closure" ``` --- ```rust struct Ref<T> { t: T } impl<T> Deref for Ref<T> { type Target = T; fn deref(&self) -> &T { &self.t } } struct RefMut<T> { t: T } impl<T> Deref for RefMut<T> { type Target = T; fn deref(&self) -> &T { &self.t } } impl<T> DerefMut for RefMut<T> { fn deref_mut(&mut self) -> &T { &mut self.t } } ``` ```rust type DynTraitRef<'lt> = Shared<DynTrait<'lt>>; struct Custom<T> { t: T } impl<T> Deref for Custom<'lt, T> where T: Deref, T::Target: { type Target = DynTrait<'lt>; fn deref(&self) -> &T { &*self.t } } impl DynTrait<'lt> { fn boxed<T>(x: T) -> DynTrait<'lt> where T: Trait, { } fn from_ref<T>(x: &'lt T) -> Ref<DynTrait<'lt>> where T: Trait, { } fn from_mut<T>(x: &'lt mut T) -> Mut<DynTrait<'lt>> where T: Trait, { } fn from_mut<T>(x: T) -> Custom<DynTrait<'lt>> where T: Deref, T::Target: Trait, { } } ``` ```rust fn foo(x: &DynTrait<'_>) { } fn bar() { let y = ...; let x = DynTrait::from_shared(&y); foo(&x) } ``` ## 2021-11-15 * [Niko wrote some stuff](https://hackmd.io/78T_K20VQVe-vBxBWWuUlw) * Bridging [poll traits](https://docs.rs/hyper/0.14.14/hyper/body/trait.HttpBody.html) with async traits * Hard with multiple poll functions * Something something resume args ((with?)) * Easier with single poll: inline async fn ## 2021-11-11 * [x] WIP updates * [x] dyn conversation * [x] implementation * [ ] Stakeholder meeting * [ ] "dyn adaptation" * [ ] Can we make it implementable by macros outside the crate defining the trait? * [ ] What's the ergonomic path? * [ ] `dynX` idea * [ ] What are the problems facing it? * [ ] Idea for conditional bounds (Niko) * [ ] How to help ### Action items * (Niko by Monday) Turn outline into something for the repo * (Spastorino by Monday) "code" * Future: Writing up issues for things like inline etc. ### Notes * Going to pursue RPITIT to start * what we will ship * stage 0 * stable compiler supports impl Trait and GATs * stage 1 * stable compiler can compile async fn in traits * no dyn * crate published by rust-lang available on crates.io for dynamic dispatch * dyner * `#[dyner] trait Foo { }` creates `DynFoo::new(...)` that implements `Foo` * supports: * traits with async fn * traits with `-> impl Trait` if `dyner` is used on Trait (maybe?) * traits with `(impl Trait)` if `dyner` is used on Trait (maybe?) * includes: * "Dyner"-ified versions of libstd traits like AsyncRead and friends * stage 2 * libstd will add stable versions of * AsyncRead, AsyncWrite, AsyncStream * (domain of the portability initiative) * stage 3 and beyond * based on experience with dyner we figure out an official dyn story * "Someone is coding with dyner... :musical_note::musical_note::musical_note:" * async closures * other fun stuff * what can you do with this * Grace and Alan are hacking on their async rust service at SomeBigCo * Traits they use internally just use async fn * If they want dyn dispatch, they use `#[dyner]` * To talk about read/write traits, they use the dyn-ified versions from dyner * Barbara has a cool idea for an arena-ified alternative (or one that caches .. or something .. ) * she forks dyner and writes her own punny named crate that embodies this pattern * she submits the link to the repo * it's collected and in the doc and when the async fundamentals team gets to stage 3 they use a variation of this brilliant idea in their final design * Grace is working on her embedded project, she can use the stable async fn support to get static dispatch * She has a plan for dyn dispatch and experiments and publishes her own procedural macro crate which the async fundamentals group adds to their repo * Opens a PR with an experimental of version of embedded-dyner * Hyper publishes a "hyper-dyner" crate that includes dyn-ified versions of its traits * or maybe Sean Monstar opts for a feature flag. Whatever dude. You do you. * Niko's wild and crazy Rust 2024 fantasies * "feature previously known as dyn" => low-level primitive, maybe it is even replaced with existential types or something * observation: if dyn is a low-level primitive, "dyn-safe" is silly, you want to expose as many capabilities as you can * "low-level"? not the thing most code reaches for first * sort of like `for<'a>` * you have `dyn trait Foo` that exposes some "pretty useful" version (probably with Box) that is ergonomic and good enough most of the time * ability to declare your own variations in other crates (with traits you didn't define and don't have to copy and paste) and use those * you have "existential types" (which replace today's `dyn`) as the "fancy version" to build those nicer, ergonomic versions with * and for scala refugees * "inline async at impl site" ?? * don't recurse!!! * add in a panic at runtime * ## 2021-11-07 * [x] WIP updates * [x] RFC * [x] Stakeholders * [x] Implementation * [x] Syntax for named fn types * [ ] "dyn adaptation" * [ ] Can we make it implementable by macros outside the crate defining the trait? * [ ] What's the ergonomic path? * [ ] `dynX` idea * [ ] What are the problems facing it? * [ ] Idea for conditional bounds (Niko) * [ ] How to help ### WIP updates #### RFC * Should we talk to cramertj? * Responded in [comments](https://github.com/rust-lang/rfcs/pull/3185#issuecomment-961499403) #### Stakeholders Looking at https://alternativeto.net/software/doodle/ #### Implementation Can use `type $ = impl Foo;` to implement RPITIT, and then implement `async fn` desugaring in terms of that. ### Syntax for named fn types * It would be nice to have explicit and anonymous generics be equivalent * but it doesn't seem to work very nicely * you need `decltype` and then `declval` * We can make anonymous generics late-bound * Hopefully explicit generics too, eventually * Still need to support `let f = foo::<i32>;` * Requires making some "wrapper" to hold the specified generics. i.e. type currying ## 2021-11-03 * [x] WIP updates * [x] RFC * [x] Implementation * [ ] Niko's dyn idea * [ ] Syntax for named fn types ### RFC conversation [cramertj comment](https://github.com/rust-lang/rfcs/pull/3185#issuecomment-956430290) Will we be able to do this in the future? Can libs assume it will be possible in the future? Backcompat hazard... ```rust trait Foo { async fn bar(&self); } // Right now RFC doesn't allow this... impl Foo for u32 { fn bar(&self) -> impl Future<Output = ()> + '_ {} // only `async fn bar() {}` allowed } // But if I'm writing a lib.. ``` ### Implementation ```rust async fn foo() {} // Lowers to... type Foo = impl Future<Output = ()>; trait Foo { async fn bar(&self); } // Lowers to... trait Foo { type __Foo<'a> = impl Future<Output = ()> + 'a; fn bar(&self) -> __Foo<'a>; } // But in the future we want... // (almost exactly what standalone functions do) trait Foo { fn bar(&self) -> impl Future<Output = ()> + '_; } // ..which then lowers to something like the above ``` Maybe faster to implement RPITIT first. https://github.com/rust-lang/impl-trait-initiative https://rust-lang.github.io/impl-trait-initiative/updates/2021-oct.html#type-alias-impl-trait-stabilization ```rust trait Foo { fn foo() -> impl Send; } ``` ## 2021-11-01 * [x] New sprint goals review * [x] Stakeholder agenda * [ ] Niko's dyn idea * and another idea for conditional bounds * [ ] Syntax for named fn types? ### Sprint goals review * Get [MVP RFC](https://github.com/rust-lang/rfcs/pull/3185) to FCP * Niko [fcp merged](https://github.com/rust-lang/rfcs/pull/3185#issuecomment-956370267) * Implementation: * Santiago has done a basic desugaring but tests are failing still ### Stakeholder meeting agenda * 90 minutes * Who will present? tmandry mostly * Outline * Format * We'll talk * You ask questions whenever they come up, no need to wait * Async vision doc overview (keep it short) * The grand [roadmap]: * Scoped APIs for reliable concurrency * Common interop traits are just "async fn" * Our part today: * The very first step * Other active initiatives: * Generators async and otherwise * Tooling (tokio console, crashdumps) * Polish (the bug! backtraces!) * What does async fn in traits mean * Async fn: fn that returns impl Future * impl Trait in traits desugars to associated type * Send bounds and named return types * The syntax we expect * Where would this be needed * Feedback point: * What have your experiences been with Send bounds in generic code so far? * Do your codebases frequently use `spawn` or other parallel operations? * This is the MVP: * You can write async fn in traits * It works in static cases * Writing the Send bounds for futures is kinda painful but possible * Major capability missing: dynamic dispatch * Associated type prevents dynamic dispatch * Also, boxing * Not sure the most general fix here * Current approach: * procedural macro * Feedback point: * Where do you use dyn dispatch vs static in your codebases? * Shortcomings of MVP and future work * Dynamic dispatch in the lang * Shorthands or better ways to put send bounds * Multiplication bounds (!) * Globs * Implementation status * Core features are on path to stabilization * Async sugar is on its way to nightly * We'll let you know when it's time to port your code to try this out and give instructions for how to do it [roadmap]: https://rust-lang.github.io/wg-async-foundations/vision/roadmap.html ### Niko's crazy dyn idea * `dynx Trait` * "A pointer to `Trait` that implements `Trait`" * whereas: * `dyn Trait` = `exists T. Implemented(T: Trait)` * whereas: * `dynX Trait` = `exists T. sizeof(T) == sizeof(usize) && Implemented(T: Trait)` * probably want to combine this with: * `impl<T, U> Trait for T where T: Deref<Target = U>, U: Trait` -- automatic for traits with only `&self` methods * `impl<T, U> Trait for T where T: DerefMut<Target = U>, U: Trait` -- automatic for traits with only `&mut self` and `&self` methods * `impl<T: Trait> Trait for Box<T>` -- always true * just imagine this is true for now * what does this mean? * `Box<dyn Iterator>` == `dynx Iterator + 'static` * `Rc<dyn Debug>` == `dynx Debug + Clone + 'static` * `&'a dyn Debug` == `dynx Debug + Copy + 'a` * for any dyn safe trait `Trait` ```rust trait MyTrait { fn foo(x: impl OtherTrait) { // we know that `x` does not escape this function // without being tracked, because `x` could be a `&T` } } ``` we want `MyTrait` to be dyn safe, but we can't put `foo` into the vtable because it requires monomorphization because we know that `&T: OtherTrait` where `T: OtherTrait`, however, we could put this in the vtable: ```rust fn vtable_foo(x: dynx Trait) { ... } ``` and then in the `impl MyTrait for dynx MyTrait` impl use this function: ```rust impl MYTrait for dynx MyTrait { fn foo(x: impl Trait) { vtable_foo(&x) } } ``` Of course, this doesn't really require `dynx`. You could do same transformation with `dyn`, if you know that `&T: Trait` where `T: Trait`. You put this in the vtable: ```rust fn vtable_foo(x: &dyn Trait) { ... } ``` and this in the `impl MyTrait for dyn MyTrait`: ```rust // shim user calls: fn foo(x: impl Trait) { vtable_foo(&x) } ``` ### Step back and just talk about dyn and erased serde ```rust trait MyTrait { fn foo(x: impl OtherTrait); } trait OtherTrait { fn bar(&self); } impl<T> OtherTrait for &T where T: ?Sized + OtherTrait { fn bar(&self) { T::bar(self) } } ``` What could we do? We could write: ```rust trait DynMyTrait { fn dyn_foo(x: &dyn OtherTrait); } impl<T> DynMyTrait for T where T: MyTrait, { fn dyn_foo(&self, x: &dyn OtherTrait) { <T::foo as FnOnce(&dyn OtherTrait)>::call_once(self, x) // ^^^^^^^^^^^^^^^ // type given to the impl trait } } ``` The final "knot" is that one can implement ```rust impl MyTrait for dyn DynMyTrait { fn foo(&self, x: impl OtherTrait) { let x = &x as &dyn OtherTrait; // ^^^^^^^^^^^^^^^ // Possible because `OtherTrait` is dyn safe self.dyn_foo(x) } } ``` Using all these pieces, if you have a `&T: MyTrait`, you can ... * convert the `&T` to `&dyn DynMyTrait` * convert the `&'a dyn DynMyTrait` to `impl MyTrait + 'a` * not that useful * ## 2021-10-27 * Syntax for named function types * https://hackmd.io/WQUfas2jSiaeIv6EBPgtew * Plan around dynamic dispatch * Stakeholder meeting, agenda plan? * Next steps for dyn * `with` clauses? ### Late-bound and arguments ```rust trait Foo { async fn bar(&self, x: impl Debug); } ``` ```rust fn my_helper<T: Foo> where T::*::Output: Send <T as Foo>::fn::bar::Output: Send for<D: Debug + Send> { <<T as Foo>::fn::bar as FnOnce(&T, D)>::Output: Send } <<T as Foo>::fn::bar as FnOnce(&T, impl Debug + Send)>::Output: Send, ``` ```rust trait Foo { async fn bar(&self); } ``` ```rust fn my_helper<T: Foo> where T::fn::bar::Output: Send, // <-- what you write // ^^ look ma, no args -- works for elided lifetime parameters for<'me> { <<T as Foo>::fn::bar as FnOnce(&'me T)>::Output: Send }, // <-- what it means // ^^^^^^^^ defaulting the arguments // from the signature of `fn bar` // and substituting the `Self` or // other trait parameters where T::Item // <T as Iterator>::Item -- T is the Self type ``` ### Stakeholder meeting * Explain the plan * Concept of MVP * MVP * Dyn async fn in traits * using a proc macro to generate a wrapper struct * Naming of output types * Feedback in meeting * How often do you think you will want dyn vs static dispatch? * Feedback after trying it * Naming output types and ### Dyn async fn High-level idea ```rust trait Foo { fn stuff(&self); } // Compiler generates: impl Foo for dyn Foo { fn stuff(&self) { self.vtable[0](self); } } trait Bar { async fn stuff(&self); } impl Bar for dyn Bar { // Post-desugaring type __stuff_output<'a> = Pin<Box<dyn Future<Output = ()>>> + 'a; fn stuff<'a>(&self) -> Pin<Box<dyn Future<Output = ()>>> + 'a {} } impl dyn Bar { fn stuff(&self, arena: &Arena<'a>) -> Pin<ArenaBox<'a, dyn Future<Output = ()>>> {} } ``` ### ## 2021-10-25 ```rust // Warning: For reasons we are in the midst of explaining, // this version of the trait will not compile. trait Iterable { type Item<'me>; type Iterator<'me>: Iterator<Item = Self::Item<'me>>; fn iter<'a>(&'a self) -> Self::Iterator<'a>; } impl Iterable for Vec<T> { type Item<'me> = &'me T; // <-- Error: don't know that T: 'me type Iterator<'me> = std::vec::Iter<'me, T>; fn iter(&self) -> Self::Iterator<'_> { self.iter() } } ``` What we want: ```rust trait Iterable { type Item<'me> where Self: 'me; type Iterator<'me>: Iterator<Item = Self::Item<'me>> where Self: 'me; fn iter<'a>(&'a self) -> Self::Iterator<'a>; } impl Iterable for Vec<T> { type Item<'me> = &'me T where Self: 'me; // Self = Vec<T> // Vec<T>: 'me => T: 'me type Iterator<'me> = std::vec::Iter<'me, T> where Self: 'me; fn iter(&self) -> Self::Iterator<'_> { self.iter() } } ``` Current status of GATs, doing an analysis of the trait def'n to find missing where clauses: * For every GAT `G` in a trait definition with generic parameters `X0...Xn` from the trait and `Xn..Xm` on the GAT... (e.g., `Item` or `Iterable`, in the case of `Iterable`, with generic parameters `[Self]` from the trait and `['me]` from the GAT) * If for every method in the trait... (e.g., `iter`, in the case of `Iterable`) * When the method signature (argument types, return type, where clauses) references `G` like `<P0 as Trait<P1..Pn>>::G<Pn..Pm>` (e.g., `<Self as Iterable>::Iterator<'a>`, in the `iter` method, where `P0 = Self` and `P1` = `'a`)... * we can show that `Pi: Pj` for two parameters on the reference to `G` (e.g., `Self: 'a`, in our example) * then the GAT must have `Xi: Xj` in its where clause list in the trait (e.g., `Self: 'me`). How we desugar RPIT today: ```rust fn foo<'a>(x: &'a u32) -> impl Debug + 'a { } fn foo<'a, T>(x: &'a u32) -> Foo<T, 'a> { type Foo<T, 'me> = impl Debug + 'me; // scope of this is the enclosing fn } ``` ```rust trait Iterable { fn iter(&self) -> impl Iterator + '_; // "desugared" fn iter(&self) -> impl Iterator + '_ type Iter<'me>; // inherits the where clauses } ``` ### Tests Things to add - Generic async fn - with where clauses req'd to type check - Elided lifetimes in async fn ### Impl notes * `async fn` -> return position `impl Trait` -> associated type * Be careful about elided lifetimes * May or may not be easier to start with a nameable associated type * `gensym`? * Ask petrochenkov how to do it * Check what argument position impl trait does ## 2021-10-20 * [RFC Draft](https://hackmd.io/ZKb-lh8iRN66RiuYjALZrA) * tmandry to upload to repo * and open a PR to rust-lang/rfcs * Milestones * Santiago to make a list of tests for desired behavior * read the RFC * Tyler/Niko review list of tests and add anything that seems to be missing * Santiago to write tests for desired behavior * Naive desugaring for async fn in traits/impls * literally introduce an associated type with the same name as the method but with `Output` appended? * Niko to open RFC for `-> impl Trait` in traits * Implement `-> impl Trait` in traits properly * Implement the dyn procedural macro "stuff" * Ergo-dyn milestones (dyno) * Open issues on the repository * Encourage people to do it * [find a new name](https://www.thefreedictionary.com/words-that-start-with-din) * dyner ### When would you want to use dyn anyway? * Parser combinator library * every combinator returns `Box<dyn Parser>` or whatever * In async, passing around a `dyn AsyncRead` or whatever * In sync, passing around a `dyn Read` * Things you lose access to: * Associated constants * Static methods, constructors * Kinds of traits: * Traits where all methods are `&self`; ideally `Trait` would be implemented for `&Trait` * Traits where all methods are `&mut self` * Traits where methods are `self` * In general, `Trait` should be implemented for `Box<Trait>` * In general, `Trait` should be implemented for `Box<Trait>` * Properties of `Box` that we need? Something Niko likes is that you can have an `&dyn Debug` and call it without forcing any allocation ```rust fn bar() { debuglog(&22) } fn debuglog(x: &dyn Debug) { } ``` ```rust= fn map<T>(x: impl Iterator<Item = T>) {} fn main() { } ``` Homework: * Try to write up some example programs and the ergonomic nits that result ## 2021-10-18 * What does the MVP story look like? * You can write async fn in traits/impls but not dyn saf†e * You can't name the resulting future (until we get fn type syntax) * but you can make an explicit associated type if you so choose * There is a procedural macro available `#[ergo_dyn]` that a `DynTrait` struct that encapsulates a `Box<dyn Trait>` and supports async dispatch, impl trait in traits, and other nifty features * If you could declare "cannot be overridden" defaults... ```rust trait AsyncIter { final type NextFuture<'me> = <Self as AsyncIter>::next_future::Output; async fn next_future(&mut self) -> ...; } ``` if you had that option, one could write a decorator to make it convenient ```rust trait AsyncIter { #[return_type(NextFuture)] async fn next_future(&mut self) -> ...; } ``` * What do we want feedback on from the stakeholders? * Do you need dyn? If so, what are the details of how you plan to use it? * Present two crates that let you easily swap back and forth * Can people land the one version? * What problems do they encounter? * After you try it: * Do you need Send bounds? * Can you measure performance impact? * MVP timeline and requirements: * Nightly version * Test suite * Implement the sugar * Procedural macros * Stabilize TAIT -- EOY was my goal, but I'm not sure we're going to make it * Stabilize GAT [Named function types](https://rust-lang.github.io/impl-trait-initiative/RFCs/named-function-types.html) [impl trait in traits](https://rust-lang.github.io/impl-trait-initiative/RFCs/rpit-in-traits.html) ### Late-bound lifetimes ```rust fn foo<T>(x: impl Iterator<Item = T>) { } fn bar<T>(x: impl Iterator<Item = u32>) {} fn main() { let x: Foo<?, ?> = foo; // ERROR today foo(...); } struct MyStruct { f: foo<u32, x: vec::IntoIter<u32>> // conceptually } ``` ```rust trait Foo { async fn bar<U>(&mut self); } // <T as Foo>::bar<u32>::Output // // desugared to...something like this... // // for<'a> <<T as Foo>::bar<'a, u32> as FnOnce>::Output ``` ### Argument types `Foo<..>` // means the fn type let's call this U `<U as FnOnce<(A1, A2)>>::Output` ```rust= let x: FnDef[Foo]<?X, ?Y> = foo; impl<'a, A2> FnOnce<(&'a mut Self, A2)> for FnDef[Foo]<&'a mut Self, A2> { type Output = SomeFutureType<'a>; } ``` ### Can we ... think of `impl Trait` as late-bound? // given the fn foo from line 62 ```rust struct MyStruct { field: foo<u32> // incomplete type // conceptually: // // for<T> { foo<u32, T> } // // Foo<u32> // FnDef just doesn't have a value here } ``` Rule: You need to name all the named type parameters, the rest are all late bound. - Already the case that elided lifetime parameters are always late bound. - For `impl Trait`, when it's called we're just doing `FnOnce` trait selection with the concrete type of that argument. No need to have a different function type for each instantiation. ## 2021-10-13 ### with * `with(x: T)` is a where clause * dyn trait * bounded clauses * `in('a) { T: Trait }` * in the future, would be the default `in('_) { ... }` * "needs to be true independently of the scope I'm in " * `in('static) { T: Trait }` * `Implemented(T: Trait for 'a)` * other use cases: * [safer transmute](https://github.com/jswrenn/rfcs/blob/af9f3012de8a642a9fcaa2f2f334e68f682742ad/text/0000-safer-transmute.md) * crate-local impls (sort of) * maybe not a real use case * implication types * `P => T` * `with(tokio: &mut TokioRuntime) => Box<dyn xxx>` * other use cases: * `for<'a, 'b> fn(&'a &'b u32, &'a u32, &'b u32)` * unsound because of https://github.com/rust-lang/rust/issues/25860 * could upcast `for<'a, 'b> fn(&'static &'static u32, &'a u32, &'b u32)` * `for<'a, 'b> ('b: 'a => fn(&'a &'b u32, &'a u32, &'b u32))` * `for<'a, 'b where 'b: 'a> fn...` * borrow checker integration * I think what happens is that when you prove a predicate you get back a set of in-scope variables that are required * much like today you get back lifetime bounds ```rust struct Foo { } impl Foo { fn foo(&mut self) where with(f: &mut Foo) { // self != f } } fn main() { let mut f = Foo { }; with(f: &mut f) { f.foo(); // Error! } let mut f = Foo { }; let mut g = Foo { }; with(f: &mut f) { let f1 = &mut f; g.foo(); // Error! drop(f1); } } ``` * async live over yield question * are `with` live over await? interesting question can express both patterns ```rust // acts like a fn param fn foo<'a>(...) -> impl Future + 'a where in('a) { with(x: &mut Context) } { } fn foo<'a>(...) -> impl with(x: &mut Context) => Future { } ``` * specialization * specializing impls can't add with clauses ```rust trait MyTrait { fn method(&self) { } } impl<T> MyTrait for T { } fn foo<A>(a: A) { MyTrait::method(&a) } impl<T> MyTrait for T where with(cx: &mut Foo) { } // in another crate fn bar() { foo(); // do I consider `cx` borrowed or not? } ``` * optional / defaulted with clauses * providing something that is `Copy` (e.g. shared ref) means can be used multiple times `Box<dyn Foo + 'a>` `Box<dyn with(cx: &mut Context) => Foo>` `trait WithContext = with(cx: &mutContext>` `trait WithContext<predicate P> = with(cx: &mutContext> P` * complicaiton: * how do you think about bounds on a struct type? * `struct Foo<T: Debug>` * what is the bound here? ### talking about dyn ```rust= trait Foo { // would be nice if this were dyn safe fn foo(x: impl Iterator<Item = u32>) { } } fn foo(x: &move dyn Iterator<Item = u32>) { } ``` ## 2021-10-06 - What about threading local arena through? - This design shouldn't add more complications to that ```rust impl<A: Allocator> Foo for dyn Foo with(a: A) { ... } with(alloc: impl Allocator); ``` https://github.com/rust-lang/rfcs/pull/2492 ```rust impl<'a> Foo<'a> for String { .. } impl Foo for String { fn method<'a>() } ``` ```rust impl Context { fn stuff(&self) -> &Output; } ``` ```rust // crate A: trait SomeTrait { fn method(&self); } // crate B: struct Context { } struct X; impl SomeTrait for X with<'a>(cx: &'a mut Context) { fn method(&self) // can't write it here: // with(cx: &mut Context) { } } ``` ```rust with(value: Type) { ... can use value ... foo(); } ``` ```rust with(cx: &mut Context) with(cx2: &mut Context2) ``` ```rust with(cx: .., cx2: ...) { } ``` ## 2021-10-04 ### Niko muses about dyn ```rust trait Foo { fn method(&self) -> impl Iterator<Item = ...>; } impl Foo for Blah { fn method(&self) -> impl Iterator<Item = ...> { ... /* call the type of this X */ } } ``` * given an instance `x: X` (`let x = <Blah as Foo>::method`) * `x as fn(&Blah) -> XXX` * trait `InstantiateFn<F> { ... }` * `x: InstantiateFn<fn(&Blah) -> XXX>` * Resolves to pointer to impl method * `x: InstantiateFn<fn(&Blah) -> Box<dyn Iterator<Item = ...>>` * Resolves to pointer to wrapper fn around impl method * given this, you can write generic code that generates a vtable * has to run at compilation time: need const fn * you can also write a `impl Foo for dyn Foo` that consumes the vtable ### Impl question Parent of a HIR node? https://doc.rust-lang.org/nightly/nightly-rustc/rustc_middle/hir/map/struct.Map.html#method.find_parent_node ### Pieces * MVP * Ability to get type of a fn item * fallback syntax to name items * what about constants? * for now we should make them an error to leave space for future expansion * Ability to construct dyn vtables and implement dyn manually * (Maybe) trait aliases ``` MyTraitSend!(X) ==> X: Send + X::foo: Send trait MyTraitSend = MyTrait + Send + <Self as MyTrait>::foo: Send + Bar: Send X: MyTraitSend trait Foo<T> where T: Ord = Bar where T: PartialOrd impl SomeTrait for X { // Today: // What Niko always types type Foo<T> where T: Ord + T: Send + T: Blah = Bar; // What Niko always types type Foo<T> = Bar where T: Ord + T: Send + T: Blah; } trait Foo<T> = where T: PartialEq<Self> trait Foo<T> = T: PartialEq<Self> X: Foo<T> => T: PartialEq<X> (): Foo<T> "write an arbitrary predicate with no self" ``` Next: Arenas ### Sprint items * Stakeholders * Niko found 2 in AWS * * MVP RFC * needs work * create a hackmd and write it together * Evaluation for the output type naming (niko will call it impl trait) * needs work * sort of belongs in the impl trait initiative * create a hackmd and write it together ```rust trait Foo { async fn bar(); async fn baz(); } type AllFooFutures<T: Foo> = (<T as Foo>::bar::Output, <T as Foo>::baz::Output); T: Foo + Send where AllFooFutures<T>: Send where T: FooSend // trait alias for the above ``` ## 2021-09-30 * Async stakeholders plan: [link](https://hackmd.io/y31gA3ElSu2DUdY6vUGs8A) * Possible stakeholders * Web services: Niko has contacts in AWS * Embedded Rust: Dario * Web framework author: * Web framework consumer: * High-performance computing: glommio author * Sourcing * Dario * Alice Rhyl * Glauber Costa (glommio) * farnz / Simon Farnsworth * Android (BT?) * Fuchsia (BT?) - Marie * AWS people x 2 -- agreed * Yosh --- * dyn for any trait * do not have to specify the associated types (ever) * maybe you can emit elide the other trait parameters * but: * you can only directly use members that meet the safety criteria for your dyn type * they can't name associated types whose values you don't know * they can't reference the generics whose values you don't know (including Self), at least not in contravariant positions * and then: * dyn trait Foo * implements the Foo for dyn Foo impl in a particular way * can give an error if that can't be done * and you can give hints on methods (maybe?) to configure it a bit * another part: * what do we need to allow people to implement the dyn impl (unsafely) and what do we need (safely) * goodies that extend what is dyn safe a little bit * mapping `impl Trait` return types (or associated types) for dyn safe traits to `Box<dyn>` and allocating * impl trait in argument position ===> pass dyn by value? maybe Boxed? [impl trait in traits brainstorming doc](https://hackmd.io/IISsYc0fTGSSm2MiMqby4A) ```rust= trait Foo { fn method(&self); } fn fun<T: Foo>(x: T) where T::method<'_>::Output: Clone {} ``` * for anonymous lifetimes, or if lifetimes are elided, always hrtb * the same rule for gats * probably `'_` should mean hrtb too ```rust fn fun<T: Foo>(t: T) where T: SomeTrait<'_>, // would be nice if this meant for<'x> T: SomeTrait<'x> // Meaning 1 fn fun<T: Foo>(t: T) where for<'a> T: SomeTrait<'a>, // would be nice if this meant for<'x> T: SomeTrait<'x> // Meaning 2 fn fun<'a, T: Foo>(t: T) where T: SomeTrait<'a>, // Meaning 3 fn fun<T: Foo>(t: T) where T: SomeTrait<impl Trait>, // Today: error fn fun<T: Foo>(t: T) where for<U: Trait> T: SomeTrait<U>, // Today: error ``` Niko's argment: * '_ and impl Trait both should "introduce name into binder" at the same depth in all "input" positions * in output positions, they "refer back" to a name from somewhere * `'_` refers to self lifetime * `-> impl Trait` returns to return type of body * in where clauses: * you don't want to introduce a fresh name at the *item* level because it would be unconstrained * (this is less true for lifetimes for "reasons" but definitely true for types) * so you probably want a forall at the where clause level * exception: value of associated types https://github.com/rust-lang/async-fundamentals-initiative/pull/3/files ```rust= // Later we could experiement with, e.g.: fn fun<T: Foo>(x: T) where T::*::Output: Send {} fn main() { (1..3).collect::<Vec<i32>().map(<Foo as Bar>::method) } ``` https://lang-team.rust-lang.org/design_notes/fn_type_trait_impls.html ## 2021-09-28 * https://rust-lang.github.io/async-fundamentals-initiative/evaluation.html * MVP (niko to open a PR describing MVP on the repo): * Straightforward desugaring * async fn in trait desugars to an anonymous associated type that implements Future * async fn in impl * No dyn * Can only use async fn in impl if you had one in the trait * Next steps: * [Something with dyn](https://rust-lang.github.io/async-fundamentals-initiative/evaluation/challenges/dyn_traits.html) * [Bounding futures](https://rust-lang.github.io/async-fundamentals-initiative/evaluation/challenges/bounding_futures.html) * Naming futures * associated type inference * typeof for fn types * "some other crazy stuff" Regarding the last bullet in "MvP": ```rust impl Iterator for Bar { fn next(&self) -> Option<u32> { // ^^^ "infers" that `type Item = u32` None } } trait AsyncThing { type Bar<'me>: Future<Output = ()> + 'me; fn do_it(&self) -> Self::Bar<'_>; } impl AsyncThing for Bar { fn do_it(&self) -> impl Future<Output = ()> { // ^^^^^^^^^^^^^^^^^^^^^^^ Self::Bar } } impl AsyncThing for Bar { async fn do_it(&self) { // infer that BAr = impl Future } } ``` ## 2021-09-23 Niko would like it if traits had to be declared dyn ```rust dyn trait Foo { } ``` Why? * Implementation soundness reasons * Transparency / semver * Don't accidentally make something not dyn safe * It's clearer to users that something is happening Plausible route: * You have to supply the dyn impl * `#[derive(dyn)]` to get the "default" * this would be an edition change, obviously ```rust // trait Foo { async fn bar(&self); } impl Foo for dyn Foo { type Bar = Box<dyn Future<Output = ()>>; fn bar(&self) -> Box<dyn Future<Output = ()>> { vtable(self)[0](self) } } // "the vtable" impl dyn Foo for T { fn bar(&self) { Box::new(T::bar(self)) // ^^^^^^ the actual impl you wrote } } impl Foo for T { fn bar(&self) { ... } } ``` ```rust dyn trait Foo { // Might want the Box thing here fn bar(&self) -> impl Iterator<Item = u32>; } dyn trait Foo { // dyn Foo<Bar = ...> type Bar: Debug; fn bar(&self) -> Self::Bar; } dyn trait Foo { // Might want the Box thing here fn bar(&self, input: impl Iterator<Item = u32>) -> impl Iterator<Item = u32>; } ``` Underlying mechanisms: * Trait methods that return `impl Trait`: * Only works for traits that `impl Trait for Box<dyn Trait>` * Vtable shim that boxes and creates `Box<dyn Trait>` * Controlling the kind of box (we have no idea how to do this yet, apart from `dyn(Box)`) Why not return unsized values? * Because futures have to have statically known size Why not determine it from vtable? ```rust let (tx, rx) = channel(); let f: impl Future<Output = u32> = async move { let fut: dyn Future<Output = u32> = rx.receive().await; fut.await }; // How big is `f`? ``` ```rust let (tx, rx) = channel(); let f: impl Future<Output = u32> = async move { let fut: dyn Future<Output = u32> = rx.receive().await.box; // ^^^ something that lets this work fut.await }; // How big is `f`? ``` ### Defining the underlying mechanisms ```rust struct Obj<trait T> { exists type E: T; data: Box<E>, vtable: vtable<E: T>, } impl<trait T> Obj<T> { fn new<E: T>(e: E) -> Obj<T> { let vtable = magic_get_vtable::<E>(&e); let data = Box::new(e); Obj { data, vtable } } fn *(*) from T { ... some code that extracts from vtable... somehow ... } } impl as<Obj<trait T>> for T { ... allocates a box ... } ``` ```rust let x = Obj::new(some_value); ``` ### What Niko wants * dyn is always 1 word * so dyn Trait is Sized * `x as dyn Trait` requires that `x` is a pointer (in practice) * `box struct Foo` means that `Foo` is actually `Box<Foo>` (covenience) * `dyn trait Foo` declare that a trait is "dyn-able" * `dyn Trait: Trait` works kind of universally * `fn foo(x: dyn Trait)` -- you could pass in a `&Foo` where `Foo: Trait` (which has size one word) * `fn foo(x: &dyn Trait)` is a pointer to pointer, not much we can do about that ## 2021-09-22 MVP ```rust trait Foo { async fn method(&self); } impl Foo for u32 { async fn method(&self); } // desugars to trait Foo { type _Foo /* unnameable */: Future<Output = ()>; fn method(&self) -> Self::_Foo; } impl Foo for u32 { type _Foo /* unnameable */ = impl Future<Output = ()>; fn method(&self) -> Self::_Foo { async move { ... } } } ``` * dyn?? ### What works ```rust async fn my_function<T: Foo>(x: T) { x.method().await; } #[async] fn main() { tokio::spawn(move async { my_function(22); }); } impl Foo for i32 { async fn method(&self) { tokio::println!("Hello, world {}", self).await } } ``` ### What doesn't ```rust fn my_function<T: Foo>(x: T) { tokio::spawn(move async { // cannot write this because `T::_Foo` is not known to be Send x.foo(); }); } ``` ```rust fn foo(x: &dyn Foo) { // ^^^^^^^ did not provide a value for `_Foo` } ``` ### Implementation work required * Extend the parser * Lower the thing in HIR lowering * Improve error messages in some cases * Make these traits not dyn-safe for now ### Prepare a test suite * Prepare a test suite https://rust-lang.github.io/async-fundamentals-initiative/ https://github.com/rust-lang/async-fundamentals-initiative https://rust-lang.github.io/async-fundamentals-initiative/evaluation/challenges/dyn_traits.html ## Dyn traits Normal "default impl" for dyn generated by the compiler ```rust trait Foo { type Bar; fn method(&self); } impl<B> Foo for dyn Foo<Bar = B> { type Bar = B; fn method(&self) { let f: fn(&Self) = get_method_from_vtable(self) // ^ literally the same function f(self) } } impl Foo for u32 { fn method(self: &u32) { XXX } } // compiles to: // // Vtable_Foo = [ ..., `<u32 as Foo>::method` ] // fn `<u32 as Foo>::method`(self: &u32) { XXX } ``` For async fn: ```rust trait Foo { async fn method(&self); } impl<B> Foo for dyn Foo { type Bar = Box<dyn Future<Output = ()>>; fn method(&self) -> Box<dyn Future<Output = ()>> { } } // compiles to: // // Vtable_Foo = [ ..., `<u32 as Foo>::methodX`] // fn `<u32 as Foo>::method`(self: &u32) { XXX } // fn `<u32 as Foo>::methodX`(self: &u32) -> Box<dyn> { Box::new(TheFuture) } ``` ### Auto traits and resulting futures Basically we just need multiple impls: ```rust trait Foo { async fn method(&self); } impl<B> Foo for dyn Foo { type Bar = Box<dyn Future<Output = ()>>; fn method(&self) -> Box<dyn Future<Output = ()>> { } } impl<B> Foo for dyn Foo * Send { type Bar = Box<dyn Future<Output = ()> + Send>; fn method(&self) -> Box<dyn Future<Output = ()>> { } } // compiles to: // // Vtable_Foo = [ ..., `<u32 as Foo>::methodX`] // fn `<u32 as Foo>::method`(self: &u32) { XXX } // fn `<u32 as Foo>::methodX`(self: &u32) -> Box<dyn> { Box::new(TheFuture) } ``` ### Avoiding hard-coding box "if you give me a box, I give you a box" but is this what we really want? Then dyn foo doesn't quite work the same ```rust trait Foo { async fn method(&self); } impl<B> Foo for Box<dyn Foo> { type Bar = Box<dyn Future<Output = ()>>; fn method(&self) -> Box<dyn Future<Output = ()>> { } } impl<B> Foo for dyn Foo * Send { type Bar = Box<dyn Future<Output = ()> + Send>; fn method(&self) -> Box<dyn Future<Output = ()>> { } } // compiles to: // // Vtable_Foo = [ ..., `<u32 as Foo>::methodX`] // fn `<u32 as Foo>::method`(self: &u32) { XXX } // fn `<u32 as Foo>::methodX`(self: &u32) -> Box<dyn> { Box::new(TheFuture) } ``` ```rust fn foo<T>(x: Box<T>) where T: ?Sized + Foo { ... } ``` "I tell you what I want, what I really really want" ```rust #[async_dyn_type(SmallBox<4, _>)] trait Foo { } ``` ```rust trait Foo { async fn method(&self); } impl<B> Foo for dyn(Box) Foo { type Bar = Box<dyn Future<Output = ()>>; fn method(&self) -> Box<dyn Future<Output = ()>> { } } ``` ### Embedded workloads * not depending on box * inline * smallbox * maybe you have some allocator object? ### Opening up dyn for Foo for anyone rust```