首页
学习
活动
专区
圈层
工具
发布
社区首页 >专栏 >26-Rust 教程 - 生命周期进阶

26-Rust 教程 - 生命周期进阶

作者头像
LarryLan
发布2026-05-29 13:08:40
发布2026-05-29 13:08:40
480
举报

生命周期进阶

多个生命周期、'static:生命周期终于能看懂了!

🎬 引入

还记得第一次看到生命周期注解时的感受吗?

代码语言:javascript
复制
fn longest<'a>(x: &'a str, y: &'a str) -> &'a str

我: "这 'a 是个啥?为啥到处都要写?"

Rust: "告诉编译器引用能活多久。"

我: "编译器自己不能推断吗?"

Rust: "有些情况可以,有些不行。"

今天咱们就来攻克生命周期的进阶内容:多个生命周期参数、'static 生命周期、trait 对象中的生命周期。

学完这篇,你再看生命周期注解,应该不会再有"这啥玩意儿"的感觉了。

📌 核心概念

多个生命周期参数

问题: 当函数有多个输入引用,但它们的生命周期不同时:

代码语言:javascript
复制
fn longest(x: &str, y: &str) -> &str {
    if x.len() > y.len() {
        x
    } else {
        y
    }
}

编译器: "等等,返回的引用是哪个的?x 还是 y?它们的生命周期可能不一样啊!"

解决: 用多个生命周期参数

代码语言:javascript
复制
fn longest<'a, 'b>(x: &'a str, y: &'b str) -> &'a str {
    // 返回的引用生命周期和 x 一样
    x  // 只能返回 x,不能返回 y
}

但这不合理啊! 有时候 y 更长,为什么不能返回 y?

答案: 生命周期注解是给调用者看的契约,不是给实现者看的。

代码语言:javascript
复制
fn longest<'a, 'b>(x: &'a str, y: &'b str) -> &'a str {
    // 这个函数签名说:"我返回的引用至少能活 'a 这么久"
    // 所以只能返回 x,因为 y 的生命周期是 'b,可能比 'a 短
}

正确的做法: 让返回值的生命周期和两个参数都相关

代码语言:javascript
复制
fn longest<'a>(x: &'a str, y: &'a str) -> &'a str {
    // 两个参数生命周期相同
    // 返回的引用也能活这么久
    if x.len() > y.len() {
        x
    } else {
        y
    }
}

生命周期省略规则

不是所有函数都要写生命周期注解。Rust 有三条省略规则

规则 1: 每个引用参数获得独立的生命周期

代码语言:javascript
复制
fn first(word: &str) -> &str {
    // 相当于 fn first<'a>(word: &'a str) -> &str
    // 但返回值没有标注,编译器不知道返回哪个的生命周期
}

规则 2: 如果只有一个输入生命周期,它赋给所有输出引用

代码语言:javascript
复制
fn first<'a>(word: &'a str) -> &'a str {
    // ✅ 可以省略
    // 编译器自动推断
}

规则 3: 如果有 &self&mut selfself 的生命周期赋给所有输出引用

代码语言:javascript
复制
impl<'a> MyStruct<'a> {
    fn get_str(&self) -> &str {
        // ✅ 可以省略
        // 相当于 fn get_str<'a>(&'a self) -> &'a str
    }
}

什么时候必须写?

  • 多个输入引用,返回值是引用
  • 结构体字段有引用
  • trait 定义中的引用

'static 生命周期

'static:表示引用可以活在整个程序运行期间。

哪些是 'static

  1. 字符串字面量
  2. 常量
  3. 静态变量
代码语言:javascript
复制
let s: &'static str = "hello";  // 字面量存在代码段,程序结束才释放

const NAME: &str = "Larry";  // 常量也是 'static

static VERSION: &str = "1.0.0";  // 静态变量

'static 作为约束:

代码语言:javascript
复制
fn print_it<T: 'static>(value: T) {
    // T 必须是 'static 的
    // 意味着 T 不能包含任何非 'static 的引用
}

常见场景:

  • 线程(线程可能比创建它的栈帧活得久)
  • 全局缓存
  • 配置数据
代码语言:javascript
复制
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 对象也需要生命周期注解

代码语言:javascript
复制
trait DisplayTrait: std::fmt::Display {}

// ❌ 这样不行
// trait Trait {
//     fn get(&self) -> &dyn DisplayTrait;
// }

// ✅ 要这样
trait Trait {
    fn get(&self) -> &dyn DisplayTrait + '_;
}

'_ 的含义: 生命周期省略,让编译器推断。

完整写法:

代码语言:javascript
复制
trait Trait {
    fn get<'a>(&'a self) -> &'a dyn DisplayTrait;
}

Box 中的 trait 对象:

代码语言:javascript
复制
// 默认是 '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 一样长

代码语言:javascript
复制
fn constrained<'a, 'b>(x: &'a str, y: &'b str) -> &'b str
where
    'a: 'b// 'a 至少和 'b 一样长
{
    // 可以安全返回 x,因为 'a >= 'b
    x
}

实际例子:

代码语言:javascript
复制
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 }
    }
}

为什么需要? 确保结构体里的引用不会比数据本身活得短。

💻 代码示例

示例 1:多个生命周期参数

代码语言:javascript
复制
// 返回第一个参数的引用
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));
}

示例 2:结构体中的多个生命周期

代码语言:javascript
复制
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());
}

示例 3:'static 生命周期

代码语言:javascript
复制
// 返回 '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);
}

示例 4:线程中的 'static

代码语言:javascript
复制
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);  // 编译错误
    // });
}

示例 5:trait 对象的生命周期

代码语言:javascript
复制
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);
}

示例 6:生命周期子类型约束

代码语言:javascript
复制
// 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());
}

错误示例:生命周期冲突

代码语言:javascript
复制
// ❌ 错误:返回局部变量的引用
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,可以返回
}

🐛 常见坑点

坑点 1:过度标注生命周期

代码语言:javascript
复制
// ❌ 不需要标注
fn first(word: &str) -> &str {
    &word[..]
}

// ✅ 编译器可以推断
// 不需要写成 fn first<'a>(word: &'a str) -> &'a str

坑点 2:'static 误解

代码语言:javascript
复制
// ❌ 'static 不是"永远",是"程序运行期间"
fn not_static(s: String) -> &'static str {
    &s  // 错误!s 在函数结束时释放
}

// ✅ 字面量才是 'static
fn is_static() -> &'static str {
    "hello"
}

坑点 3:trait 对象忘记生命周期

代码语言:javascript
复制
// ❌ 错误
// trait Trait {
//     fn get(&self) -> &dyn Display;
// }

// ✅ 正确
trait Trait {
    fn get(&self) -> &dyn Display + '_;
}

🎯 实战案例

案例 1:解析器中的生命周期

代码语言:javascript
复制
// 解析器返回输入字符串的切片
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());
}

案例 2:配置管理

代码语言:javascript
复制
// 配置引用程序运行期间的数据
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"));
}

案例 3:迭代器适配器

代码语言:javascript
复制
// 自定义迭代器,返回引用的引用
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]
}

🧠 思维导图

26-生命周期进阶
26-生命周期进阶

📝 小结

核心要点:

  1. 多个生命周期:不同输入引用可能需要不同生命周期参数
  2. 省略规则:三种情况可以省略,其他时候必须标注
  3. 'static:程序运行期间,字面量和常量是 'static
  4. trait 对象:需要生命周期注解,'_ 可以省略
  5. 子类型约束'a: 'b 表示 'a 至少和 'b 一样长

生命周期标注原则:

  • 能省略就省略
  • 必须标就标清楚
  • 多个引用考虑是否需要不同生命周期
  • trait 对象别忘记加生命周期

下篇预告:

生命周期终于搞定了!接下来咱们来聊聊 Rust 的Attribute 与 Derive。属性语法怎么用?内置 derive 有哪些?能不能自定义 attribute?下篇一起探索 Rust 的元编程!

互动问题:

你觉得生命周期最难理解的是啥?有没有被 'static 坑过?评论区聊聊你的"血泪史"!

🔗 参考资料

  • Rust Book - Lifetimes
  • Lifetime Elision
  • 'static Lifetime
本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2026-05-25,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 Larry的Hub 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 生命周期进阶
    • 🎬 引入
    • 📌 核心概念
      • 多个生命周期参数
      • 生命周期省略规则
      • 'static 生命周期
      • trait 对象中的生命周期
      • 生命周期子类型约束
    • 💻 代码示例
      • 示例 1:多个生命周期参数
      • 示例 2:结构体中的多个生命周期
      • 示例 3:'static 生命周期
      • 示例 4:线程中的 'static
      • 示例 5:trait 对象的生命周期
      • 示例 6:生命周期子类型约束
      • 错误示例:生命周期冲突
    • 🐛 常见坑点
      • 坑点 1:过度标注生命周期
      • 坑点 2:'static 误解
      • 坑点 3:trait 对象忘记生命周期
    • 🎯 实战案例
      • 案例 1:解析器中的生命周期
      • 案例 2:配置管理
      • 案例 3:迭代器适配器
    • 🧠 思维导图
    • 📝 小结
    • 🔗 参考资料
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档