At the October T-types meetup, as part of the proposal for Mini-TAIT, the types team proposed a "may define implies must define" restriction.
The purpose and effect of this restriction is two-fold.
One, and principally, it was designed to avoid changes in inference when moving from the old solver to the new solver.
Secondly, the restriction avoids producing cycle errors on the old solver.
At the meetup, it seemed to be assumed that we could likely later lift this restriction after the new trait solver lands.
We need to work out how likely or not that is, as it may affect various design decisions for TAIT.
One thing that was maybe not understood at the time of the T-types meetup was that moving to the new solver will involve breaking changes to type inference with stable RPIT. Consider:
To work through and think through the hard cases, I've assembled a draft test suite:
https://github.com/rust-lang/rust/pull/118717
(I need to rebase it and re-bless the tests, as well as add some comments…)
It contains a few dozens tests like the one above exercising both the old trait solver and the new one and comparing them to what is proposed as the correct behavior.
id2
patternConsider this pattern:
In both the new and the old trait solver, whether the opaque type is the left or the right argument to id2
matters in terms of whether the opaque type gets returned. But due to lazy normalization, in the new trait solver, it doesn't matter. Under the new trait solver, the concrete type will be witnessed (though that is not yet implemented).
Consider:
This is an example of where spurious cycle errors happen in the old solver (even with RPIT) but where the new solver can work things out.
The "may define implies must define" rule means that when, during type inference, we see the opaque type, we know immediately whether we can use that type concretely, because:
To lift the restriction, we need to consider:
The question is, can we craft a set of rules for type inference that allow this to work reasonably in the context of a new more complete trait solver with lazy normalization?
One perhaps(?) possible way we could do that (or save space to do that) is with a "must define before use" rule.
https://github.com/rust-lang/rust/issues/117866
If the body of an item that may define the hidden type of some opaque does define that hidden type, it must do so syntactically before using the opaque type in a non-defining way.
The idea is that we'd enforce that defining uses, if they exist, must precede non-defining uses.
TC: Do we need this to save space, e.g. for doing passthrough later with ATPIT?
CE: I'm not worried about it. I think we can do it anyway.
Consensus: We won't do this.
Niko:
Niko:
TC: impl Trait Everywhere:
Niko:
Niko:
Niko:
Niko:
We believe that…
impl Trait
syntax (outside of type aliases) is for convenience and should have minimal overhead to use.impl Trait
should be very crisp: if it a defining use, it should act exactly like an inference variable (existential quantification). If it NOT a defining use, it should be like a generic type (universal quantification).impl Trait
is referenced by "name" (i.e., you can't expect to copy/paste impl Trait
, you have to give it a name).Consensus: Let's stabilize ATPIT essentially as it is implemented today. Specifically:
impl Trait
in the value of an associated type.Self::AssocType
.impl Trait
you encounter in this process may and must be defined.impl Trait
opaque type doesn't otherwise appear in the signature.We'll keep the other rules from the Mini-TAIT proposal, in particular:
https://github.com/rust-lang/rust/issues/117861
At least until the new trait solver is stabilized, any item that is allowed to define the hidden type of some opaque type must define the hidden type of that opaque type.
https://github.com/rust-lang/rust/issues/117865
The compiler is allowed to rely on whether or not an item is allowed to define the hidden type of an opaque type to guide inference.