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

10-Rust 教程 - 生命周期基础

作者头像
LarryLan
发布2026-04-13 14:59:14
发布2026-04-13 14:59:14
540
举报

生命周期基础

生命周期:引用的"保质期",过期无效


🎬 引入

如果你写过返回引用的函数,可能遇到过这个错误:

代码语言:javascript
复制
fn get_first(s: &String) -> &str {
    &s[..]
}

编译器:"缺少生命周期说明符!" :"啥周期?我就是要返回个切片啊!" 编译器:"不说清楚这个引用能活多久,我不放心!"

这就是 Rust 的**生命周期(Lifetime)**系统。听起来很高大上,其实就是编译器在问:"你这个引用能保证多久有效?别给我返回个'过期引用'!"

今天咱们就来搞懂生命周期的基础概念,让你不再被编译器的"生命周期"警告吓到。


📌 核心概念

生命周期是什么?

生命周期是引用的有效作用域。它告诉编译器:"这个引用至少能活这么久"。

生活化类比

  • 生命周期 = 食品的保质期
  • 引用 = 食品
  • 超过保质期 = 悬垂引用(不能吃!)
代码语言:javascript
复制
{
    let r;                // ---------+-- 'a
                          //          |
    {                     //          |
        let x = ;        // -+-- 'b  |
        r = &x;           //  |       |
    }                     // -+       |
                          //          |
    println!("{}", r);    //          |
}                         // ---------+

这里 'b'a 短,x 在内层作用域结束时就没了,但 r 还想用它——这就是悬垂引用,编译器会阻止。

为什么需要生命周期?

编译器需要知道引用的有效期,以防止悬垂引用

代码语言:javascript
复制
fn dangle() -> &String {
    let s = String::from("hello");
    &s  // s 在函数结束时被 drop,返回的引用指向无效内存
}

编译器错误

代码语言:javascript
复制
error[E0106]: missing lifetime specifier
 --> src/main.rs:1:16
  |
1 | fn dangle() -> &String {
  |                ^ expected named lifetime parameter

人话翻译:编译器:"你返回的引用指向谁?不说清楚生命周期,我怎么知道它会不会变成悬垂引用?"

生命周期注解语法

生命周期注解用单引号 + 字母表示,如 'a'b'static

代码语言:javascript
复制
// 基本语法
&'a i32// 引用,生命周期 'a
&'a mut i32// 可变引用
&'a str// 字符串切片

重点:生命周期注解是给编译器看的,不是给运行时的。它不改变程序行为,只帮助编译器检查。

函数中的生命周期

当函数有多个引用参数和返回引用时,需要标注生命周期:

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

解读

  • 'a 是生命周期参数(类似泛型)
  • xy 的生命周期都是 'a
  • 返回的引用生命周期也是 'a

含义:返回的引用至少和 xy较短的那个一样长。

生命周期省略规则(Elision Rules)

好消息:大多数情况不需要写生命周期!编译器有三条省略规则:

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

代码语言:javascript
复制
fn first_word(s: &str) -> &str { ... }
// 编译器推断为:
fn first_word<'a>(s: &'a str) -> &'a str { ... }

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

代码语言:javascript
复制
fn longest(x: &str, y: &str) -> &str { ... }
// 编译器无法推断!两个输入生命周期,不知道返回哪个

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

代码语言:javascript
复制
impl<'a> MyStruct<'a> {
    fn get_str(&self) -> &str { ... }
    // 自动推断,不需要写生命周期
}

'static 生命周期

'static 表示跟程序一样长的生命周期。

代码语言:javascript
复制
let s: &'static str = "hello";  // 字符串字面量

字符串字面量存在二进制文件里,程序结束才释放,所以是 'static

结构体中的生命周期

如果结构体包含引用字段,需要标注生命周期:

代码语言:javascript
复制
struct ImportantExcerpt<'a> {
    part: &'a str,
}

fn main() {
    let novel = String::from("Call me Ishmael. Some years ago...");
    let first_sentence = novel.split('.').next().unwrap();
    
    let excerpt = ImportantExcerpt {
        part: first_sentence,
    };
    
    println!("摘录:{}", excerpt.part);
}

含义ImportantExcerpt 实例不能比它引用的 novel 活得更久。


💻 代码示例

基础示例:带生命周期的函数

代码语言:javascript
复制
// 返回两个字符串中较长的那个
fn longest<'a>(x: &'a str, y: &'a str) -> &'a str {
    if x.len() > y.len() {
        x
    } else {
        y
    }
}

fn main() {
    let s1 = String::from("hello");
    let s2 = String::from("world!");
    
    let result = longest(&s1, &s2);
    println!("较长的是:{}", result);
}

错误示例 1:返回局部变量的引用

代码语言:javascript
复制
fn get_invalid() -> &str {
    let s = String::from("hello");
    &s  // ❌ 错误!s 在函数结束时被 drop
}

编译器错误

代码语言:javascript
复制
error[E0106]: missing lifetime specifier
 --> src/main.rs:1:18
  |
1 | fn get_invalid() -> &str {
  |                     ^ expected named lifetime parameter
  |
  = help: this function's return type contains a borrowed value, 
          but there is no value for it to be borrowed from

人话翻译:编译器:"你返回的引用指向谁?s 是局部变量,函数结束就没了。你想返回个'过期引用'?没门!"

修复:返回 String 而不是引用。

代码语言:javascript
复制
fn get_valid() -> String {
    let s = String::from("hello");
    s  // 返回所有权
}

错误示例 2:生命周期不匹配

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

fn main() {
    let s1 = String::from("hello");
    let result;
    
    {
        let s2 = String::from("world!");
        result = longest(&s1, &s2);  // s2 生命周期短
    }  // s2 在这里结束
    
    println!("{}", result);  // ❌ 错误!result 可能引用已释放的 s2
}

编译器错误

代码语言:javascript
复制
error[E0597]: `s2` does not live long enough
  --> src/main.rs:12:32
   |
11 |         let s2 = String::from("world!");
   |             -- binding `s2` declared here
12 |         result = longest(&s1, &s2);
   |                                ^^ borrowed value does not live long enough
13 |     }
   |     - `s2` dropped here while still borrowed

人话翻译:编译器:"result 的生命周期跟 s2 绑定,但 s2 在内层作用域就结束了。你想让 result 引用一个已经释放的值?不行!"

修复:让 s2 活得跟 result 一样久。

代码语言:javascript
复制
fn main() {
    let s1 = String::from("hello");
    let s2 = String::from("world!");
    let result = longest(&s1, &s2);
    
    println!("{}", result);
}

错误示例 3:结构体生命周期错误

代码语言:javascript
复制
struct Excerpt {
    part: &str,  // ❌ 错误!缺少生命周期
}

编译器错误

代码语言:javascript
复制
error[E0106]: missing lifetime specifier
 --> src/main.rs:2:11
  |
2 |     part: &str,
  |           ^ expected named lifetime parameter
  |
help: consider introducing a named lifetime parameter
  |
1 | struct Excerpt<'a> {
2 |     part: &'a str,

修复

代码语言:javascript
复制
struct Excerpt<'a> {
    part: &'a str,
}

🐛 常见坑点

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

代码语言:javascript
复制
// 不需要!编译器能推断
fn first_word(s: &str) -> &str {
    let bytes = s.as_bytes();
    for (i, &item) in bytes.iter().enumerate() {
        if item == b' ' {
            return &s[..i];
        }
    }
    &s[..]
}

原则:先不写,编译器报错再加。

坑点 2:混淆生命周期和借用

代码语言:javascript
复制
fn get_ref(x: &i32) -> &i32 {
    x  // 不需要生命周期注解
}

单个引用参数,编译器能推断。

坑点 3:'static 滥用

代码语言:javascript
复制
fn process(s: &'static str) { ... }

除非你真的需要 'static(比如全局常量),否则用普通生命周期。

代码语言:javascript
复制
fn process(s: &str) { ... }  // 更灵活

坑点 4:结构体生命周期想当然

代码语言:javascript
复制
struct Data<'a> {
    value: &'a str,
}

impl<'a> Data<'a> {
    fn new(value: &str) -> Data {  // ❌ 错误!缺少生命周期
        Data { value }
    }
}

修复

代码语言:javascript
复制
impl<'a> Data<'a> {
    fn new(value: &'a str) -> Data<'a> {
        Data { value }
    }
}

🎯 实战案例

案例 1:带生命周期的结构体

代码语言:javascript
复制
#[derive(Debug)]
struct HighlightedText<'a> {
    text: &'a str,
    start: usize,
    end: usize,
}

impl<'a> HighlightedText<'a> {
    fn new(text: &'a str, start: usize, end: usize) -> Self {
        HighlightedText { text, start, end }
    }
    
    fn highlighted(&self) -> &str {
        &self.text[self.start..self.end]
    }
}

fn main() {
    let text = String::from("Hello, Rust!");
    let highlighted = HighlightedText::new(&text, , );
    
    println!("原文:{}", highlighted.text);
    println!("高亮:{}", highlighted.highlighted());
}

案例 2:生命周期和泛型组合

代码语言:javascript
复制
struct Pair<'a, T> {
    x: &'a T,
    y: &'a T,
}

impl<'a, T> Pair<'a, T> {
    fn new(x: &'a T, y: &'a T) -> Self {
        Pair { x, y }
    }
}

fn main() {
    let a = ;
    let b = ;
    let pair = Pair::new(&a, &b);
    
    println!("x={}, y={}", pair.x, pair.y);
}

案例 3:方法中的生命周期省略

代码语言:javascript
复制
struct TextProcessor {
    text: String,
}

impl TextProcessor {
    fn first_word(&self) -> &str {
        self.text.split_whitespace().next().unwrap_or("")
    }
    
    fn as_str(&self) -> &str {
        &self.text
    }
}

fn main() {
    let processor = TextProcessor {
        text: String::from("Hello, World!"),
    };
    
    println!("第一个单词:{}", processor.first_word());
    println!("全文:{}", processor.as_str());
}

注意:方法里 &self 的生命周期自动赋给返回值,不需要写。


🧠 思维导图

10-生命周期基础
10-生命周期基础

📝 小结

  1. 生命周期是引用的有效期,编译器用来防止悬垂引用
  2. 函数返回引用时,多个输入参数需要标注生命周期
  3. 省略规则:单参数或 &self 时编译器能自动推断
  4. 'static 表示程序级生命周期,字符串字面量是 'static
  5. 结构体有引用字段必须标注impl 块要匹配

🎉 恭喜!入门基础 10 篇完成!

你已经掌握了 Rust 的核心基础:所有权、变量、函数、模块、结构体、枚举、借用、字符串、生命周期。接下来可以进入核心概念篇,学习集合、错误处理、泛型、Trait 等进阶内容!

下篇预告:核心概念篇第 11 篇——集合类型,聊聊 VecHashMap 这些实用数据结构!


🔗 参考资料

  • Rust Book - 生命周期
  • Rust By Example - 生命周期
本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2026-04-04,如有侵权请联系 cloudcommunity@tencent.com 删除

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

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

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

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 生命周期基础
    • 🎬 引入
    • 📌 核心概念
      • 生命周期是什么?
      • 为什么需要生命周期?
      • 生命周期注解语法
      • 函数中的生命周期
      • 生命周期省略规则(Elision Rules)
      • 'static 生命周期
      • 结构体中的生命周期
    • 💻 代码示例
      • 基础示例:带生命周期的函数
      • 错误示例 1:返回局部变量的引用
      • 错误示例 2:生命周期不匹配
      • 错误示例 3:结构体生命周期错误
    • 🐛 常见坑点
      • 坑点 1:过度标注生命周期
      • 坑点 2:混淆生命周期和借用
      • 坑点 3:'static 滥用
      • 坑点 4:结构体生命周期想当然
    • 🎯 实战案例
      • 案例 1:带生命周期的结构体
      • 案例 2:生命周期和泛型组合
      • 案例 3:方法中的生命周期省略
    • 🧠 思维导图
    • 📝 小结
    • 🔗 参考资料
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档