I think I understood how lifetimes work with function parameters/outputs and with structs, since those cases are explained in the book (and further in the nomicon), but I do not understand how lifetimes are used for type bounds. For example:
trait Min<Elmt> {
fn get_min(&self) -> Elmt;
}
impl<T, Elmt> Min<Elmt> for T
where
&T: IntoIterator<Item = Elmt>,
Elmt: Ord,
{
fn get_min(&self) -> Elmt {
let mut iter = self.into_iter();
match iter.next() {
None => panic!("Empty List"),
Some(x) => iter.fold(x, |acc, y| acc.min(y)),
}
}
}
Here I require the type T
to fulfill the following property: Any of its references must implement IntoIterator<Item = Ord>
. This code does not compile because Rust wants &T
to have a lifetime and I can not figure out why. The where
clause should only ensure that something is of a certain type, which is done at compile time. How do lifetimes/references come into this? Ensuring that &T
of an implementing type T
implements IntoIterator
has nothing to do with dangling pointers or memory leaks.
Someone from the Rust Discord helped me by making this working implementation:
trait Min<'a, Elmt> {
fn get_min(&'a self) -> Elmt;
}
impl<'a, T, Elmt> Min<'a, Elmt> for T
where
&'a T: IntoIterator<Item = Elmt> + 'a,
Elmt: Ord,
{
fn get_min(&'a self) -> Elmt {
let mut iter = self.into_iter();
match iter.next() {
None => panic!("Empty List"),
Some(x) => iter.fold(x, |acc, y| acc.min(y)),
}
}
}
They tried to explain it to me, but I could not understand, so I am looking for another explanation to find its way into my brain.
Let me go through it one by one:
&'a T: IntoIterator<Item = Elmt> + 'a
, we give the generic reference&T
a lifetime'a
. Why is this needed?Why do we have to restrict
&'a T
to the lifetime'a
? I mean isn't that kind of a tautology?Since we used a lifetime in the
where
clause, we have to add it to some signature. That is whereimpl<'a, T, Elmt>
comes from - fineWhy does the trait
Min
and theget_min()
function need a lifetime parameter now? Lifetime elision should give every input element a lifetime and the output does not have a lifetime anyway.
I think it might help if I knew what traits are/how they are implemented. Objects are basically a list of variable and method pointers. Do the methods from traits simply get appended to the method pointers of every implementing object? Are they separate objects somehow? Do they actually have a lifetime? Which lifetime? I am never assigning the "general Min
trait" by using let a = Trait::new()
syntax, so how can it have a lifetime?
Addressing the for<>
-implementation comments:
While the trait and implementation would compile with a for<'a>
lifetime bound on &T
, the result is not usable. Since &Vec
apparently does not fulfill the trait bounds then (cf. this playground). This was also brought up in the Discord discussion, and I also do not understand the for<>
syntax yet, but I thought that this would maybe be out of scope for this question.