(translator's note: lifetimes is one of the most confusing things in Rust, which often causes difficulty for beginners, even despite the official documentation . There are explanations on individual aspects of lifetimes, but they are all scattered across different sources and answers on Stack Overflow. The author of this article has collected and clarified a lot of life-related issues in one place, which makes this article so valuable (and I learned new things from here myself). does not speak English enough to read the original fluently, and also to increase the popularity of this article among the Russian-speaking Rust community)
May 19, 2020 37 minutes #rust # lifetimes
Table of contents
- Introduction
- Delusions
1) T
contains only owning types2) T: 'static
, then itT
should live for the entire duration of the program3) &'a T
andT: 'a
- they are the same- 4) my code is not generic and has no lifetimes
- 5) if it compiles, then my lifetime annotations are correct
- 6) trait objects behind the owning pointers do not have lifetimes
- 7) compilation error messages will tell me how to fix my program
- 8) lifetimes can go up and down at runtime
- 9) weakening mut links to shared links safely
- 10) closures follow the same rules for implicitly inferring lifetime elision as functions
11) 'static
links can always lead to'a
- Conclusion
- Discussion
- Contacts
- Further reading
Introduction
- , , . , .
T |
1)
2) |
, , , i32 , String , Vec . . |
|
1)
2) |
, , &i32 , &mut i32 . . |
1) mut-
2) |
, .. &mut T |
1) immut-
2) |
, .. &T |
: โ , , , . ~6500 , .
1) T
, , Rust, , . :
Rust, , i32
, &i32
&mut i32
โ . , T
, . , , , . , Rust :
T |
&T |
&mut T |
|
i32 |
&i32 |
&mut i32 |
T
. &T
. &mut T
. T
, &T
&mut T
โ . , , , , . Rust -:
T |
&T |
&mut T |
|
i32 , &i32 , &mut i32 , &&i32 , &mut &mut i32 , ... |
&i32 , &&i32 , &&mut i32 , ... |
&mut i32 , &mut &mut i32 , &mut &i32 , ... |
T
, &T
&mut T
โ , . T
&T
&mut T
, &T
&mut T
โ . , :
trait Trait {}
impl<T> Trait for T {}
impl<T> Trait for &T {} //
impl<T> Trait for &mut T {} //
, :
error[E0119]: conflicting implementations of trait `Trait` for type `&_`:
--> src/lib.rs:5:1
|
3 | impl<T> Trait for T {}
| ------------------- first implementation here
4 |
5 | impl<T> Trait for &T {}
| ^^^^^^^^^^^^^^^^^^^^ conflicting implementation for `&_`
error[E0119]: conflicting implementations of trait `Trait` for type `&mut _`:
--> src/lib.rs:7:1
|
3 | impl<T> Trait for T {}
| ------------------- first implementation here
...
7 | impl<T> Trait for &mut T {}
| ^^^^^^^^^^^^^^^^^^^^^^^^ conflicting implementation for `&mut _`
Trait
&T
&mut T
, Trait
T
, &T
&mut T
. , , &T
&mut T
:
trait Trait {}
impl<T> Trait for &T {} //
impl<T> Trait for &mut T {} //
T
&T
,&mut T
&T
&mut T
2) T: 'static
, T
T: 'static
ยซT
'static
ยป&'static T
T: 'static
โ-
T: 'static
,T
-
T: 'static
,T
Rust 'static
, , :
fn main() {
let str_literal: &'static str = "str literal";
}
, "str literal"
, , 'static
. static
static
.
static BYTES: [u8; 3] = [1, 2, 3];
static mut MUT_BYTES: [u8; 3] = [1, 2, 3];
fn main() {
MUT_BYTES[0] = 99; // ,
unsafe {
MUT_BYTES[0] = 99;
assert_eq!(99, MUT_BYTES[0]);
}
}
static
:
- ,
'static
, , - static
, ? , 'static
, ?
, , , 'static
, , 'static
. , , .
&'static T
T: 'static
.
&'static T
โ T
, , . , T
. T
. 'static
, :
use rand;
// 'static str
fn rand_str_generator() -> &'static str {
let rand_string = rand::random::<u64>().to_string();
Box::leak(rand_string.into_boxed_str())
}
T: 'static
โ T
, , . T: 'static
&'static T
, , String
, Vec
. . , , , , , . T: 'static
, ยซT
'static
ยป, ยซT
'static
ยป. :
use rand;
fn drop_static<T: 'static>(t: T) {
std::mem::drop(t);
}
fn main() {
let mut strings: Vec<String> = Vec::new();
for _ in 0..10 {
if rand::random() {
//
//
let string = rand::random::<u64>().to_string();
strings.push(string);
}
}
// ,
// 'static
for mut string in strings {
//
string.push_str("a mutation");
//
drop_static(string); //
}
//
println!("i am the end of the program");
}
T: 'static
, ยซT
'static
ยป-
T: 'static
,T
'static
. -
T: 'static
, ,T
3) &'a T
T: 'a
โ
.
&'a T
T: 'a
, T
, 'a
, 'a
, T
'a
. , Rust &'static Ref<'a, T>
Ref
'a
, 'static
.
T: 'a
&'a T
, .
// , 'a
fn t_ref<'a, T: 'a>(t: &'a T) {}
// , 'a
fn t_bound<'a, T: 'a>(t: T) {}
// ,
struct Ref<'a, T: 'a>(&'a T);
fn main() {
let string = String::from("string");
t_bound(&string); //
t_bound(Ref(&string)); //
t_bound(&Ref(&string)); //
t_ref(&string); //
t_ref(Ref(&string)); // , ,
t_ref(&Ref(&string)); //
// 'static, , , 'a
t_bound(string); //
}
T: 'a
,&'a T
T: 'a
, , ,&'a T
-
T: 'static
,T: 'a
,'static
>='a
'a
4)
- (lifetime elision), , Rust :
- -
- , (. : , , )
- , โ
&self
&mut self
,self
, :
//
fn print(s: &str);
//
fn print<'a>(s: &'a str);
//
fn trim(s: &str) -> &str;
//
fn trim<'a>(s: &'a str) -> &'a str;
// , ,
// . .
fn get_str() -> &str;
//
fn get_str<'a>() -> &'a str; //
fn get_str() -> &'static str; // 'static
// , ,
// . .
fn overlap(s: &str, t: &str) -> &str;
//
// ( )
fn overlap<'a>(s: &'a str, t: &str) -> &'a str; // s
fn overlap<'a>(s: &str, t: &'a str) -> &'a str; // t
fn overlap<'a>(s: &'a str, t: &'a str) -> &'a str; // s t
fn overlap(s: &str, t: &str) -> &'static str; // s t
fn overlap<'a>(s: &str, t: &str) -> &'a str; //
//
fn overlap<'a, 'b>(s: &'a str, t: &'b str) -> &'a str;
fn overlap<'a, 'b>(s: &'a str, t: &'b str) -> &'b str;
fn overlap<'a>(s: &'a str, t: &'a str) -> &'a str;
fn overlap<'a, 'b>(s: &'a str, t: &'b str) -> &'static str;
fn overlap<'a, 'b, 'c>(s: &'a str, t: &'b str) -> &'c str;
//
fn compare(&self, s: &str) -> &str;
//
fn compare<'a, 'b>(&'a self, &'b str) -> &'a str;
- :
- ,
- ,
- - ( )
- ( )
.
- Rust ,
5) ,
- (borrow checker) Rust , ,
- Rust
Rust , . :
struct ByteIter<'a> {
remainder: &'a [u8]
}
impl<'a> ByteIter<'a> {
fn next(&mut self) -> Option<&u8> {
if self.remainder.is_empty() {
None
} else {
let byte = &self.remainder[0];
self.remainder = &self.remainder[1..];
Some(byte)
}
}
}
fn main() {
let mut bytes = ByteIter { remainder: b"1" };
assert_eq!(Some(&b'1'), bytes.next());
assert_eq!(None, bytes.next());
}
ByteIter
โ , . Iterator
. , , , ?
fn main() {
let mut bytes = ByteIter { remainder: b"1123" };
let byte_1 = bytes.next();
let byte_2 = bytes.next();
if byte_1 == byte_2 {
// -
}
}
! :
error[E0499]: cannot borrow `bytes` as mutable more than once at a time
--> src/main.rs:20:18
|
19 | let byte_1 = bytes.next();
| ----- first mutable borrow occurs here
20 | let byte_2 = bytes.next();
| ^^^^^ second mutable borrow occurs here
21 | if byte_1 == byte_2 {
| ------ first borrow later used here
, . โ , , ByteIter
, &'a [T]
, , /. , , , , , , ?
, ! , . , :
struct ByteIter<'a> {
remainder: &'a [u8]
}
impl<'a> ByteIter<'a> {
fn next<'b>(&'b mut self) -> Option<&'b u8> {
if self.remainder.is_empty() {
None
} else {
let byte = &self.remainder[0];
self.remainder = &self.remainder[1..];
Some(byte)
}
}
}
. . , Rust: . :
struct ByteIter<'remainder> {
remainder: &'remainder [u8]
}
impl<'remainder> ByteIter<'remainder> {
fn next<'mut_self>(&'mut_self mut self) -> Option<&'mut_self u8> {
if self.remainder.is_empty() {
None
} else {
let byte = &self.remainder[0];
self.remainder = &self.remainder[1..];
Some(byte)
}
}
}
'mut_self
, 'remainder
! .
struct ByteIter<'remainder> {
remainder: &'remainder [u8]
}
impl<'remainder> ByteIter<'remainder> {
fn next(&mut self) -> Option<&'remainder u8> {
if self.remainder.is_empty() {
None
} else {
let byte = &self.remainder[0];
self.remainder = &self.remainder[1..];
Some(byte)
}
}
}
fn main() {
let mut bytes = ByteIter { remainder: b"1123" };
let byte_1 = bytes.next();
let byte_2 = bytes.next();
std::mem::drop(bytes); // !
if byte_1 == byte_2 { //
// -
}
}
, , , . Rust ? : (memory safe).
(borrow checker) Rust- , . Rust , - .
, : Rust , .
#[derive(Debug)]
struct NumRef<'a>(&'a i32);
impl<'a> NumRef<'a> {
// 'a,
// self 'a, ? (: , )
fn some_method(&'a mut self) {}
}
fn main() {
let mut num_ref = NumRef(&5);
num_ref.some_method(); // num_ref
num_ref.some_method(); //
println!("{:?}", num_ref); //
}
- , 'a
, &'a mut self
. Rust, ยซ ยป. , Rust some_method
, , , . , , , , . , Rust:
#[derive(Debug)]
struct NumRef<'a>(&'a i32);
impl<'a> NumRef<'a> {
// mut self 'a
fn some_method(&mut self) {}
//
fn some_method_desugared<'b>(&'b mut self){}
}
fn main() {
let mut num_ref = NumRef(&5);
num_ref.some_method();
num_ref.some_method(); //
println!("{:?}", num_ref); //
}
- Rust
- Rust ,
- ,
6) -
Rust . Rust -:
- - ฬ , -
- , ,
- , , ,
- ,
- ,
-
'static
,'static
- , ,
'static
, ยซ - ยป. , , , :
use std::cell::Ref;
trait Trait {}
//
type T1 = Box<dyn Trait>;
// , Box<T> T,
// 'static
type T2 = Box<dyn Trait + 'static>;
//
impl dyn Trait {}
//
impl dyn Trait + 'static {}
//
type T3<'a> = &'a dyn Trait;
// , &'a T T: 'a, 'a
type T4<'a> = &'a (dyn Trait + 'a);
//
type T5<'a> = Ref<'a, dyn Trait>;
// , Ref<'a, T> T: 'a, 'a
type T6<'a> = Ref<'a, dyn Trait + 'a>;
trait GenericTrait<'a>: 'a {}
//
type T7<'a> = Box<dyn GenericTrait<'a>>;
//
type T8<'a> = Box<dyn GenericTrait<'a> + 'a>;
//
impl<'a> dyn GenericTrait<'a> {}
//
impl<'a> dyn GenericTrait<'a> + 'a {}
, , , , , , - . , , , :
trait Trait {}
struct Struct {}
struct Ref<'a, T>(&'a T);
impl Trait for Struct {}
impl Trait for &Struct {} //
impl<'a, T> Trait for Ref<'a, T> {} // ,
, , , - . :
use std::fmt::Display;
fn dynamic_thread_print(t: Box<dyn Display + Send>) {
std::thread::spawn(move || {
println!("{}", t);
}).join();
}
fn static_thread_print<T: Display + Send>(t: T) {
std::thread::spawn(move || {
println!("{}", t);
}).join();
}
:
error[E0310]: the parameter type `T` may not live long enough
--> src/lib.rs:10:5
|
9 | fn static_thread_print<T: Display + Send>(t: T) {
| -- help: consider adding an explicit lifetime bound...: `T: 'static +`
10 | std::thread::spawn(move || {
| ^^^^^^^^^^^^^^^^^^
|
note: ...so that the type `[closure@src/lib.rs:10:24: 12:6 t:T]` will meet its required lifetime bounds
--> src/lib.rs:10:5
|
10 | std::thread::spawn(move || {
| ^^^^^^^^^^^^^^^^^^
, , . .
use std::fmt::Display;
fn dynamic_thread_print(t: Box<dyn Display + Send>) {
std::thread::spawn(move || {
println!("{}", t);
}).join();
}
fn static_thread_print<T: Display + Send + 'static>(t: T) {
std::thread::spawn(move || {
println!("{}", t);
}).join();
}
, . 'static
T
, โ ? . , Rust 'static
, 'static
. Rust:
use std::fmt::Display;
fn dynamic_thread_print(t: Box<dyn Display + Send + 'static>) {
std::thread::spawn(move || {
println!("{}", t);
}).join();
}
fn static_thread_print<T: Display + Send + 'static>(t: T) {
std::thread::spawn(move || {
println!("{}", t);
}).join();
}
- -
7) ,
- -
- Rust
โ , :
use std::fmt::Display;
fn box_displayable<T: Display>(t: T) -> Box<dyn Display> {
Box::new(t)
}
:
error[E0310]: the parameter type `T` may not live long enough
--> src/lib.rs:4:5
|
3 | fn box_displayable<T: Display>(t: T) -> Box<dyn Display> {
| -- help: consider adding an explicit lifetime bound...: `T: 'static +`
4 | Box::new(t)
| ^^^^^^^^^^^
|
note: ...so that the type `T` will meet its required lifetime bounds
--> src/lib.rs:4:5
|
4 | Box::new(t)
| ^^^^^^^^^^^
, , . , , , - 'static
:
use std::fmt::Display;
fn box_displayable<T: Display + 'static>(t: T) -> Box<dyn Display> {
Box::new(t)
}
, โฆ , ? , , , . , :
use std::fmt::Display;
fn box_displayable<'a, T: Display + 'a>(t: T) -> Box<dyn Display + 'a> {
Box::new(t)
}
, , ! ? , . , :
fn return_first(a: &str, b: &str) -> &str {
a
}
:
error[E0106]: missing lifetime specifier
--> src/lib.rs:1:38
|
1 | fn return_first(a: &str, b: &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 `a` or `b`
help: consider introducing a named lifetime parameter
|
1 | fn return_first<'a>(a: &'a str, b: &'a str) -> &'a str {
| ^^^^ ^^^^^^^ ^^^^^^^ ^^^
. , , . :
fn return_first<'a>(a: &'a str, b: &str) -> &'a str {
a
}
- -
- Rust ,
- Rust , , , , .
8)
- ,
- Rust
:
struct Has<'lifetime> {
lifetime: &'lifetime str,
}
fn main() {
let long = String::from("long");
let mut has = Has { lifetime: &long };
assert_eq!(has.lifetime, "long");
{
let short = String::from("short");
// ""
has.lifetime = &short;
assert_eq!(has.lifetime, "short");
// " " ( )
has.lifetime = &long;
assert_eq!(has.lifetime, "long");
// `short`
}
// , `short` ""
assert_eq!(has.lifetime, "long");
}
:
error[E0597]: `short` does not live long enough
--> src/main.rs:11:24
|
11 | has.lifetime = &short;
| ^^^^^^ borrowed value does not live long enough
...
15 | }
| - `short` dropped here while still borrowed
16 | assert_eq!(has.lifetime, "long");
| --------------------------------- borrow later used here
, :
struct Has<'lifetime> {
lifetime: &'lifetime str,
}
fn main() {
let long = String::from("long");
let mut has = Has { lifetime: &long };
assert_eq!(has.lifetime, "long");
//
if false {
let short = String::from("short");
// ""
has.lifetime = &short;
assert_eq!(has.lifetime, "short");
// " " ( )
has.lifetime = &long;
assert_eq!(has.lifetime, "long");
// `short`
}
// , `short` ""
assert_eq!(has.lifetime, "long");
}
, , , if-else
match
, . , . , .
- , -
- Rust , ,
9) mut-
- (re-borrowing)
mut- , , Rust mut- :
fn takes_shared_ref(n: &i32) {}
fn main() {
let mut a = 10;
takes_shared_ref(&mut a); //
takes_shared_ref(&*(&mut a)); //
}
, mut- , ? โ , :
fn main() {
let mut a = 10;
let b: &i32 = &*(&mut a); //
let c: &i32 = &a;
dbg!(b, c); //
}
:
error[E0502]: cannot borrow `a` as immutable because it is also borrowed as mutable
--> src/main.rs:4:19
|
3 | let b: &i32 = &*(&mut a);
| -------- mutable borrow occurs here
4 | let c: &i32 = &a;
| ^^ immutable borrow occurs here
5 | dbg!(b, c);
| - mutable borrow later used here
, , . Rust , mut-? , mut- :
use std::sync::Mutex;
struct Struct {
mutex: Mutex<String>
}
impl Struct {
// self
fn get_string(&mut self) -> &str {
self.mutex.get_mut().unwrap()
}
fn mutate_string(&self) {
// Rust ,
//
// , get_string
*self.mutex.lock().unwrap() = "surprise!".to_owned();
}
}
fn main() {
let mut s = Struct {
mutex: Mutex::new("string".to_owned())
};
let str_ref = s.get_string(); //
s.mutate_string(); // str_ref ,
dbg!(str_ref); // ,
}
, mut- , , : , . , , . , , , . , Rust -. - , , :
// T T
fn some_function<T>(some_arg: &mut T) -> &T;
struct Struct;
impl Struct {
// self
fn some_method(&mut self) -> &Self;
// self T
fn other_method(&mut self) -> &T;
}
, Rust - , , :
use std::collections::HashMap;
type PlayerID = i32;
#[derive(Debug, Default)]
struct Player {
score: i32,
}
fn start_game(player_a: PlayerID, player_b: PlayerID, server: &mut HashMap<PlayerID, Player>) {
// ,
let player_a: &Player = server.entry(player_a).or_default();
let player_b: &Player = server.entry(player_b).or_default();
// -
dbg!(player_a, player_b); //
}
. or_default()
&mut Player
, &Player
- . , , :
use std::collections::HashMap;
type PlayerID = i32;
#[derive(Debug, Default)]
struct Player {
score: i32,
}
fn start_game(player_a: PlayerID, player_b: PlayerID, server: &mut HashMap<PlayerID, Player>) {
// Player,
server.entry(player_a).or_default();
server.entry(player_b).or_default();
// , ,
let player_a = server.get(&player_a);
let player_b = server.get(&player_b);
// -
dbg!(player_a, player_b); //
}
, , โ , .
(. : . , server
: Player
, . Player
, . , , , . Rust , Player
)
- mut- ,
- mut- ,
10) ,
, .
, , , , .
fn function(x: &i32) -> &i32 {
x
}
fn main() {
let closure = |x: &i32| x;
}
:
error: lifetime may not live long enough
--> src/main.rs:6:29
|
6 | let closure = |x: &i32| x;
| - - ^ returning this value requires that `'1` must outlive `'2`
| | |
| | return type of closure is &'2 i32
| let's call the lifetime of this reference `'1`
:
//
fn function<'a>(x: &'a i32) -> &'a i32 {
x
}
fn main() {
//
let closure = for<'a, 'b> |x: &'a i32| -> &'b i32 { x };
// , Rust,
}
. , , , , . ? :
fn main() {
// -, , ,
let identity: dyn Fn(&i32) -> &i32 = |x: &i32| x;
// , -
let identity: Box<dyn Fn(&i32) -> &i32> = Box::new(|x: &i32| x);
// ,
let identity: &dyn Fn(&i32) -> &i32 = &|x: &i32| x;
// :)
let identity: &'static (dyn for<'a> Fn(&'a i32) -> &'a i32 + 'static) = &|x: &i32| -> &i32 { x };
// ,
let identity: impl Fn(&i32) -> &i32 = |x: &i32| x;
// ,
let identity = for<'a> |x: &'a i32| -> &'a i32 { x };
// "impl trait"
fn return_identity() -> impl Fn(&i32) -> &i32 {
|x| x
}
let identity = return_identity();
//
fn annotate<T, F>(f: F) -> F where F: Fn(&T) -> &T {
f
}
let identity = annotate(|x: &i32| x);
}
, , , .
- , .
11) 'static
- 'a
-
:
fn get_str<'a>() -> &'a str; //
fn get_str() -> &'static str; // 'static
, , - . , , , , , .
, , 'static
- , 'a
-, Rust 'static
- 'a
- . : , , . , , .
use rand;
fn generic_str_fn<'a>() -> &'a str {
"str"
}
fn static_str_fn() -> &'static str {
"str"
}
fn a_or_b<T>(a: T, b: T) -> T {
if rand::random() {
a
} else {
b
}
}
fn main() {
let some_string = "string".to_owned();
let some_str = &some_string[..];
let str_ref = a_or_b(some_str, generic_str_fn()); //
let str_ref = a_or_b(some_str, static_str_fn()); //
}
, , :
use rand;
fn generic_str_fn<'a>() -> &'a str {
"str"
}
fn static_str_fn() -> &'static str {
"str"
}
fn a_or_b_fn<T, F>(a: T, b_fn: F) -> T
where F: Fn() -> T
{
if rand::random() {
a
} else {
b_fn()
}
}
fn main() {
let some_string = "string".to_owned();
let some_str = &some_string[..];
let str_ref = a_or_b_fn(some_str, generic_str_fn); //
let str_ref = a_or_b_fn(some_str, static_str_fn); //
}
:
error[E0597]: `some_string` does not live long enough
--> src/main.rs:23:21
|
23 | let some_str = &some_string[..];
| ^^^^^^^^^^^ borrowed value does not live long enough
...
25 | let str_ref = a_or_b_fn(some_str, static_str_fn);
| ---------------------------------- argument requires that `some_string` is borrowed for `'static`
26 | }
| - `some_string` dropped here while still borrowed
โ , &'static str
&'a str
, for<T> Fn() -> &'static T
for<'a, T> Fn() -> &'a T
. โ , โ .
-
for<'a, T> fn() -> &'a T
,for<T> fn() -> &'static T
T
&T
,&mut T
&T
&mut T
T: 'static
, ยซT
'static
ยป-
T: 'static
,T
'static
. -
T: 'static
, ,T
- -
T: 'a
,&'a T
T: 'a
, , ,&'a T
-
T: 'static
,T: 'a
,'static
>='a
'a
- Rust ,
- Rust
- Rust ,
- ,
- -
- Rust , , , , .
- , -
- Rust , ,
- ,
- mut- ,
-
for<'a, T> fn() -> &'a T
,for<T> fn() -> &'static T
- pretzelhammer Twitter
- (
Watch
Releases only
)
Rust, nlinker.