Try   HackMD

Async fundamentals ere long ago (2021)

2021-12-20

Problem with with clauses

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.

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
{ ... }
// This is exactly what we need.
fn deserialize_and_print_later(
    deserializer: &mut Deserializer,
    bytes: &[u8],
)
where with(Send + 'static) T: Deserialize + Debug
{ ... }
dyn Debug

// desugars to
dyn with(ContextFree) Debug + 'static

// or you could write..
dyn with(Bound) Debug + '_
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?

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

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

where we were

#[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 */
    }
}
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
    
}
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

fn foo(x: impl Len) {
    
    fn helper(x: &dyn Len) {
        ...
    }
    
    helper(&x)
}

you wrote

#[dynify]
fn foo(x: impl Len) {
    ...
}
fn foo(x: impl Len) {
    fn bar(x: dynx<'_> Len) {
        ...
    }
    bar(dynx!(x))
}
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(...);
#[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)
fn foo(x: Box<dyn AsyncIterator<next: Send> + Send>)

2021-12-13

RPITIT

Bounding function types: What about const bounds?

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)

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

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
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

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>
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();
    //     ^ ??
}
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)
    {}
}
fn foo<T: Foo>() where <T as Foo>::bar: Send {}

Dyn

  • dyn trait design outline
  • 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
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
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:

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
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:

fn foo(x: impl Debug) { ... }
foo(22)

and you change foo to use dyner, you start to get errors:

fn foo(x: dyner Debug) { ... }
foo(22) // ERROR

arguably that is the wrong code, and what you wanted is

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

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
dyn trait Foo {
    
}
dyn<Box> Foo
&dyn<Box> Foo
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

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:

foo(|| ...)
fn foo(x: impl FnMut<Item = u32>) {}

to make it dynamic today I have to add &mut both places

foo(&mut || ...)
fn foo(x: &mut dyn FnMut<Item = u32>) {}

this would be nicer..

foo(|| ...) // compiler autorefs
// foo(&mut || ...)
// relying on:
// &mut dyn FnMut<Item = u32>: dyn FnMut<Item = u32>
fn foo(x: dyn FnMut<Item = u32> + '_) {}
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

fn foo(x: impl FnMut<Item = u32>) {
    fn bar(x: &mut dyn FnMut<Item = u32>) { ... }
    bar(&mux x)
}
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))
}
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>

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?

trait Iterator {
    fn next(&mut self) -> Option<impl Sized>;
}
trait AsyncTrigger {
    fn start(&mut self) -> impl Future<Output = ()>;
}
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

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

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

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

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).

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

trait Iterable {
    final type Item<'me> = Self::iter<'me>::Item
    where
        Self: 'me;

    fn iter(&self) -> impl Iterator + '_;
    // implied bound: where Self: '_
}

Step 4

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
struct MustBeEq<T: Eq>(T);

type Foo<T> where T: Ord = MustBeEq<T>;
//          ^^^^^^^^^^^^ unenforced
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

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"
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 }
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

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>, { }

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
  • 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

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:
struct Foo<T: FnOnce()> { }

struct Foo<T: FnOnce(), const F: T> { }
impl<T: FnOnce(), const F: T> Foo<T, F> { 
    fn foo() {
        F();
    }
}

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"


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
    }
}



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,
    {
        
    }
}
fn foo(x: &DynTrait<'_>) {
    
}

fn bar() {
    let y = ...;
    let x = DynTrait::from_shared(&y);
    foo(&x)
}

2021-11-15

  • Niko wrote some stuff
    • Bridging poll traits with async traits
      • Hard with multiple poll functions
        • Something something resume args ((with?))
      • Easier with single poll: inline async fn

2021-11-11

  • WIP updates
    • dyn conversation
    • 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
          Image Not Showing Possible Reasons
          • The image file may be corrupted
          • The server hosting the image is unavailable
          • The image path is incorrect
          • The image format is not supported
          Learn More →
          Image Not Showing Possible Reasons
          • The image file may be corrupted
          • The server hosting the image is unavailable
          • The image path is incorrect
          • The image format is not supported
          Learn More →
          Image Not Showing Possible Reasons
          • The image file may be corrupted
          • The server hosting the image is unavailable
          • The image path is incorrect
          • The image format is not supported
          Learn More →
          "
      • 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

  • WIP updates
    • RFC
    • Stakeholders
    • Implementation
  • 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?

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

  • WIP updates
    • RFC
    • Implementation
  • Niko's dyn idea
  • Syntax for named fn types

RFC conversation

cramertj comment

Will we be able to do this in the future?

Can libs assume it will be possible in the future? Backcompat hazard

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

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

trait Foo {
    fn foo() -> impl Send;
}

2021-11-01

  • New sprint goals review
  • Stakeholder agenda
  • Niko's dyn idea
    • and another idea for conditional bounds
  • Syntax for named fn types?

Sprint goals review

  • Get MVP RFC to FCP
  • 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

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
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:

fn vtable_foo(x: dynx Trait) {
    ...
}

and then in the impl MyTrait for dynx MyTrait impl use this function:

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:

fn vtable_foo(x: &dyn Trait) {
    ...
}

and this in the impl MyTrait for dyn MyTrait:


// shim user calls:
fn foo(x: impl Trait) {
    vtable_foo(&x)
}

Step back and just talk about dyn and erased serde

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:

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

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

Late-bound and arguments

trait Foo {
    async fn bar(&self, x: impl Debug);
}
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,
trait Foo {
    async fn bar(&self);
}
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

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

// 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:

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:

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
}
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
    • 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
      • 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

fn bar() {
    debuglog(&22)
}

fn debuglog(x: &dyn Debug) {

}
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

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

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
impl trait in traits

Late-bound lifetimes

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
}
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

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

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:
  • 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
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

// 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

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

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
impl<A: Allocator> Foo for dyn Foo
with(a: A) {
    ...
}


with(alloc: impl Allocator);

https://github.com/rust-lang/rfcs/pull/2492

impl<'a> Foo<'a> for String { .. }

impl Foo for String {
    fn method<'a>()
}
impl Context {
    fn stuff(&self) -> &Output;
}
// 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)
    {
    
    }

}
with(value: Type) {
    ... can use value ...
    foo();
}
with(cx: &mut Context)
with(cx2: &mut Context2)
with(cx: .., cx2: ...) {
    
}

2021-10-04

Niko muses about dyn

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
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
  • 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

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
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

// 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

Regarding the last bullet in "MvP":

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

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
// 
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) {
        ...
    }
}
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?

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`?
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

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 ... }
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

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

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

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();
    });
}
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

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:

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:

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

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)  }
fn foo<T>(x: Box<T>) where T: ?Sized + Foo { ... }

"I tell you what I want, what I really really want"

#[async_dyn_type(SmallBox<4, _>)]
trait Foo {

}
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```