Here is a working minimal example of a trait with a method which returns &Self
:
Rust Playground (rust-lang.org)
use std::path::{
Ancestors,
Path,
PathBuf,
};
fn main() {
let path = PathBuf::from("/example/path");
println!("{:#?}", path.ancestors());
}
pub trait Ancestry<'a> {
type AncestorsIterator: Iterator<Item = &'a Self> where Self: 'a;
fn ancestors(&'a self) -> Self::AncestorsIterator;
}
impl<'a> Ancestry<'a> for Path {
type AncestorsIterator = Ancestors<'a>;
fn ancestors(&'a self) -> Self::AncestorsIterator {
println!("trait method");
self.ancestors()
}
}
If I remove where Self: 'a
, I get the following error:
error[E0309]: the parameter type `Self` may not live long enough
--> src/main.rs:15:38
|
15 | type AncestorsIterator: Iterator<Item = &'a Self>;
| ^^^^^^^^^^^^^^^
|
= help: consider adding an explicit lifetime bound `Self: 'a`...
= note: ...so that the reference type `&'a Self` does not outlive the data it points at
Why is the explicit lifetime needed? I think I understand the basics of lifetimes for references, but this seems redundant to me given that I've already specified &'a Self
as the yielded reference type. Isn't that already a type and a lifetime?
I can't think of any use for this trait that IntoIterator
or Iterator
doesn't cover, but the reference error makes sense.
If you have a type S<'s>
and implement it like
impl<'a, 's> Ancestry<'a> for S<'s>
Then you need to assert that 's
outlives 'a
.
I think this would be a slightly better option Rust Playground
use std::path::{
Ancestors,
Path,
PathBuf,
};
fn main() {
let path = PathBuf::from("/example/path");
println!("{:#?}", Ancestry::ancestors(path.as_path()));
}
pub trait Ancestry<'a, 'b: 'a> {
type AncestorsIterator: Iterator<Item = &'a Self> where Self: 'b;
fn ancestors(&'a self) -> Self::AncestorsIterator;
}
impl<'a, 'b: 'a> Ancestry<'a, 'b> for Path {
type AncestorsIterator = Ancestors<'a>;
fn ancestors(&'a self) -> Self::AncestorsIterator {
println!("trait method");
self.ancestors()
}
}
I don't have the code handy right this second, but essentially, the trait has the following methods:
fn is_descendant_of(&self, other: &Self) -> bool;
fn is_ancestor_of(&self, other: &self) -> bool;
fn closest_common_ancestor(&self, other: &Self) -> &Self;
To be clear, those signatures are not complete. Self
needs PartialEq
and 'a
in a few places. I have it working in my project, but again, not handy at the moment. The original example was just the minimal reproduction case, which is why it looks like a pointless wrapper. But in my project, those 3 methods all have default implementations, so all a type needs to implement is ancestors()
to get that functionality, and in the case of Path
, it's trivial.
I'm not sure I understand the point you're making in your reply. If you're dealing with 2 separate lifetimes and no references, sure, but how does that map to my example? I already specified &'a Self
as the type in the iterator. Why do I then need to specify where Self: 'a
? What would it even mean to have a pointer which is in scope to a type which is not in scope? I guess it could be like a naked pointer in C, but I thought rust would be smart and aggressive enough to infer the lifetime of the type from the lifetime of the reference.
I think what you're suggesting is that the lifetime of the reference is not the same thing as the lifetime of the type. Could you elaborate more on that? I'm assuming 'b: 'a
just means 'b
needs to live at least as long as 'a
, which makes perfect sense. I'm just having trouble understanding why that can't be inferred from &'a Self
.
Is there an example of a type that wouldn't satisfy that? Maybe some temporary inner type or something? And again, even if that were the case, it seems redundant to have a separate type lifetime when the program clearly indicates how long the reference needs to live anyway.
Dunno if it helps, but this is pretty much the same as your Self: 'a
version:
pub trait Ancestry<'a>: 'a {
type AncestorsIterator: Iterator<Item = &'a Self>;
fn ancestors(&'a self) -> Self::AncestorsIterator;
}
I think the only difference from other things like types is that for traits, the lifetime is not necessarily contained within Self
. Look at Pattern
: the lifetime generic is actually part of a return type.
I think it's less clear than generic associated types, though. So you should probably use those instead. It better conveys what you're trying to do.
pub trait Ancestry {
type AncestorsIterator<'a>: Iterator<Item = &'a Self> where Self: 'a;
fn ancestors<'a>(&'a self) -> Self::AncestorsIterator<'a>;
}
Type-wise, there's nothing wrong with &'a T<'b>
with any 'a
and 'b
(imagine Self = T<'b>
). You are free to declare such a type. But in order to be useful, for example to return it from a function, you need 'b: 'a
, which is the same as T<'b>: 'a
, and therefore Self: 'a
.
Also remember that your trait definition does not care that you're only implementing it for Path
, a type with no lifetimes. You could do trait Ancestry<'a>: 'static
to enforce only implementing it for types with no (non-static) lifetimes, but that's unnecessarily restrictive.
The key method that makes your trait tricky is is_ancestor_of
, since it requires you to call ancestors
on other
. It would work more generally if it was its own function or trait. Then you wouldn't have to worry about the iterator type being Self
, only that it implements PartialEq<Other>
. Then you could get rid of all the references and lifetimes in your trait entirely, and implement it for references instead.
1 Like
Given the example I'd suggest a GAT.
pub trait Ancestry {
type AncestorsIterator<'a>: Iterator<Item = &'a Self> where Self: 'a;
fn ancestors(&self) -> Self::AncestorsIterator<'_>;
}
Speaking of which, check out the explanation in this issue. It's a similar situation to your OP question.
It has to be the responsibility of some code somewhere to guarantee that types are well-formed. Or perhaps phrased differently, the compiler has to know when to check well-formedness and when it can assume it; any gap in the checks may be unsound.
When you have something like &'a self
as a function argument, there's an implicit "types are well-formed" requirement that the caller must prove, from which follows an implicit Self: 'a
. So the callee can just assume Self: 'a
. (The compiler knows to check well-formedness at the call sites.)
https://rust-lang.github.io/rfcs/0192-bounds-on-object-and-generic-types.html#inference
The story around associated types is a bit more complicated. I don't have time to deep dive on the details at the moment myself, but here are a couple perhaps-pertinent details.
What your OP and the related function version below demonstrate is that mentioning a type in an associated type equality bound does not result in an implicit bound that can be relied on, and instead an explicit bound is required.
// error[E0309]: the parameter type `T` may not live long enough
fn g<'a, I: Iterator<Item = &'a T>, T>() {}
I'm not sure how feasible it would be to make the currently required bound implicit.
7 Likes
I think in this code example there is no need to remove Self
after the iterator AncestorsIterator
is no longer needed, or to keep this iterator for the entire lifetime of Self
, which would follow from their equal lifetime of 'a
'b: 'a
allows us to confirm the validity of the iterator and nothing more.
Well, that was certainly a deeper dive into the language than I anticipated, but the links you provided definitely clarified the situation. Thank you very much!
1 Like
I marked another reply as the solution based on some links provided, but I just wanted to point out that your point about moving methods out of the trait and having them operate on trait objects is a good one. I'll give it a try and see if it simplifies the code.
2 Likes