First, the questions around backwards compatibility, which I believe is still preserved:
This is not actually an issue for this proposal. impl Trait is, itself, something like â T. T: Trait => T. So at the type-checking level, the types of returns_impl_trait(1) and returns_impl_trait(2) unify.
Each time returns_impl_trait returns, you have the potential to enter a differently-monomorphized piece of the caller, which knows the new concrete type. (And the stack frame can stay the same because x already has to reserve enough space for any of returns_impl_trait's concrete return types.)
@ExpHPâs example is harder, but not impossible either. The idea of forbidding it in the 2018 epoch is appealing in some ways, as Vec<impl Debug> is not a type we support today. But the full interpretation can be made to work, of impl Trait as a full existential type with its witness âfolded into the instruction pointer.â
The key is that, while Vec inherently and intentionally âstrips awayâ the control flow around the values it contains, we can instead convert the existentialâs witness from its encoding as the instruction pointer to a value that goes in the Vec.
Then, as part of println!, std::slice::Iter<impl Debug>'s Iterator::next method can use the same mechanism as any other function that returns impl Trait- the caller (in this case <[impl Debug] as Debug>::fmt, or rather its indirect callees in the display plumbing) monomorphizes its continuation for each of the concrete types that the original foo call returns, and the callee returns to the appropriate one based on the value stored in the Vec.
And finally, @scalexmâs exampleâthis is also doable, though itâs a more interesting question of whether it should be allowed. At one level, this interpretation means one impl Debug is the same type as any other impl Debug, satisfying the constraint that x and y have the same type.
Without any further constraints on T, it doesnât actually matter- we could monomorphize bar to give x and y the same size regardless of what g returns. And if you add T: Display or whatever trait g returns, bar could be monomorphized as if it were bar<T, U>(x: T, y: U) while again giving x and y the same size. But at another, is that actually what bar meant? Does it break anything important?
And the questions about functionality and syntax:
From what I understand, there arenât any plans to allow putting impl Trait in structs anyway. Thatâs to be provided by abstract type instead, which also provides a name. What this does disallow is things like this, which try to retroactively name an existing impl Trait type:
abstract type T: Trait;
fn f() -> T { g() }
fn g() -> impl Trait { .. }
This is exactly the same scale as normal generic functions- âroughly exponentialâ in the number of type parameters, i.e. the number of eliminated dyn Traits. And as @earthengine points out, combinator-based futures code would run different logic in each monomorphized continuation, leading to approximately the same number of âstatesâ as an equivalent async fn.
Itâs certainly worth considering, but without benchmarking things I donât believe we can really say that the outcome is any worse than pervasive use of type parameters. We still need good tools like cargo bloat regardless.
These are intertwinedâthe answer to the first depends on (among other things) the answer to the second. One of the issues with enum impl Trait, which makes people want yet-another-syntax for it instead of just reusing impl Trait, is that itâs basically a new dynamic dispatch mechanism. A major draw of this proposal, on the other hand, is that it works the same as argument-position impl Trait, without a new dynamic dispatch mechanism.
So while as it stands I believe this is backwards compatible, we canât really separate those two questions without at least committing to forward compatibility with it until (1) is decided. And as a general response to the enum impl Trait bikeshedding going on here, I would say any new syntax in the type essentially defeats the proposalâs purpose (of making APIT and RPIT equivalent).
I do think a marker in the callee function body, like @scottmcm suggests, might make sense. It would be a helpful call-out that the following code is using âstatic existentials,â and isnât just a type error.