
多个生命周期、'static:生命周期终于能看懂了!
还记得第一次看到生命周期注解时的感受吗?
fn longest<'a>(x: &'a str, y: &'a str) -> &'a str
我: "这 'a 是个啥?为啥到处都要写?"
Rust: "告诉编译器引用能活多久。"
我: "编译器自己不能推断吗?"
Rust: "有些情况可以,有些不行。"
今天咱们就来攻克生命周期的进阶内容:多个生命周期参数、'static 生命周期、trait 对象中的生命周期。
学完这篇,你再看生命周期注解,应该不会再有"这啥玩意儿"的感觉了。
问题: 当函数有多个输入引用,但它们的生命周期不同时:
fn longest(x: &str, y: &str) -> &str {
if x.len() > y.len() {
x
} else {
y
}
}
编译器: "等等,返回的引用是哪个的?x 还是 y?它们的生命周期可能不一样啊!"
解决: 用多个生命周期参数
fn longest<'a, 'b>(x: &'a str, y: &'b str) -> &'a str {
// 返回的引用生命周期和 x 一样
x // 只能返回 x,不能返回 y
}
但这不合理啊! 有时候 y 更长,为什么不能返回 y?
答案: 生命周期注解是给调用者看的契约,不是给实现者看的。
fn longest<'a, 'b>(x: &'a str, y: &'b str) -> &'a str {
// 这个函数签名说:"我返回的引用至少能活 'a 这么久"
// 所以只能返回 x,因为 y 的生命周期是 'b,可能比 'a 短
}
正确的做法: 让返回值的生命周期和两个参数都相关
fn longest<'a>(x: &'a str, y: &'a str) -> &'a str {
// 两个参数生命周期相同
// 返回的引用也能活这么久
if x.len() > y.len() {
x
} else {
y
}
}
不是所有函数都要写生命周期注解。Rust 有三条省略规则:
规则 1: 每个引用参数获得独立的生命周期
fn first(word: &str) -> &str {
// 相当于 fn first<'a>(word: &'a str) -> &str
// 但返回值没有标注,编译器不知道返回哪个的生命周期
}
规则 2: 如果只有一个输入生命周期,它赋给所有输出引用
fn first<'a>(word: &'a str) -> &'a str {
// ✅ 可以省略
// 编译器自动推断
}
规则 3: 如果有 &self 或 &mut self,self 的生命周期赋给所有输出引用
impl<'a> MyStruct<'a> {
fn get_str(&self) -> &str {
// ✅ 可以省略
// 相当于 fn get_str<'a>(&'a self) -> &'a str
}
}
什么时候必须写?
'static:表示引用可以活在整个程序运行期间。
哪些是 'static?
let s: &'static str = "hello"; // 字面量存在代码段,程序结束才释放
const NAME: &str = "Larry"; // 常量也是 'static
static VERSION: &str = "1.0.0"; // 静态变量
'static 作为约束:
fn print_it<T: 'static>(value: T) {
// T 必须是 'static 的
// 意味着 T 不能包含任何非 'static 的引用
}
常见场景:
use std::thread;
fn main() {
let s = "hello"; // &'static str
thread::spawn(move || {
println!("{}", s); // ✅ 可以,s 是 'static
});
// 如果用 String 的引用就不行
let owned = String::from("world");
// thread::spawn(move || {
// println!("{}", &owned); // ❌ owned 不是 'static
// });
}
问题: trait 对象也需要生命周期注解
trait DisplayTrait: std::fmt::Display {}
// ❌ 这样不行
// trait Trait {
// fn get(&self) -> &dyn DisplayTrait;
// }
// ✅ 要这样
trait Trait {
fn get(&self) -> &dyn DisplayTrait + '_;
}
'_ 的含义: 生命周期省略,让编译器推断。
完整写法:
trait Trait {
fn get<'a>(&'a self) -> &'a dyn DisplayTrait;
}
Box 中的 trait 对象:
// 默认是 'static
let boxed: Box<dyn std::fmt::Display> = Box::new();
// 显式指定生命周期
let boxed: Box<dyn std::fmt::Display + 'static> = Box::new();
// 非 'static
fn make_ref<'a>(x: &'a i32) -> Box<dyn std::fmt::Display + 'a> {
Box::new(x)
}
语法: 'a: 'b 表示 'a 至少和 'b 一样长
fn constrained<'a, 'b>(x: &'a str, y: &'b str) -> &'b str
where
'a: 'b// 'a 至少和 'b 一样长
{
// 可以安全返回 x,因为 'a >= 'b
x
}
实际例子:
struct Ref<'a, T> {
reference: &'a T,
}
impl<'a, T> Ref<'a, T>
where
T: 'a// T 必须至少活 'a 这么久
{
fn new(reference: &'a T) -> Self {
Ref { reference }
}
}
为什么需要? 确保结构体里的引用不会比数据本身活得短。
// 返回第一个参数的引用
fn first<'a, 'b>(x: &'a str, y: &'b str) -> &'a str {
x
}
// 返回第二个参数的引用
fn second<'a, 'b>(x: &'a str, y: &'b str) -> &'b str {
y
}
// 返回生命周期较短的那个
fn shorter<'a, 'b>(x: &'a str, y: &'b str) -> &'a str
where
'b: 'a// 'b 至少和 'a 一样长,所以 'a 是较短的
{
x
}
fn main() {
let s1 = String::from("hello");
let s2 = String::from("world");
println!("{}", first(&s1, &s2));
println!("{}", second(&s1, &s2));
}
struct TextPair<'a, 'b> {
first: &'a str,
second: &'b str,
}
impl<'a, 'b> TextPair<'a, 'b> {
fn new(first: &'a str, second: &'b str) -> Self {
TextPair { first, second }
}
fn first(&self) -> &str {
self.first
}
fn second(&self) -> &str {
self.second
}
}
fn main() {
let s1 = String::from("hello");
let s2 = String::from("world");
let pair = TextPair::new(&s1, &s2);
println!("{} {}", pair.first(), pair.second());
}
// 返回 'static 引用
fn get_static_str() -> &'static str {
"I live forever!"
}
// 接受 'static 生命周期的函数
fn print_static(s: &'static str) {
println!("{}", s);
}
fn main() {
let s = get_static_str();
print_static(s);
// 字符串字面量
let literal: &'static str = "hello";
print_static(literal);
// 常量
const CONST: &str = "constant";
print_static(CONST);
}
use std::thread;
use std::time::Duration;
fn main() {
// ✅ 'static 数据可以移到线程
let s = "hello";
let handle = thread::spawn(move || {
println!("{}", s);
});
handle.join().unwrap();
// ✅ 也可以显式标注
fn spawn_thread<'a>(data: &'a str)
where
'a: 'static
{
thread::spawn(move || {
println!("{}", data);
});
}
spawn_thread("from function");
// ❌ 非 'static 不行
// let owned = String::from("world");
// let handle = thread::spawn(move || {
// println!("{}", &owned); // 编译错误
// });
}
use std::fmt::Display;
// trait 对象作为返回值
fn make_display<'a>(x: &'a i32) -> Box<dyn Display + 'a> {
Box::new(x)
}
// 结构体包含 trait 对象
struct Container<'a> {
data: Box<dyn Display + 'a>,
}
impl<'a> Container<'a> {
fn new(x: &'a i32) -> Self {
Container {
data: Box::new(x),
}
}
}
fn main() {
let num = ;
let display = make_display(&num);
println!("{}", display);
let container = Container::new(&num);
println!("{}", container.data);
}
// T 必须至少活 'a 这么久
struct Borrowed<'a, T: 'a> {
data: &'a T,
}
impl<'a, T: 'a> Borrowed<'a, T> {
fn new(data: &'a T) -> Self {
Borrowed { data }
}
fn get(&self) -> &'a T {
self.data
}
}
// 更复杂的约束
fn process<'a, 'b, T>(x: &'a T, y: &'b T) -> &'a T
where
'b: 'a, // 'b 至少和 'a 一样长
T: 'a + 'b, // T 必须同时满足 'a 和 'b
{
x
}
fn main() {
let num = ;
let borrowed = Borrowed::new(&num);
println!("{}", borrowed.get());
}
// ❌ 错误:返回局部变量的引用
fn returns_local() -> &str {
let s = String::from("hello");
&s // s 在函数结束时释放
}
// ✅ 正确:返回 owned String
fn returns_owned() -> String {
String::from("hello")
}
// ❌ 错误:生命周期不匹配
fn wrong_lifetime<'a>(x: &str, y: &str) -> &'a str {
// 返回的 'a 和 x、y 都没关系
y // 编译器不知道 y 能不能活 'a 这么久
}
// ✅ 正确:明确生命周期关系
fn correct_lifetime<'a>(x: &'a str, y: &'a str) -> &'a str {
y // x 和 y 都是 'a,可以返回
}
// ❌ 不需要标注
fn first(word: &str) -> &str {
&word[..]
}
// ✅ 编译器可以推断
// 不需要写成 fn first<'a>(word: &'a str) -> &'a str
// ❌ 'static 不是"永远",是"程序运行期间"
fn not_static(s: String) -> &'static str {
&s // 错误!s 在函数结束时释放
}
// ✅ 字面量才是 'static
fn is_static() -> &'static str {
"hello"
}
// ❌ 错误
// trait Trait {
// fn get(&self) -> &dyn Display;
// }
// ✅ 正确
trait Trait {
fn get(&self) -> &dyn Display + '_;
}
// 解析器返回输入字符串的切片
struct Parser<'a> {
input: &'a str,
position: usize,
}
impl<'a> Parser<'a> {
fn new(input: &'a str) -> Self {
Parser { input, position: }
}
fn peek(&self) -> Option<&'a str> {
if self.position < self.input.len() {
Some(&self.input[self.position..])
} else {
None
}
}
fn consume(&mut self, n: usize) -> Option<&'a str> {
let start = self.position;
self.position += n;
if self.position <= self.input.len() {
Some(&self.input[start..self.position])
} else {
None
}
}
}
fn main() {
let input = "hello world";
let mut parser = Parser::new(input);
println!("{:?}", parser.peek());
println!("{:?}", parser.consume());
println!("{:?}", parser.peek());
}
// 配置引用程序运行期间的数据
struct Config<'a> {
name: &'a str,
version: &'static str, // 版本是编译时确定的
data: std::collections::HashMap<&'a str, &'a str>,
}
impl<'a> Config<'a> {
fn new(name: &'a str) -> Self {
let mut data = std::collections::HashMap::new();
data.insert("env", "production");
Config {
name,
version: "1.0.0",
data,
}
}
fn get(&self, key: &str) -> Option<&'a str> {
self.data.get(key).copied()
}
}
fn main() {
let name = String::from("MyApp");
let config = Config::new(&name);
println!("配置:{}", config.name);
println!("版本:{}", config.version);
println!("环境:{:?}", config.get("env"));
}
// 自定义迭代器,返回引用的引用
struct WindowIter<'a, T> {
slice: &'a [T],
window_size: usize,
index: usize,
}
impl<'a, T> WindowIter<'a, T> {
fn new(slice: &'a [T], window_size: usize) -> Self {
WindowIter {
slice,
window_size,
index: ,
}
}
}
impl<'a, T> Iterator for WindowIter<'a, T> {
type Item = &'a [T];
fn next(&mut self) -> Option<Self::Item> {
if self.index + self.window_size <= self.slice.len() {
let window = &self.slice[self.index..self.index + self.window_size];
self.index += ;
Some(window)
} else {
None
}
}
}
fn main() {
let data = [, , , , ];
for window in WindowIter::new(&data, ) {
println!("{:?}", window);
}
// 输出:
// [1, 2, 3]
// [2, 3, 4]
// [3, 4, 5]
}

核心要点:
'_ 可以省略'a: 'b 表示 'a 至少和 'b 一样长生命周期标注原则:
下篇预告:
生命周期终于搞定了!接下来咱们来聊聊 Rust 的Attribute 与 Derive。属性语法怎么用?内置 derive 有哪些?能不能自定义 attribute?下篇一起探索 Rust 的元编程!
互动问题:
你觉得生命周期最难理解的是啥?有没有被 'static 坑过?评论区聊聊你的"血泪史"!