Rust 所有对象都具有 lifetime。
lifetime 是编译时的注解参数, 编译器使用和检查,运行时不感知,也不能在表达式中使用。
// error: expected expression, found keyword `dyn`
let b = &'a dyn MyTrait + Send + 'static;
// error: borrow expressions cannot be annotated with lifetimes
let b = &'a(dyn MyTrait + Send + 'static);
lifetime 必须位于其它泛型参数之前,如 <'a, T, T2>
。
struct Student<'a, T> {
name: &'a str
description: &'a mut str // lifetime 注解必须紧随 & 操作符后
age: i32
}
borrow checker #
lifetime 是一个相对的约束,Rust borrow checker
检查 lifetime 是否有效,否则编译报错:
-
<T: 'b>
:表示 T 的生命周期比'b
长。&'b T
隐式表示T: 'b
, 即 T 的生命周期要比 ‘b 长。 -
<T: Trait + 'b>
:表示 T 实现 Trait 且 T 的生命周期比 ‘b 长。 -
struct foo<'a: 'b, 'b,T: 'b> (val1: &'a String, val2: &'a String, val3: &'b String, val4: &T)
:<'a: 'b, 'b>
:表示 ‘a 的生命周期比 ‘b 长,需要先写 ‘a: ‘b 再写 ‘b。- val1 和 val2 的生命周期一样长, 且比 val3 的生命周期长;
- val4 的生命周期要比 T 长,进而比 ‘b 长,所以 val4 的生命周期要比 val3 长;
- struct foo 对象的生命周期不能长于 ‘a 和 ‘b,否则 foo 可能会引用应该销毁的对象;
-
fn print_refs<'a: 'b, 'b>(x: &'a i32, y: &'b i32) -> &'b String
:- 函数执行期间 ‘a, ‘b 的引用要一直有效,即函数的生命周期要比 ‘a 和 ‘b 短;
- ‘a: ‘b 表示 ‘a 的生命周期比 ‘b 长,所以 x 的生命周期要比 y 长;
- 返回值的生命周期要和 y 一样长;
fn add_ref<'a, 'b, T>(a: &'a mut T, b: &'b T) -> &'a T where T: std::ops::Add<T, Output = T> + Copy,
{
*a = *a + *b;
a // OK
// Error: b 的生命周期是 'b,与返回值的声明 'a 不一致。
// b
}
#[derive(Debug)]
// 等效于 struct Ref<'a, T: 'a>(&T);
struct Ref<'a, T: 'a>(&'a T);
// 等效于 fn print_ref<'a, T>(t: &T) where T: Debug{
fn print_ref<'a, T>(t: &'a T) where T: Debug + 'a {
println!("`print_ref`: t is {:?}", t);
}
struct/enum 的 lifetime #
- 如果 struct/enum 有引用成员,则必须要为 struct/enum 整体指定 lifetime 参数;
- struct/enum 对象的 lifetime 要比指定的所有 lifetime 参数短;
- 特殊的
struct MyStruct<'static>
表示 MyStruct 对象的 lifetime 可以任意长(因为'static
是在程序整个运行期间都有效);
#[derive(Debug)]
struct Borrowed<'a>(&'a i32);
#[derive(Debug)]
struct NamedBorrowed<'a> {
x: &'a i32,
y: &'a i32,
}
#[derive(Debug)]
enum Either<'a> {
Num(i32),
Ref(&'a i32),
}
fn main() {
let x = 18;
let y = 15;
let single = Borrowed(&x);
let double = NamedBorrowed { x: &x, y: &y };
let reference = Either::Ref(&x);
let number = Either::Num(y);
println!("x is borrowed in {:?}", single);
println!("x and y are borrowed in {:?}", double);
println!("x is borrowed in {:?}", reference);
println!("y is *not* borrowed in {:?}", number);
}
函数 lifetime #
- 借用类型的函数参数和返回值,必须有 lifetime 注解,如果没有显式指定,则编译器自动添加;
- 所有借用类型返回值的 lifetime 必须和某些输入的值的 lifetime 相同(如 &self),或者是
'static
;
- 闭包函数返回借用时,一般需要是 ‘static 类型;
- 如果自动推断后还是不能确定借用类型返回值和输入值 lifetime 的关系,则编译报错,这时需要手动设置 lifetime;
fn print_refs<'a, 'b>(x: &'a i32, y: &'b i32) {
println!("x is {} and y is {}", x, y);
}
fn main() {
let (four, nine) = (4, 9);
// &four、&nine 的生命周期必须比 print_refs 执行的时间长,也就是在函数执行期间,four 和 nice 对象的引用要一直有效。
print_refs(&four, &nine);
failed_borrow(); // 报错。
}
// 传入的 x 的生命周期要比 print_one 函数长
fn print_one<'a>(x: &'a i32) {
println!("`print_one`: x is {}", x);
}
fn add_one<'a>(x: &'a mut i32) {
*x += 1;
}
fn print_multi<'a, 'b>(x: &'a i32, y: &'b i32) {
println!("`print_multi`: x is {}, y is {}", x, y);
}
// 返回值的 lifetime 必须来源于输入参数的 lifetime 或 'static
fn pass_x<'a, 'b>(x: &'a i32, _: &'b i32) -> &'a i32 { x }
fn failed_borrow<'a>() {
let _x = 12;
// ERROR: `_x` does not live long enough
let _y: &'a i32 = &_x;
// 这是由于 &_x 的借用有效期持续到函数返回,而 'a 的有效期要长于函数。
}
// 错误:返回值的 lifetime 和 'a 一样长, 而函数内的 ref 在函数返回即失效, 所以编译报错。
fn invalid_output<'a>() -> &'a String { &String::from("foo") }
// 错误:编译器不能推断出返回值引用的 lifetime 关系。
fn order_string(s1 : &str, s2 : &str) -> (&str, &str) {
if s1.len() < s2.len() {
return (s1, s2);
}
return (s2, s1);
}
<’_> #
如果泛型类型包含 lifetime 参数,但在为它实现的 Trait(的方法)并不需要该 lifetime 或编译器可以自动推导时,可以使用 <'_>
来指定该类型所需 lifetime 参数 :
// BufReader 类型需要指定 lifetime 参数,但是在实现 Read 时并没有使用该 lifetime 参数
impl<'a> Reader for BufReader<'a> {
// 'a is not used in the following methods
}
// 等效于
impl Reader for BufReader<'_> {
}
// 自动推导的例子
impl<T> Vec<T> {
pub fn iter(&self) -> Iter<'_, T> {
// 编译器自动推导:返回的 Iter 的 lifetime 和 &self 一致
// [...]
}
}
在 lifetime 消除的场景下,也可以使用 <'_>
:
struct NumRef<'a> {
x: &'a i32,
}
// NumRef<'_> 的 lifetime 由编译器自动推导:
fn as_num_ref(x: &i32) -> NumRef<'_> {
NumRef { x: &x }
}
fn main() {
let x: i32 = 99;
let x_ref = as_num_ref(&x);
// `x_ref` cannot outlive `x`, etc.
}
liftime subtype #
lifetime 之间存在 subtype 关系 ,即更长的 lifetime 是更短 lifetime 的子类型,更长的 lifetime 可以被 coerced 到短的 lifetime:
'a: 'b
:表示 ‘a 比 ‘b 长,在返回 ‘b 时可以返回 ‘a 的 lifetime 对象;'static
: 在程序运行期间(甚至 main 函数返回)一直有效,所以是其它任意 lifetime 的子类型;HRTB
:HRTB 声明的 lifetime 是其它任意 lifetime 的子类型;
fn choose_first<'a: 'b, 'b>(first: &'a i32, _: &'b i32) -> &'b i32 {
first // 'a 被 coreced 到更短的 'b
}
fn main() {
let first = 2; // Longer lifetime
{
let second = 3; // Shorter lifetime
println!("The product is {}", multiply(&first, &second));
println!("{} is the first", choose_first(&first, &second));
};
}
trait MyTrait<'a> {
fn say_hello(&'a self) -> &'a String;
}
struct MyStruct(String);
impl<'a> MyTrait<'a> for MyStruct {
fn say_hello(&'a self) -> &'a String {
println!("hello {}", self.0);
&self.0
}
}
fn printf_hello<'a, 'b>(say_hello: Option<&'a (dyn MyTrait<'a> + Send + 'b)>) -> Option<&'b String> where 'a: 'b,
{
let hello = if let Some(my_trait) = say_hello {
my_trait.say_hello()
} else {
return None;
};
Some(hello)
}
隐式 liftime bound:&'a T
隐式表示 T: 'a
, 即 T 的生命周期比 ‘a 长:
fn requires_t_outlives_a_not_implied<'a, T: 'a>() {}
// OK:T 的生命周期比 'a 长
fn requires_t_outlives_a<'a, T>(x: &'a T) {
requires_t_outlives_a_not_implied::<'a, T>();
}
// 错误:T 的生命周期不满足比 'a 长的约束
fn not_implied<'a, T>() {
requires_t_outlives_a_not_implied::<'a, T>();
}
struct Struct<'a, T> {
field: &'a T, // T 的生命周期比 'a 长
}
enum Enum<'a, T> {
SomeVariant(&'a T), // T 的声明周期比 'a 长
OtherVariant,
}
trait Trait<'a, T: 'a> {}
impl<'a, T> Trait<'a, T> for () {} // 错误:T 的声明周期不一定比 'a 长
impl<'a, T> Trait<'a, T> for &'a T {} // OK
使用 std::mem::transmute()
来扩充或缩短 lifetime,因为该函数转换后的对象的 lifetime 是显式指定的:
struct R<'a>(&'a i32);
unsafe fn extend_lifetime<'b>(r: R<'b>) -> R<'static> {
std::mem::transmute::<R<'b>, R<'static>>(r)
}
unsafe fn shorten_invariant_lifetime<'b, 'c>(r: &'b mut R<'static>) -> &'b mut R<'c> {
std::mem::transmute::<&'b mut R<'static>, &'b mut R<'c>>(r)
}
HRTB (Higher-Rank Trait Bounds) #
下面对泛型类型 F 没有加 lifetime 的代码是可以正常编译的:
struct Closure<F> {
data: (u8, u16),
func: F,
}
impl<F> Closure<F> where F: Fn(&(u8, u16)) -> &u8,
{
fn call(&self) -> &u8 {
(self.func)(&self.data)
}
}
fn do_it(data: &(u8, u16)) -> &u8 { &data.0 }
fn main() {
let clo = Closure { data: (0, 1), func: do_it };
println!("{}", clo.call());
}
如果要给上面的代码添加 lifetime bound ,则会遇到 F 的 lifetime 该如何指定的问题:
struct Closure<F> {
data: (u8, u16),
func: F,
}
impl<F> Closure<F> // where F: Fn(&'??? (u8, u16)) -> &'??? u8,
{
fn call<'a>(&'a self) -> &'a u8 {
(self.func)(&self.data)
}
}
// Error:表达式 &'b data.0 不能指定 lifetime
//fn do_it<'b>(data: &'b (u8, u16)) -> &'b u8 { &'b data.0 }
fn do_it<'b>(data: &'b (u8, u16)) -> &'b u8 { &data.0 }
fn main() {
'x: {
let clo = Closure { data: (0, 1), func: do_it };
println!("{}", clo.call());
}
}
// Error1:
impl<'a, F> Closure<F> where F: Fn(&'a (u8, u16)) -> &'a u8,
{
fn call<'a>(&'a self) -> &'a u8 {
// lifetime name `'a` shadows a lifetime name that is already in scope
(self.func)(&self.data)
}
}
// Error2:
impl<'a, F> Closure<F> where F: Fn(&'a (u8, u16)) -> &'a u8,
{
fn call<'b>(&'b self) -> &'b u8 {
// method was supposed to return data with lifetime `'a` but it is returning data with lifetime `'b`
(self.func)(&self.data)
}
}
// Error3:
impl<'a, F> Closure<F> where F: Fn(&'a (u8, u16)) -> &'a u8,
{
fn call(&self) -> &u8 {
// 编译器自动为 &self 添加 liefitime 如 '1:
// method was supposed to return data with lifetime `'1` but it is returning data with lifetime `'a`
(self.func)(&self.data)
}
}
// Error4: 可以编译过,但是要求 Closure 对象的 liefitime 和传入的 Fn 的参数 lifetime 一致,不符合预期语义: Fn 的函数有自己独立的 lifetime,和
// Closure 对象 lifetime 无关。
impl<'a, F> Closure<F> where F: Fn(&'a (u8, u16)) -> &'a u8,
{
fn call(&'a self) -> &'a u8 {
(self.func)(&self.data)
}
}
// Error5: argument requires that `'b` must outlive `'a
impl<'a: 'b, 'b, F> Closure<F> where F: Fn(&'a (u8, u16)) -> &'a u8,
{
// 函数的参数要求 'b: 'a, 但是函数的返回值要求 'a: 'b, 相互矛盾,所以编译失败。
fn call(&'b self) -> &'b u8 { (self.func)(&self.data) }
}
// OK:HRTB
// 1. 函数 Closure 泛型参数中没有 'a lifetitme
//
// 2. 在 F 的 Bound 中使用 for <'a> 来声明 'a lifetime,这里 for <'a> 表示对于任意 lifetime 'a, Fn 都满足, 也即 for <'a> 的
// 'a 可以看作无限长,可以是任何其他 lifetime 的 subtype
impl<F> Closure<F> where F: for <'a> Fn(&'a (u8, u16)) -> &'a u8,
{
fn call<'a>(&'a self) -> &'a u8 {
// 'a 和上面的 for <'a> 没有关系,是 call() 方法自己的 lifetime。
// 由于编译器会自动加 lifetime,所以可以不指定 'a 如:fn call(&self) -> &u8
(self.func)(&self.data)
}
}
for <'a> Fn(&'a input u8)
表示 Fn 满足任意长的 'a liftime
限界。
当闭包的参数或返回值使用借用时,需要使用 HRTB 来指定 lifetime。
T: 'a + TraitA + for <'a> Fn(&'a i32) -> i32
T: for <'a> Fn(&'a i32) -> i32 + 'b + TraitA
// 等效于
T: (for <'a> Fn(&'a i32) -> i32) + 'b + TraitA
HRTB 主要用于为闭包类型的限界(Fn/FnMut/FnOnce) 指定 lifetime,该 lifetime 是任 意其它 lifetime 的子类型,这样闭包返回的借用对象可以赋值给其它 lifetime 的变量。
// HRTB 为闭包的参数和返回值指定 lifetime
fn use_closure<F>(f: F) where F: for<'a> Fn(&'a str) -> &'a str,
{
let s = "example";
let result = f(s);
println!("{}", result);
}
HRTB 也可以用于 fn 函数指针的 lifetime 声明:
// HRTB 为函数指针 fn 的参数和返回值指定 lifetime
fn use_fn_ptr(f: for<'a> fn(&'a str) -> &'a str) {
let s = "example";
let result = f(s);
println!("{}", result);
}
let test_fn: for<'a> fn(&'a _) -> &'a _ = |p: &String| p;
println!("Results:{}", test_fn(&"asdfab".to_string()));
HRTB 等效语法:
where F: for<'a> Fn(&'a (u8, u16)) -> &'a u8
// 等效为
where for <'a> F: Fn(&'a (u8, u16)) -> &'a u8
‘static #
&'static
表示借用对象在程序运行期间一直有效,即使 main 函数返回也还有效。
例如全局 const/static
常量,字符串字面量等,它们都保存在程序二进制的 read-only 部分,在程序运行期间一直有效。
let s: &'static str = "hello world";
fn generic<T>(x: T) where T: 'static {}
fn need_static(r : &'static str) {
assert_eq!(r, "hello");
}
fn main() {
let v: &'static string = "hello";
need_static(v);
println!("Success!")
}
'static
是任意其它 lieftime 的子类型,所以可以被 coerced 到一个更短的生命周期:
static NUM: i32 = 18;
fn coerce_static<'a>(_: &'a i32) -> &'a i32 {
&NUM
}
'static
的两种解释:
- 转移对象所有权时,对应的 Bound 会隐式的具有 ‘static lifetime;
- 引用在程序的整个运行期间一直有效,如全局 const/static 常量,或函数内定义的 static 常量;
'static
只有在引用场景下,才代表引用存在于整个程序运行期间。对象赋值、闭包 move 等语义下,接收方拥有传入的对象的所有权,这时该接收方也实现了 'static
,如 Box<dyn MyTrait + 'static + Send>
会将 trait object 的所有权转移到返回的 Box 对象中,所以传入的 trait object 也具有 'static
。
use std::fmt::Debug;
// 函数 hold input 值,所以 T 的 Bound 会隐式的自动加 'static 并自动满足。
fn print_it<T: Debug + 'static>(input: T) {
println!("'static value passed in is: {:?}", input);
}
// 函数 hold input 值,所以 input 的 Bound 会隐式的加 'static 并自动满足。
fn print_it1(input: impl Debug + 'static) {
println!("'static value passed in is: {:?}", input);
}
// input 借用的 T 值必须是 'static,在程序的整个生命周期都有效(即使 main 函数返回)。
fn print_it2<T: Debug + 'static>(input: &T) {
println!("'static value passed in is: {:?}", input);
}
fn main() {
let i = 5;
print_it(i);
// i 的 lifetime 持续到 main() 函数返回,而非整个程序声明周期,所以不满两个函数泛型限界的要求。
print_it(&i); // `i` does not live long enough
print_it1(&i); // `i` does not live long enough
// but this one WORKS !
print_it2(&i);
}
Box/String 的 leak() 方法 #
该方法返回一个可以自定义 lifetime 的 &‘a mut 借用类型,常用于返回一个 'static
借用。
Box::leak()
:
- 编译器释放 Box 中的对象所有权,并承诺永远不会 drop 它。所以返回的借用在程序整个生命周期内一直有效,相当于是
'static
; - 缺点:它是内存泄露机制,需要小心使用!
// 'a 是自定义的参数
pub fn leak<'a>(self) -> &'a mut str
// 示例:
let x = String::from("bucket");
// 指定 'a 为 'static
let static_ref: &'static mut str = x.leak();
assert_eq!(static_ref, "bucket");
闭包的 ‘static 要求 #
在多线程场景中,提交的闭包必须实现 Send + 'static
,闭包返回值也必须实现 Send + 'static
。
闭包会被编译器转换成一个匿名类型,它可以捕获上下文中的对象,捕获方式分为:
- 共享借用 &T;
- 可变借用 &mut T;
- 转移对象所有权;
具体是哪种捕获方式,编译器根据闭包使用的上下文对象自动而定。默认优先级是:&T
,&mut T
,然后是转移。
对于前两种借用捕获的方式,生成的匿名类型中的借用一般很难满足 'static
的要求,所以常用 move 闭包类型,通过将对象的所有权转移到闭包中来满足 'static
要求。
另外,在异步场景,各种 spawnXX()
函数提交的 Future 对象,也需要实现 Send + 'static
:
- spawn(future):多线程来调度执行,future 需要实现
Future+Send+'static
- spawn_local(future): 单线程执行,future 需要实现
Future+'static
- spawn_blocking(closure): 多线程执行,closure 需要实现
Send+'static
(注意没有 Future,它是同步闭包);
async block/closure 会捕获上下文对象,而且默认优先是借用 &T 和 &mut T 捕获,所以很难满足 ‘static 要求,故一般需要使用 async move 的 block 和 closure 方式。
lifetime elision #
变量的 lifetime 开始于它创建,结束于它被销毁。Rust borrow checker
使用 lifetime annotation
检查所有借用操作,从而确保所有的 borrow 都
是有效的。
由于 elision rule
的存在,一般不需要显式指定 lifetime annotation
:
// 不需要显式指定 lifetime annotation
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[..]
}
Rust 编译器的 elision rule
如下:
-
针对函数的输入参数:编译器自动为所有引用的输入参数设置一个 lifetime:
struct S<'a, 'b> { x: &'a i32, y: &'b i32 } fn sum_r_xy(r: &i32, s: S) -> i32 { r + s.x + s.y } // 等效为: fn sum_r_xy<'a, 'b, 'c>(r: &'a i32, s: S<'b, 'c>) -> i32
-
针对函数的输出参数:如果函数 有且只有一个 输入引用类型参数,则该 lifetime 会设置为所有输出引用参数:
fn first_third(point: &[i32; 3]) -> (&i32, &i32) { (&point[0], &point[2]) } // 等效为 fn first_third<'a>(point: &'a [i32; 3]) -> (&'a i32, &'a i32)
-
针对方法的输出参数:如果函数是方法,即第一个参数类型是
&self 或 &mut self
,则它的 lifetime 会设置到所有输出引用参数。struct StringTable { elements: Vec<String>, } impl StringTable { fn find_by_prefix(&self, prefix: &str) -> Option<&String> { for i in 0 .. self.elements.len() { if self.elements[i].starts_with(prefix) { // [i] 返回对象本身,这里需要通过 & 获得它的引用 return Some(&self.elements[i]); } } None } } // 等效为 fn find_by_prefix<'a, 'b>(&'a self, prefix: &'b str) -> Option<&'a String>
如果经过上面 elision rule
后,如果还有引用参数 lifetime 不明确,Rust 编译出错:
// OK
fn first_word(s: &str) -> &str {
// 编译器等效为
fn first_word<'a>(s: &'a str) -> &'a str {
fn longest(x: &str, y: &str) -> &str { // 错误,经过 elision rule 后输出引用 lifetime 不明确
// fn longest<'a, 'b>(x: &'a str, y: &'b str) -> &str {
fn get_str() -> &str; // 错误:不能推断返回值 lifetime
通过指定 '_
lifetime 可以强制使用上面的 lifetime elision 规则。
- 对于 path 的 lifetime,优选
'_
。
struct NumRef<'a> {
x: &'a i32,
}
// NumRef<'_> 表示使用上面的 elision rule 来自动推断生命周期
// 即与 x 的生命周期一致。
fn as_num_ref(x: &i32) -> NumRef<'_> {
NumRef { x: &x }
}
fn new1(buf: &mut [u8]) -> Thing<'_>; // elided - preferred
// 等效于:
fn new2(buf: &mut [u8]) -> Thing; // elided
// 等效于:
fn new3<'a>(buf: &'a mut [u8]) -> Thing<'a>;
上面的 lifetime elision rule 也可能带来问题,例如:
// 自动推导的 &MyType2 的生命周期和 self 一致
fn get_token(&self, var: &MyType) -> Option<&MyType2> {}
// 但是如果要和 var 一致,则需要手动来指定 lifetime
fn get_token<'a, 'b>(&'a self, var: &'b MyType) -> Option<&'b MyType2> {}
由于 lifetime elision 的存在,有些需要 lifetime 的类型也可以不指定 lifetime,而由编译器推导:
// std::slice::Iter` 的定义包含一个 lifetime
pub struct Iter<'a, T>
where
T: 'a,
{ /* private fields */ }
// 但这里并没有为 Iter 类型的返回值指定 lifetime,而是由编译器自动推导
fn items(scores: &[u8]) -> std::slice::Iter<u8> {
scores.iter()
}
从 Rust 1.89.0 开始,这种情况 Rust 编译器会警告, 提示需要显式的加一个 '_
标识,即明确告诉编译器按照 lifetime elision
规则进行推导:
warning: hiding a lifetime that's elided elsewhere is confusing
--> src/lib.rs:1:18
|
1 | fn items(scores: &[u8]) -> std::slice::Iter<u8> {
| ^^^^^ -------------------- the same lifetime is hidden here
| |
| the lifetime is elided here
|
= help: the same lifetime is referred to in inconsistent ways, making the signature confusing
= note: `#[warn(mismatched_lifetime_syntaxes)]` on by default
help: use `'_` for type paths
|
1 | fn items(scores: &[u8]) -> std::slice::Iter<'_, u8> {
| +++
其它可以消除 lifetime 的情况:
// 隐式推导:T: 'a
fn requires_t_outlives_a<'a, T>(x: &'a T) {}
fn requires_t_outlives_a_not_implied<'a, T: 'a>() {}
fn requires_t_outlives_a<'a, T>(x: &'a T) {
// OK, 因为 &'a T 隐式表示 T: 'a, 满足函数签名要求
requires_t_outlives_a_not_implied::<'a, T>();
}
fn not_implied<'a, T>() {
// Error:因为 T 和 'a 之间的关系不明确
requires_t_outlives_a_not_implied::<'a, T>();
}
trait object 有特殊的 lifetime bound 规则:
Box<dyn Trait>
等效于Box<dyn Trait + 'static>
, 因为dyn Trait
对象的所有权被转移到 Box 中;&'x Box<dyn Trait>
等效于&'x Box<dyn Trait + 'static>
;&'r Ref<'q, dyn Trait>
等效于&'r Ref<'q, dyn Trait+'q>
;