Lifetimes
- Every value in Rust has a lifetime.
- The lifetime of a value starts when the value is initialized and ends when the value is freed.
- The lifetime of a value is also called the value’s scope.
fn main() {
let a: i32 = 5; // -------------+-- 'a
// |
{ // |
let b: i32 = 6; // =====+== 'b |
println!("value: {b}"); // || |
} // =====+ |
// |
println!("value: {a}"); // |
} // -------------+
Lifetimes (2)
- Every reference in Rust has a lifetime.
- The lifetime of a reference starts when the reference is initialized and ends when the reference is no longer used.
fn main() {
let value: i32 = 5;
let reference: &i32 = &value; // ----------------+-- 'r
// |
println!("reference: {reference}"); // ----------------+
}
Lifetimes (3)
- The lifetime of a reference cannot be greater than the lifetime of the value it refers to (referents must outlive their references).
fn main() {
let value: i32 = 5; // ------------------+-- 'v
let reference: &i32 = &value; // =========+== 'r |
// || |
println!("reference: {reference}"); // =========+ |
} // ------------------+
Lifetime Error
fn main() {
let reference: &i32;
{
let value: i32 = 5; // --------+-- 'v
reference = &value; // ========|=======+== 'r
} // --------+ ||
// ||
println!("reference: {reference}"); // ================+
}
Lifetimes (4)
fn main() {
let value_1: i32 = 5; // --------------------------+-- 'v1
let mut reference: &i32; // |
// |
{ // |
let value_2: i32 = 100; // -----------------+-- 'v2 |
reference = &value; // =====+== 'r(v2) | |
// || | |
println!("reference: {reference}"); // =====+ | |
} // -----------------+ |
// |
reference = &value_1; // ===========+== 'r(v1) |
// || |
println!("reference: {reference}"); // ===========+ |
} // --------------------------+
Lifetimes and Aliasing (1)
fn main() {
let mut x = 5; // ---------------------+-- 'x1
// |
let r = &x; // ======+== 'r1(x1) |
// || |
x = 100; // -----||--------------+-- 'x1*
// || |
println!("{} {}", x, *r); // ======+--------------+
}
Lifetimes and Aliasing (2)
fn main() {
let mut x = 5; // ---------------------+-- 'x1
// |
let r = &x; // ========= 'r1(x1) |
// |
x = 100; // ---------------------+-- 'x1*
// |
println!("{}", x); // ---------------------+
}
Lifetimes and Functions
Q: What is the lifetime of the reference returned?
fn longest(x: &str, y: &str) -> &str {
if x.len() > y.len() {
x
} else {
y
}
}
Lifetimes Annotations
We can explicitly annotate the lifetime of references in function parameters.
fn longest<'a>(x: &'a str, y: &'a str) -> &'a str {
if x.len() > y.len() {
x
} else {
y
}
}
This means that for some lifetime 'a
:
-
The references
x
andy
live at least as long as'a
-
The returned reference lives for at least as long as
'a
Lifetimes Annotations (2)
fn longest<'a>(x: &'a str, y: &'a str) -> &'a str {
if x.len() > y.len() {
x
} else {
y
}
}
fn main() {
let s1 = String::from("crab"); // ----------------------------+-- 's1
let s2 = String::from("penguin"); // --------------------+-- 's2 |
let result = longest(&s1, &s2); // ===+== 'r ('s1|'s2) | |
// || | |
println!("{result}"); // ===+ | |
} // --------------------+-------+
Lifetimes Annotations (3)
fn longest<'a>(x: &'a str, y: &'a str) -> &'a str {
if x.len() > y.len() {
x
} else {
y
}
}
fn main() {
let s1 = String::from("crab"); // ----------------------------+-- 's1
let result: &str; // |
{ // |
let s2 = String::from("penguin"); // --------------------+-- 's2 |
result = longest(&s1, &s2); // ===+== 'r ('s1|'s2) | |
} // --||----------------+ |
println!("{result}"); // ===+ |
} // ----------------------------+
Lifetimes Annotations in Struct Definitions
struct ArrayPart<'a> {
part: &'a [i32],
}
fn main() { // 'a
let array: [i32; 5] = [1, 2, 3, 4, 5]; // -----------------------------|
// |
let slice: &[i32] = &array[1, 2, 3]; // =================+== 's ('a) |
// || |
let a_part = ArrayPart { part: slice }; // ===+== 'ap ('a) || |
// || || |
println!("{:?}", slice); // ==||=============+ |
println!("{:?}", a_part.part); // ===+ |
} // -----------------------------+
Lifetime Elision
fn first_word(s: &str) -> &str {
let bytes = s.as_bytes();
for (i, &item) in bytes.iter().enumerate() {
if item == b' ' {
return &s[0..i];
}
}
&s[..]
}
is equivalent to…
fn first_word<'a>(s: &'a str) -> &'a str {
// ...
}
Lifetime Elision Rules
- Each function parameter that is a reference gets its own lifetime parameter.
fn func(x: &i32) {}
fn func2(x: &i32, y: &i32) {}
fn func<'a>(x: &'a i32) {}
fn func2<'a, 'b>(x: &'a i32, y: &'b i32) {}
- If there is exactly one input lifetime parameter, the lifetime is assigned to all output lifetime parameters.
fn func(x: &i32) -> (&i32, &i32) {}
fn func<'a>(x: &'a i32) -> (&'a i32, &'a i32) {}
Lifetime Elision Rules (2)
-
If there are multiple input lifetime parameters, but one of them is
&self
or&mut self
, the lifetime ofself
is assigned to all output lifetime parameters.
struct NumWrapper { num: i32 }
impl NumWrapper {
fn observe_other_return_self(&self, other: &i32) -> &i32 {
println!("Observing: {other}");
&self.num
}
}
is equivalent to
fn observe_other_return_self<'a, 'b>(&'a self, other: &'b i32) -> &'a i32 {
// ...
}
Lifetime Elision Rules (3)
-
If there are multiple input lifetime parameters, but one of them is
&self
or&mut self
, the lifetime ofself
is assigned to all output lifetime parameters.
struct NumWrapper { num: i32 }
impl NumWrapper {
fn observe_self_return_other(&self, other: &i32) -> &i32 {
println!("Observing: {}", self.num);
other
}
}
Lifetime Elision Rules (4)
-
If there are multiple input lifetime parameters, but one of them is
&self
or&mut self
, the lifetime ofself
is assigned to all output lifetime parameters.
struct NumWrapper { num: i32 }
impl NumWrapper {
fn observe_self_return_other<'o>(&self, other: &'o i32) -> &'o i32 {
println!("Observing: {}", self.num);
other
}
}