Lifetimes
Rust Ownership
- Every piece of memory in Rust program has exactly one owner at the time
- Ownership changes ("moves")
fn takes_ownership(data: Data)fn producer() -> Datalet people = [paul, john, emma];
Producing owned data
fn producer() -> String {
String::new()
}
Producing references?
fn producer() -> &str {
// ???
}
&str"looks" at some string data. Where can this data come from?
Local Data
Does this work?
fn producer() -> &str {
let s = String::new();
&s
}
Local Data
No, we can't return a reference to local data...
error[E0515]: cannot return reference to local variable `s`
--> src/lib.rs:3:5
|
3 | &s
| ^^ returns a reference to data owned by the current function
Static Data
static HELLO: &str = "hello";
fn producer() -> &'static str {
HELLO
}
- bytes
h e l l oare "baked" into your program - part of static memory (not heap or stack)
- a slice pointing to these bytes will always be valid
- safe to return from
producerfunction
Note:
You didn't need to specify 'static for the static variable - there's literally no other lifetime that can work here.
How big is a &'static str? Do you think the length lives with the string data, or inside the str-reference itself?
(It lives with the reference - so you can take sub-slices)
'static annotation
- Rust never assumes
'staticfor function returns or fields in types &'static Tmeans this reference toTwill never become invalidT: 'staticmeans that "if typeThas any references inside they should be'static"Tmay have no references inside at all!
- string literals are always
&'static str
fn takes_and_returns(s: &str) -> &str {
}
Where can the returned &str come from?
- can't be local data
- is not marked as
'static - Conclusion: must come from
s!
Multiple sources
fn takes_many_and_returns(s1: &str, s2: &str) -> &str {
}
Where can the returned &str come from?
- is not marked as
'static - should it be
s1ors2? - Ambiguous. Should ask programmer for help!
Tag system
fn takes_many_and_returns<'a>(s1: &str, s2: &'a str) -> &'a str {
}
"Returned &str comes from s2"
'a
- "Lifetime annotation"
- often called "lifetime" for short, but that's a very bad term
- every reference has a lifetime
- annotation doesn't name a lifetime of a reference, but used to tie lifetimes of several references together
- builds "can't outlive" and "should stay valid for as long as" relations
- arbitrary names:
'a,'b,'c,'whatever
Lifetime annotations in action
fn first_three_of_each(s1: &str, s2: &str) -> (&str, &str) {
(&s1[0..3], &s1[0..3])
}
fn main() {
let amsterdam = format!("AMS Amsterdam");
let (amsterdam_code, denver_code) = {
let denver = format!("DEN Denver");
first_three_of_each(&amsterdam, &denver)
};
println!("{} -> {}", amsterdam_code, denver_code);
}
Annotate!
fn first_three_of_each<'a, 'b>(s1: &'a str, s2: &'b str) -> (&'a str, &'b str) {
(&s1[0..3], &s1[0..3])
}
Annotations are used to validate function body
"The source you used in code doesn't match the tags"
error: lifetime may not live long enough
--> src/lib.rs:2:5
|
1 | fn first_three_of_each<'a, 'b>(s1: &'a str, s2: &'b str) -> (&'a str, &'b str) {
| -- -- lifetime `'b` defined here
| |
| lifetime `'a` defined here
2 | (&s1[0..3], &s1[0..3])
| ^^^^^^^^^^^^^^^^^^^^^^ function was supposed to return data with lifetime `'b` but it is returning data with lifetime `'a`
|
= help: consider adding the following bound: `'a: 'b`
Annotations are used to validate reference lifetimes at a call site
"Produced reference can't outlive the source"
error[E0597]: `denver` does not live long enough
--> src/main.rs:10:41
|
8 | let (amsterdam_code, denver_code) = {
| -------------- borrow later used here
9 | let denver = format!("DEN Denver");
| ------ binding `denver` declared here
10 | first_three_of_each(&amsterdam, &denver)
| ^^^^^^^ borrowed value does not live long enough
11 | };
| - `denver` dropped here while still borrowed
For more information about this error, try `rustc --explain E0597`.
Lifetime annotations help the compiler help you!
- You give Rust hints
- Rust checks memory access for correctness
fn first_three_of_each<'a, 'b>(s1: &'a str, s2: &'b str) -> (&'a str, &'b str) { (&s1[0..3], &s2[0..3]) } fn main() { let amsterdam = format!("AMS Amsterdam"); let denver = format!("DEN Denver"); let (amsterdam_code, denver_code) = { first_three_of_each(&amsterdam, &denver) }; println!("{} -> {}", amsterdam_code, denver_code); }
What if multiple parameters can be sources?
fn pick_one(s1: &'? str, s2: &'? str) -> &'? str {
if coin_flip() {
s1
} else {
s2
}
}
What if multiple parameters can be sources?
fn pick_one<'a>(s1: &'a str, s2: &'a str) -> &'a str {
if coin_flip() {
s1
} else {
s2
}
}
- returned reference can't outlive either
s1ors2 - potentially more restrictive
Note:
This function body does not force the two inputs to live for the same amount of time. Variables live for as long as they live and we can't change that here. This just says "I'm going to use the same label for the lifetimes these two references have, so pick whichever is the shorter".
What if multiple parameters can be sources?
fn pick_one<'a>(s1: &'a str, s2: &'a str) -> &'a str { if coin_flip() { s1 } else { s2 } } fn coin_flip() -> bool { false } fn main() { let a = String::from("a"); let b = "b"; let result = pick_one(&a, b); // drop(a); println!("{}", result); }
Lifetime annotations for types
struct Configuration {
database_url: &str,
}
Where does the string data come from?
Lifetime annotations are generic parameters
struct Configuration<'a> {
database_url: &'a str,
}
An instance of Configuration can't outlive a string
that it refers to via database_url.
or
The string can't be dropped
while an instance of Configuration still refers to it.
Lifetimes and Generics
- Lifetime annotations act like generics from type system PoV.
- Can be used to to add bounds to types:
where T: Debug + 'a- Type
Thas to be printable with:?. - If
Thas references inside, they have to stay valid for as long as'atag requires.
- Type
- Can be used to match lifetime generics in
structorenumwith the annotations used in function signatures and in turn with exact lifetimes of references.
Complex example
fn select_peer<'a>(peers: &[&'a str]) -> Option<Cow<'a, str>> {
for p in peers {
if is_up(p) {
return Some(Cow::Borrowed(p))
}
}
None
}
fn main() {}
Compiler concludes:
Returned value will not be allowed to outlive any reference in peers list
let selected = select_peer(&peers);
Lifetime annotations in practice
- Like generics, annotations make function signatures verbose and difficult to read
- they often can be glossed over when reading code
T: 'staticmeans "Owned data or static references", owned data can be very short-lived- Using owned data in your types helps avoid borrow checker difficulties