Elision rules are straightforward and well-defined. In a &self
method, elided lifetimes in the return type always refer to the lifetime of the &self
borrow. Thus
fn slice_index(&self, start: usize, end: usize) -> Self::Slice<'_>
desugars to
fn slice_index<'short>(&'short self, start: usize, end: usize) -> Self::Slice<'short>
Click for more context/explanation ;-)
In fact, the whole impl
block also has another elided lifetime in the Self
-type.
impl<T: Scalar> RowIndex<T> for Vec<&Vec<T>> {
type Slice<'a> = Vec<&'a [T]> where Self: 'a;
fn slice_index(&self, start: usize, end: usize) -> Self::Slice<'_> {
self.iter().map(|x| &x[start..end]).collect()
}
}
More explicitly but still elided:
impl<T: Scalar> RowIndex<T> for Vec<&'_ Vec<T>> {
type Slice<'a> = Vec<&'a [T]> where Self: 'a;
fn slice_index(&'_ self, start: usize, end: usize) -> Self::Slice<'_> {
self.iter().map(|x| &x[start..end]).collect()
}
}
we can discuss the whole desugaring. There are three elided lifetimes (the three '_
s), two of them in an βinputβ position and on in an βoutputβ position (the output one is the Self::Slice<'_>
. Both βinputβ lifetimes are introduced as a new generic argument
impl<'long, T: Scalar> RowIndex<T> for Vec<&'long Vec<T>> {
type Slice<'a> = Vec<&'a [T]> where Self: 'a;
fn slice_index<'short>(&'short self, start: usize, end: usize) -> Self::Slice<'_> {
self.iter().map(|x| &x[start..end]).collect()
}
}
and the output lifetime is chosen following the rule to take the lifetime of β&self
β (or &mut self
), or otherwise the only input lifetime on the same function, or otherwise error. The first case applies, so 'short
is chosen.
impl<'long, T: Scalar> RowIndex<T> for Vec<&'long Vec<T>> {
type Slice<'a> = Vec<&'a [T]> where Self: 'a;
fn slice_index<'short>(&'short self, start: usize, end: usize) -> Self::Slice<'short> {
self.iter().map(|x| &x[start..end]).collect()
}
}
finally, we can also desugar the &'short self
to self: &'short Self
and replace Self
by the actual Self
-type as well as Self::Slice<'short>
by its definition, to get
impl<'long, T: Scalar> RowIndex<T> for Vec<&'long Vec<T>> {
type Slice<'a> = Vec<&'a [T]> where Self: 'a;
fn slice_index<'short>(self: &'short Vec<&'long Vec<T>>, start: usize, end: usize) -> Vec<&'short [T]> {
self.iter().map(|x| &x[start..end]).collect()
}
}
which matches what @quinedot wrote above. The 'long: 'short
bound that @quinedot also wrote is an implicit bound that always exists when a type of the form &'short SomeTypeInvolving<'long>
is used, such as the type in the self
argument. The only remaining difference is that @quinedot's function above is a free-standing funciton. You can write a freestanding function equivalent to a method in an impl
block by transferring all the generic arguments from the impl
to the function.
No, elided lifetimes in function signatures do not let the compiler have any choice. Itβs merely a convenient notation to abbreviate an equivalent longer alternative with explicit lifetimes. The rules are defined in a way thatβs useful in many/most cases, without being overly complicated and without depending on the contents of the function body. The signature is supposed to stay a stable interface and its meaning is supposed to be clear (and unchanged) from the signature alone. (Rust does unfortunately also break this principle occasionally, but in general itβs a very useful thing.)
Also see: