三条消除规则 (因此用于省略)
编译器使用三条消除规则来确定哪些场景不需要显式地去标注生命周期。其中第一条规则应用在输入生命周期上,第二、三条规则应用在输出生命周期上。若编译器发现三条规则都不适用时,就会报错,提示你需要手动标注生命周期。
规则 1:每一个引用参数都会获得独自的生命周期
例如:
-
一个引用参数的函数:
fn foo<'a>(x: &'a i32) -
两个引用参数的函数:
fn foo<'a, 'b>(x: &'a i32, y: &'b i32)
依此类推,每个引用类型的参数都将拥有自己的生命周期标注。
规则 2:若只有一个输入生命周期,那么该生命周期会被赋给所有的输出生命周期
也就是说,所有返回值的生命周期都等于该输入生命周期。
例如:
fn foo(x: &i32) -> &i32
此时,x 参数的生命周期会被自动赋给返回值 &i32,等价于:
fn foo<'a>(x: &'a i32) -> &'a i32
规则 3:若存在多个输入生命周期,且其中一个是 &self 或 &mut self,则 &self 的生命周期被赋给所有的输出生命周期
拥有 &self 形式的参数说明该函数是一个方法,该规则使得方法的使用变得更加便利。
思考:返回值生命周期与 &self 不同时怎么办?
例如第三条规则,若一个方法,它的返回值的生命周期与参数 &self 不一致怎么办?总不能强迫返回值总是和 &self 活得一样久吧?
答案:手动标注生命周期。
这些规则只是当你没有手动标注生命周期时编译器默认使用的规则,当你标注生命周期后,编译器自然会乖乖听你的话。
1. &'static 与 T: 'static 的区别
在 Rust 中,&'static 和 T: 'static 都涉及生命周期 'static,但它们的含义和用途完全不同:
| 特性 | &'static | T: 'static |
|---|---|---|
| 含义 | 引用指向的数据必须在整个程序运行期间有效(如全局变量、字符串字面量)。 | 类型 T 本身可以拥有其数据,或者包含 'static 引用。T 不一定是引用! |
| 约束对象 | 引用的生命周期 | 类型 T 的生命周期 |
| 典型例子 | &'static str(字符串字面量) | String, i32(拥有所有权的类型) |
关键结论
&'static:引用必须指向静态数据。T: 'static:类型T可以是任意拥有所有权的类型,或包含'static引用的类型。
2. 代码分析
(1) 第一段代码(不报错)
fn print_it<T: Debug + 'static>(input: &T) {
println!("'static value passed in is: {:?}", input);
}
fn main() {
let i = 5;
print_it(&i); // ✅ 合法
}
- 为什么合法?
T是i32(即i的类型),满足T: 'static,因为i32是拥有所有权的类型。input: &T中的生命周期由调用处推断,与'static无关。- 关键点:
T: 'static不约束引用的生命周期,只约束T本身。
(2) 第二段代码(报错)
fn print_it<T: Debug + 'static>(input: T) {
println!("'static value passed in is: {:?}", input);
}
fn print_it1(input: impl Debug + 'static) {
println!("'static value passed in is: {:?}", input);
}
fn main() {
let i = 5;
print_it(&i); // ❌ 错误
print_it1(&i); // ❌ 错误
}
- 为什么报错?
- 在
print_it(&i)中,泛型T被推断为&i32,而&i32不满足T: 'static。 &i32的生命周期是main函数内的局部作用域,不是'static。- 错误信息:
error[E0597]: `i` does not live long enough --> src/main.rs:13:15 | 13 | print_it(&i); | ---------^^- | | | | | borrowed value does not live long enough | argument requires that `i` is borrowed for `'static` 14 | } | - `i` dropped here while still borrowed
- 在
3. 深入理解 T: 'static
(1) T: 'static 的两种可能性
- 所有权类型:
T拥有其数据(如String,i32),无需依赖外部生命周期。let s: String = String::from("hello"); // String: 'static let num: i32 = 42; // i32: 'static - 包含
'static引用的类型:T内部有&'static引用。let s: &'static str = "hello"; // &'static str: 'static
(2) 为什么 &i32 不满足 T: 'static?
&i32是一个引用,其生命周期由被引用的数据决定。- 如果被引用的数据(如局部变量
i)的生命周期短于'static,则&i32也不满足T: 'static。
4. 修复方法
(1) 传递所有权类型
fn main() {
let i = 5;
print_it(i); // ✅ 合法:i 是 i32,满足 T: 'static
print_it1(i); // ✅ 合法
}
(2) 使用 'static 引用
static GLOBAL: i32 = 42;
fn main() {
print_it(&GLOBAL); // ✅ 合法:&GLOBAL 是 &'static i32
print_it1(&GLOBAL); // ✅ 合法
}
5. 总结
| 场景 | 代码 | 是否合法 | 原因 | |———————|————————–|———-|———————————————————————-| | 传递 &T(T: 'static) | print_it(&i32) | ✅ | T 是 i32(拥有所有权),引用生命周期由调用处推断。 | | 传递 &T(T = &i32) | print_it(&i32) | ❌ | T 是 &i32,其生命周期不是 'static。 | | 传递所有权类型 | print_it(i32) | ✅ | i32 满足 T: 'static。 | | 传递 &'static T | print_it(&'static i32) | ✅ | T 是 i32,且引用是 'static。 |
关键规则:
T: 'static约束的是类型T,不是引用!- 当
T是引用时(如&i32),它必须满足&'static i32才能通过T: 'static约束。