Lifetimes
Dangeling References
- A dangling reference is a pointer that references a location in memory that may have been given to someone else, or that may have been deallocated.
fn main() {
let r;
{
let x = 5;
r = &x;
}
println!("r: {}", r);
}Error: x does not live long enoug
- The variable
xis dropped when it goes out of scope, but the referencerstill points to the memory location ofx.
Lifetime Annotations
- Lifetime annotations are used to specify the relationship between the lifetimes of references.
fn main() {
let r; // ------------------+-- 'a
{ // |
let x = 5; // -+-- 'b |
r = &x; // | |
} // -+ |
// |
println!("r: {}", r); // ------------------+
}- The lifetime of
ris'a, and the lifetime ofxis'b. - The lifetime of
xis shorter than the lifetime ofr, which causes the error.
Borrow Checker
-
The Rust compiler has a borrow checker that compares scopes to determine whether all borrows are valid.
-
The borrow checker uses lifetimes to determine the validity of references.
fn main() {
let x = 5; // ----------+-- 'b
// |
let r = &x; // -+-- 'a |
// | |
println!("r: {}", r); // | |
} // -+---------+
- No error is thrown because the lifetime of
xis longer than the lifetime ofr.
Generic Lifetime Annotations
fn main() {
let string1 = String::from("abcd");
let string2 = String::from("xyz");
let result = longest(string1.as_str(), string2.as_str());
println!("The longest string is {}", result);
}
fn longest(x: &str, y: &str) -> &str {
if x.len() > y.len() {
x
} else {
y
}
}Error: missing lifetime specifier
-
The error is thrown because the borrow checker cannot determine the lifetimes of the references
xandy.-
Since,
xandyhave different lifetimes, the compiler cannot determine the lifetime of the return value. -
Cannot determine the exact lifetime of the return value.
-
Generic lifetime annotations are used to specify the relationship between the lifetimes of references.
&i32-> References&'a i32-> References with lifetime annotations&'a mut i32-> Mutable references with lifetime annotations
-
&'a strspecifies that the return value will have the same lifetime as the referencesxandy. -
conventionally, lowercase letters are used for lifetime annotations.
fn longest<'a>(x: &'a str, y: &'a str) -> &'a str {
if x.len() > y.len() {
x
} else {
y
}
}- if x is the longest string, the return value will have the same lifetime as
x, and if y is the longest string, the return value will have the same lifetime asy.
fn main() {
let string1 = String::from("abcd");
{
let string2 = String::from("xyz");
let result = longest(string1.as_str(), string2.as_str());
println!("The longest string is {}", result);
}
}
fn longest<'a>(x: &'a str, y: &'a str) -> &'a str {
if x.len() > y.len() {
x
} else {
y
}
}$ cargo run
The longest string is abcd- the borrow checker checks if the smallest lifetime is valid for all references.
fn main() {
let string1 = String::from("abcd");
let result;
{
let string2 = String::from("xyz");
result = longest(string1.as_str(), string2.as_str());
}
print!("The longest string is {}", result);
}
fn longest<'a>(x: &'a str, y: &'a str) -> &'a str {
if x.len() > y.len() {
x
} else {
y
}
}Error: borrowed value does not live long enough
- since the lifetime of
string2is shorter than the lifetime ofresult, which is a dangling reference.
fn main() {
let string1 = String::from("abcd");
let result;
{
let string2 = String::from("xyz");
result = longest(string1.as_str(), string2.as_str());
}
print!("The longest string is {}", result);
}
fn longest<'a>(x: &'a str, y: &str) -> &'a str {
x
}- since
yis not being returned, if lifetime ofyis not specified, no error is thrown.
Lifetime in Structs
struct ImportantExcerpt<'a> {
part: &'a str,
}
fn main() {
let novel = String::from("Call me Ishmael. Some years ago...");
let first_sentence = novel.split('.').next().expect("Could not find sentence.");
let i = ImportantExcerpt {
part: first_sentence,
};
}- The lifetime annotation
'ais used to specify that the lifetime ofpartis the same as the lifetime of the referencefirst_sentence.
Lifetime Elision
-
Each Parameters that is a reference gets its own lifetime parameter.
-
If there is exactly one input lifetime parameter, that lifetime is assigned to all output lifetime parameters.
-
If there are multiple input lifetime parameters, but one of them is
&selfor&mut self, the lifetime ofselfis assigned to all output lifetime parameters.
fn first_word(s: &str) -> &str {
let bytes = s.as_bytes();
for (i, &item) in bytes.iter().enumerate() {
if item == b' ' {
return &s[..i];
}
}
&s[..]
}- No errors are thrown because the lifetime of the return value is the same as the lifetime of the reference
s.
Lifetime Annotations in Methods
struct ImportantExcerpt<'a> {
part: &'a str,
}
impl<'a> ImportantExcerpt<'a> {
fn level(&self) -> i32 {
3
}
}
fn main() {
let novel = String::from("Call me Ishmael. Some years ago...");
let first_sentence = novel.split('.').next().expect("Could not find sentence.");
let i = ImportantExcerpt {
part: first_sentence,
};
println!("ImportantExcerpt level: {}", i.level());
}- The lifetime annotation
'ais used to specify that the lifetime ofpartis the same as the lifetime of the referencefirst_sentence.
Static Lifetime
'staticis a special lifetime that lasts for the entire duration of the program.
fn main () {
let s: &'static str = "I have a static lifetime.";
}Putting it all Together
- Generics, Traits, and Lifetimes can be combined to create complex functions and data structures.
use std::fmt::Display;
fn longest_with_an_annoucement<'a, T>(x: &'a str, y: &'a str, ann: T) -> &'a str
where
T: Display,
{
println!("Announcement! {}", ann);
if x.len() > y.len() {
x
} else {
y
}
}
fn main() {}