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
Local Data
You will also see:
error[E0106]: missing lifetime specifier
--> src/lib.rs:1:18
|
1 | fn producer() -> &str {
| ^ expected named lifetime parameter
|
Static Data
#![allow(unused)] fn main() { 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 Data
It doesn't have to be a string literal - any reference to a static is OK.
#![allow(unused)] fn main() { static HELLO: [u8; 5] = [0x68, 0x65, 0x6c, 0x6c, 0x6f]; fn producer() -> &'static str { std::str::from_utf8(&HELLO).unwrap() } }
'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]: `amsterdam` does not live long enough
--> src/main.rs:10:29
|
6 | let amsterdam = format!("AMS Amsterdam");
| --------- binding `amsterdam` declared here
...
10 | first_three_of_each(&amsterdam, &denver)
| --------------------^^^^^^^^^^----------
| | |
| | borrowed value does not live long enough
| argument requires that `amsterdam` is borrowed for `'static`
...
14 | }
| - `amsterdam` dropped here while still borrowed
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".
Example
fn coin_flip() -> bool { false } fn pick_one<'a>(s1: &'a str, s2: &'a str) -> &'a str { if coin_flip() { s1 } else { s2 } } 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?
Generic lifetime parameter
struct Configuration<'a> {
database_url: &'a str,
}
- An instance of
Configurationcan't outlive a string
that it refers to viadatabase_url. - The string can't be dropped
while an instance ofConfigurationstill 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