Description
Coming from the "missing required bounds" error?
The "missing required bounds" error that was emitted is intended to ensure that current code is forwards-compatible with potential future changes. We'll provide a brief summary of the why the bounds are required, the potential future changes, and a workaround if the required bounds are too restrictive. We would appreciate any feedback for this issue.
Why are these bounds required
Let's start by imagining a trait:
trait Iterable {
type Item<'x>;
fn iter<'a>(&'a self) -> Self::Item<'a>;
}
As-is, nothing looks wrong about this; without the "missing required bounds" error, this would compile. However, let's try to add an impl:
impl<T> Iterable for T {
type Item<'a> = &'a T;
fn iter<'a>(&'a self) -> Self::Item<'a> { self }
}
This definition of Item<'a> = &'a T
is invalid, since we don't know that T
outlives 'a
. So, our first thought might be to modify the definition to add a where Self: 'a
clause. This is what we want. However, impls are not allowed to have more where clauses than the trait.
Luckily, we can detect in a trait when this type of problem might occur. To do this, we look at the trait methods that construct the GAT. There, we find the bounds that we know and require that those bounds be written on the GAT itself.
Here, on the Iterable
trait, we construct the GAT Self::Item<'a>
in the iter
method. There, we have an argument &'a self
, which allows us to know Self: 'a
. Therefore, we require the where Self: 'a
bound on the GAT.
Potential future changes
Following the above logic, if we know all the required bounds when the trait is written, why require them at all? In fact, allowed these bounds to be implied is a potential future change. However, to make this change in a backwards-compatible manner, we must require the bounds now that we want to eventually imply. At that time, the written bounds will be redundant and can be removed.
This breaks my code. Workaround?
First, if any code breaks from adding the required bounds, we really want feedback. Second, the workaround is to move the GAT into a super trait. Using the example above, our new code would look like:
trait IterableSuper {
type Item<'x>;
}
trait Iterable: IterableSuper {
fn iter<'a>(&'a self) -> Self::Item<'a>;
}
Previous discussion
What is this bug?
We are moving towards stabilizing GATs (tracking issue: #44265) but there is one major ergonomic hurdle that we should decide how to manage before we go forward. In particular, a great many GAT use cases require a surprising where clause to be well-typed; this typically has the form where Self: 'a
. It might be useful if we were to create some rules to add this rule by default. Once we stabilize, changing defaults will be more difficult, and could require an edition, therefore it's better to evaluate the rules now.
I have an opinion! What should I do?
To make this decision in an informed way, what we need most are real-world examples and experience reports. If you are experimenting with GATs, for example, how often do you use where Self: 'a
and how did you find out that it is necessary? Would the default proposals described below work for you? If not, can you describe the trait so we can understand why they would not work?
Of great use would be example usages that do NOT require where Self: 'a
. It'd be good to be able to evaluate the various defaulting schemes and see whether they would interfere with the trait. Knowing the trait and a rough sketch of the impls would be helpful.
Background: what where clause now?
Consider the typical "lending iterator" example. The idea here is to have an iterator that produces values that may have references into the iterator itself (as opposed to references into the collection being iterated over). In other words, given a next
method like fn next<'a>(&'a mut self)
, the returned items have to be able to reference 'a
. The typical Iterator
trait cannot express that, but GATs can:
trait LendingIterator {
type Item<'a>;
fn next<'b>(&'b mut self) -> Self::Item<'b>;
}
Unfortunately, this trait definition turns out to be not quite right in practice. Consider an example like this, an iterator that yields a reference to the same item over and over again (note that it owns the item it is referencing):
struct RefOnce<T> {
my_data: T
}
impl<T> LendingIterator for RefOnce<T> {
type Item<'a> where Self: 'a = &'a T;
fn next<'b>(&'b mut self) -> Self::Item<'b> {
&self.my_data
}
}
Here, the type type Item<'a> = &'a T
declaration is actually illegal. Why is that? The assumption when authoring the trait was that 'a
would always be the lifetime of the self
reference in the next
function, of course, but that is not in fact required. People can reference Item
with any lifetime they want. For example, what if somebody wrote the type <SomeType<T> as LendingIterator>::Item<'static>
? In this case, T: 'static
would have to be true, but T
may in fact contain borrowed references. This is why the compiler gives you a "T may not outlive 'a
" error (playground).
We can encode the constraint that "'a
is meant to be the lifetime of the self
reference" by adding a where Self: 'a
clause to the type Item
declaration. This is saying "you can only use a 'a
that could be a reference to Self
". If you make this change, you'll find that the code compiles (playground):
trait LendingIterator {
type Item<'a> where Self: 'a;
fn next<'b>(&'b mut self) -> Self::Item<'b>;
}
When would you NOT want the where clause Self: 'a
?
If the associated type cannot refer to data that comes from the Self
type, then the where Self: 'a
is unnecessary, and is in fact somewhat constraining. As an example, consider:
XXX finish this
What could we do about it?
There are a few options. Here is the list of ideas we've had so far.
- Status quo: require that people add the
where Self: 'a
bounds, and try to do better with diagnostics. - Simple, limited default: If a GAT has exactly one lifetime parameter
'a
, addwhere Self: 'a
to both traits and impls. Need some way to opt out. - More extensive defaults: e.g., for every lifetime parameter
'a
to a GAT, addwhere Self: 'a
, and maybewhere T: 'a
for type parameters too. Need some way to opt out. - Add a syntactic sugar for this common case, e.g.
type Foo<'self>
. This could be added later. - self-oriented defaults: Given some GAT
Foo<'a>
, if each useSelf::Foo<'b>
within the trait methods references a'b
that is the lifetime parameter forself
, then addwhere Self: 'b
. While kind of complex to explain, this captures the intuition that'a
is meant to be the "lifetime of the self reference" in practice. We probably still want a way to opt out (though maybe not; maybe that way is "don't use&'a self
notation"). - Even smarter defaults A: Look at the method signatures in the trait. If we find that each use of
Self::Item<'b>
is associated with a lifetime'b
whereSelf: 'b
is implied by the method arguments, then infer thatwhere Self: 'b
. This is a more extensive, general version of self-oriented defaults. We probably still want a way to opt out (though maybe not).
In general, once we stabilize GATs, we likely cannot add defaults, except via an edition -- although we may be able to get away with it in this instance if the defaults are smart enough.