Lifetimes in Rust, Why and How
What is lifetime
Lifetimes tells how long references would be valid, ensuring memory safety without a garbage collector.
Does not compile
fn longest (x:& str, y:& str) -> & str{
if x.len() > y.len() {
x
} else {
y
}
}
fn main() {
let string1 = String::from("Hi");
{
let string2 = String::from("Hello");
let result = longest (string1.as_str(), string2.as_str());
println! ("{result}");
}
}
$ cargo build
3 | fn longest(x:& str, y:& str) -> & str{
| ----- ----- ^ expected named lifetime parameter
|
= help: this function's return type contains a borrowed value, but
the signature does not say whether it is borrowed from `x` or `y`
help: consider introducing a named lifetime parameter
Why compliation error? Compiler is unable to infer the lifetimes of parameters
Compiles (With Lifetime Annotation)
Lifetime? Prefix every parameter with ‘k. Apostrophe (‘k), denotes reference has generic lifetime.
fn longest <'k> (x:&'k str, y:&'k str) -> &'k str{
if x.len() > y.len() {
x
} else {
y
}
}
fn main() {
let string1 = String::from("Hi");
{
let string2 = String::from("Hello ");
let result = longest (string1.as_str(), string2.as_str());
println! ("{result}");
}
}
$ cargo run
Hello
How this worked? Rust can’t tell whether the reference being returned refers to x or y Borrow checker can’t determine lifetime of return value will relate to x or y. With changed function signature, We tell compiler, returned reference will be valid as long as both the parameters are valid.
Lifetime in struct
Why we need lifetime in struct?
if we want to store reference in struct, ie struct does not hold Owned types.
Will not compile
struct A {
x:&str
}
fn main() {
let m = String::from("test");
let o = A {
x : &m,
};
println!("{}", o.x);
}
$ cargo run
missing lifetime specifier
--> src/main.rs:6:7
6 | x:&str
| ^ expected named lifetime parameter
Compiles (With Lifetime)
struct A <'a> {
x : &'a str,
}
fn main() {
let m = String::from("test");
let o = A {
x : &m,
};
println!("{}", o.x);
}
$ cargo run
test
Lifetime Ellision
The patterns programmed into Rust complier which applies lifetime rules in desired situtation automatically. These are not rules for programmers to follow; they’re a set of particular cases that the compiler will consider, and if your code fits these cases, you don’t need to write the lifetimes explicitly. Input Lifetime: Lifetimes on function or method parameters Output lifetimes: lifetimes on return values
3 rules to figure out the lifetimes
Rule-1(applies to input lifetimes)
compiler assigns a lifetime parameter to each parameter that’s a reference. 1 parameter gets one lifetime parameter, 2 parameters gets 3 separate lifetime parameters & so on
fn foo (x: &i32); becomes
fn foo <'a>(x: &'a i32);
fn foo (x:&i32, y:&i32) becomes
fn foo <'a, 'b>(x: &'a i32, y: &'b i32)
Rule-2(applies to input lifetimes)
if there is exactly one input lifetime parameter, that lifetime is assigned to all output lifetime parameters.
fn foo <'a>(x: &'a i32) -> &'a i32
Rule-3
if there are multiple input lifetime parameters, but one of them is &self or &mut self, lifetime of self is assigned to all output lifetime parameters.