Lifetimes in Methods
When implementing methods on a struct or enum that declares lifetime parameters (impl<'a> Type<'a>), the lifetime parameters must be declared after impl and passed to the type name. Inside method signatures, the compiler's third lifetime elision rule often makes explicit lifetime annotations on parameters and return values unnecessary.
Declaring impl<'a> Struct<'a>
The lifetime 'a is part of the struct's full type signature. Therefore, an impl block must introduce 'a in impl<'a> so it is in scope for the target type Excerpt<'a>:
struct Excerpt<'a> {
part: &'a str,
}
impl<'a> Excerpt<'a> {
// Associated function (constructor): ties input `'a` to `Excerpt<'a>`
fn new(part: &'a str) -> Self {
Self { part }
}
// Method returning a value that does not borrow anything
fn level(&self) -> i32 {
3
}
}
fn main() {
let text = String::from("Rust memory safety without garbage collection.");
let excerpt = Excerpt::new(&text[..18]);
println!("Excerpt '{}' has level {}", excerpt.part, excerpt.level());
}
Excerpt 'Rust memory safety' has level 3
Method Lifetime Elision (&self Rule)
The three lifetime elision rules apply to method signatures:
- Each reference parameter (
&self,&str, etc.) gets its own distinct lifetime parameter. - If there is exactly one input lifetime parameter, that lifetime is assigned to all output reference parameters.
- Method rule: If one of the input parameters is
&selfor&mut self, the lifetime ofselfis assigned to all elided output reference lifetimes.
struct Excerpt<'a> {
part: &'a str,
}
impl<'a> Excerpt<'a> {
// Rule 1 assigns `'s` to `&self` and `'b` to `announcement`.
// Rule 3 assigns `'s` (the lifetime of `&self`) to the return type `&str`.
fn announce_and_return_part(&self, announcement: &str) -> &str {
println!("Attention please: {announcement}");
self.part
}
}
fn main() {
let novel = String::from("Call me Ishmael.");
let excerpt = Excerpt { part: &novel[..7] };
let part = excerpt.announce_and_return_part("New chapter starting");
println!("Returned part: {part}");
}
Attention please: New chapter starting
Returned part: Call me
Returning 'a vs. Returning the Lifetime of &self
Notice a subtle distinction on a view struct Excerpt<'a>:
- Returning
&strwith elision (fn get(&self) -> &str) ties the returned slice to the borrow of&self(&'s self -> &'s str). - Returning
&'a strexplicitly (fn as_str(&self) -> &'a str) ties the returned slice to the underlying buffer'a, allowing the returned&'a strto live even after theExcerpt<'a>struct itself is dropped!
struct Excerpt<'a> {
part: &'a str,
}
impl<'a> Excerpt<'a> {
// Explicitly returns `'a` (the underlying string slice's lifetime),
// not the shorter borrow of `&self`:
fn into_underlying_str(&self) -> &'a str {
self.part
}
}
fn main() {
let backing = String::from("Long-lived backing text");
let slice_from_method: &str;
{
let temp_excerpt = Excerpt { part: &backing[..10] };
slice_from_method = temp_excerpt.into_underlying_str();
} // `temp_excerpt` is dropped here, but `slice_from_method` borrows `backing` ('a)!
println!("Still valid after struct drop: '{slice_from_method}'");
}
Still valid after struct drop: 'Long-lived'
Using the Anonymous Lifetime '_ in impl Blocks
When a method does not need to name the struct's lifetime parameter 'a inside its body or return type, Rust allows eliding it in the impl header using the anonymous lifetime '_:
struct Excerpt<'a> {
part: &'a str,
}
impl Excerpt<'_> {
fn word_count(&self) -> usize {
self.part.split_whitespace().count()
}
}
fn main() {
let e = Excerpt { part: "one two three" };
println!("Words: {}", e.word_count());
}
Words: 3