The problem with a different ABI is subtler than that; you either have the ABI of calling it directly differ from calling it through dyn Trait
(in which the dyn Trait
version has to codegen a copy of the method with a different ABI), or the method always uses the return-unsized ABI, in which the extern "Rust"
function ABI differs based on if this is a trait impl which returns a non concrete type (which has nothing to do with the actual signature of the function).
Both of these are possible solutions. One of these has to be the case, in fact, since the trait impl returns a type with runtime size, and the concrete impl returns a type with static size.
Of course, there's the extra wrinkle of whether LLVM supports function calls that change the stack pointer at all. It does support allocas with runtime size, but I'm fairly certain it doesn't (currently) support a function call ABI that returns a value in a new alloca to the calling stack frame.
I think more generally placement can solve the problem more properly. In short,
- Allow
-> impl Trait
in object safe traits,
- Put the size/align layout of the return object (which is static per trait impl) in the vtable,
- The caller is responsible for allocating the space per the vtable, and then
- Call the function with the newly allocated return slot.
This doesn't try to solve the general case of returning truly runtime sized values, and just handles dynamic dispatch to a statically sized return value.
BUT, this does raise another (more frustrating) problem: you can't do this (with a stack held return value) in async fn
. Why? Because the witness object (the impl Future
) itself needs to have a statically known size. While you can technically do funky things to get dynamically sized stack objects, the "stack" that an async fn
has access to that persists over .await
points is not actually stack, but instead memory slots in the impl Future
. We have "stackless (semi)coroutines", not "green threads" / "stackful coroutines".
So while you could call an async fn
in trait that returns its future on the stack, you couldn't .await
it, because that would involve putting that dynamically sized future in your own statically sized future, so you'd have to box it to await it anyway.
(And propagating unsizedness makes your size impossible to determine before you request the size at runtime, because nothing limits you from using multiple of these semi unsized returning functions which depend on each other's results, so there's no way to pre-allocate size for your future at all.)