(which is essentially what `#[async_trait]` desugars to)
I'm not sure how you would even support dynamic dispatch in the general case without implicitly boxing the return value, which would be deeply unpopular within the Rust community and ecosystem. If we're saying we're ok with implicit allocations for futures then the whole "zero cost abstraction" of async/await seems pointless.
Note that an "implicit allocation" doesn't need to be a heap allocation, it just needs to be dynamically sized. Stabilizing the allocator api (which seems tantalizingly close) and someway to pass a non-default allocator to the implicit box should cover zero cost sensitive folks.
Also note that "zero cost abstraction" means that "zero cost" over implementing it by hand there is no way to return an owned dyn sized type in current rust. Syntax that returns a box is inherently zero cost because there's no other way to spell it.
C++ `co_await` also forces an allocation for similar reasons and lets the optimizer get rid of them in the static case which predictably is a mixed bag in practice; at least in rust you can guarantee the static sized version doesn't allocate. That said I don't like forcing an implicit allocation it feels against Rust's ethos and the language will likely have to have better handling of dyn types writ large to get there.
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
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.