I agree this is basically an ABI problem. Stripping away the async issue this snippet highlights the issue:
trait T {
fn f(&self) -> impl U;
}
trait U {
fn g(&self);
}
fn h(t: &dyn T) {
// What type is u? how big is it?
let u = t.f();
// How does this method resolve?
u.g();
}
If `U` is object safe, and `f()` returns something that is `Sized` then it's possible to define a non-heap allocated `dyn U` that is essentially a tuple `(<method table>, <anonymous>)` and this code would desugar to something like
fn h(t: &dyn T) {
let u: (<method table>, <anonymous>) = t.f();
u.0.g(&u.1);
}
But that kind of requires a special calling convention for `T::f` when invoked from a `dyn T` that also returns the size of its return type.
edit: Thinking about it some more, what could be done is that functions called from a `dyn Trait` object that are `impl Trait` have a special calling convention where they return everything on the stack. Then callsites take the top of the stack + offset of the method as the function arg and stack + sizeof the method table as their `self` argument and everything else is fine.
I'm not sure if LLVM supports this directly. I know some stack based language implementations and LISPs will do this to avoid writing register allocators so everything just gets dumped on the stack.
Also there's no way (afaik) in Rust to "spell" a non-heap allocated dyn Trait object. There's bare `dyn Trait` but that means something else and is preserved for backwards compatibility, iirc.