C 开发者第一次看到 lifetime,很容易以为它是在给对象标注“活多久”。
这个理解不准确。
Rust 的 lifetime 不是运行时计时器,也不是对象真实存活时间的手写说明。它更像编译器用来检查引用关系的一套静态约束:一个引用不能比它指向的数据活得更久。
这件事在 C 里也一直存在,只是靠人脑判断。
C 里的悬垂指针
最典型的例子是返回局部变量地址:
const char *bad_name(void)
{
char buf[32] = "sensor";
return buf;
}
buf 在函数返回时已经不存在。返回出去的指针指向一段已经失效的栈空间。
类似问题还包括:
- 返回局部数组地址
- 保存调用方临时 buffer 的指针
free后继续访问旧指针realloc后继续使用旧地址- 把指向短生命周期对象的指针放进长生命周期结构体
C 里这些问题不一定马上崩。它可能在另一次函数调用、一次栈覆盖、一次堆分配之后才表现出来。
Rust 的 lifetime 要阻止的,就是这类引用关系错误。
Rust 会拒绝返回局部引用
Rust 里类似代码无法通过编译:
fn bad_name() -> &str {
let name = String::from("sensor");
&name
}
问题不是 String 本身,也不是 &str 本身,而是这个引用指向 name 的内部数据。函数结束时,name 会释放,返回的引用就会悬空。
编译器看见的是这条关系:
返回值引用 -> 指向局部变量 name
name -> 函数结束时释放
返回值 -> 函数外继续使用
这条关系不成立,所以不能编译。
这就是 lifetime 的第一层意义:检查引用指向的数据是否还活着。
大多数 lifetime 不需要手写
很多函数不需要写 lifetime 标注:
fn first_byte(data: &[u8]) -> Option<u8> {
data.first().copied()
}
这里返回的是 u8,不是引用。函数内部借用了 data,但没有把引用返回出去。
再看这个:
fn trim_name(name: &str) -> &str {
name.trim()
}
它返回的 &str 来自输入 name。编译器能推断:返回值的有效范围不能超过输入引用。
所以不需要写成:
fn trim_name<'a>(name: &'a str) -> &'a str {
name.trim()
}
这两个签名表达的是同一件事。显式写 lifetime 只是把编译器能推断的关系写出来。
标注 lifetime 是在说明引用关系
什么时候需要写 lifetime?通常是函数签名里有多个引用,编译器无法只靠规则判断返回值和哪个输入有关。
例如:
fn longer<'a>(a: &'a str, b: &'a str) -> &'a str {
if a.len() >= b.len() {
a
} else {
b
}
}
'a 不是说 a 和 b 真实活得一样久。它表达的是一种约束:
返回的引用,要在 a 和 b 都仍然有效的范围内使用
换句话说,返回值可能来自 a,也可能来自 b,所以调用方拿到的引用不能比其中较短的那个输入活得更久。
这和 C 里的文档说明很像:
/* returns a pointer into either a or b; caller must keep both alive */
const char *longer(const char *a, const char *b);
Rust 把这条文档约定写进了类型。
结构体里保存引用也要说明关系
C 里结构体保存外部指针很常见:
struct View {
const uint8_t *data;
size_t len;
};
这个结构体不拥有数据,只是一个 view。问题是:data 指向的 buffer 必须比 View 活得更久。
Rust 里如果结构体保存引用,需要 lifetime 参数:
struct View<'a> {
data: &'a [u8],
}
'a 表达的是:
View 不能比它借用的 data 活得更久
它不是让 View 拥有数据。如果你希望结构体拥有数据,就应该放 Vec<u8>:
struct OwnedBuffer {
data: Vec<u8>,
}
这里的 API 设计边界很重要:
保存借用:struct View<'a> { data: &'a [u8] }
拥有数据:struct OwnedBuffer { data: Vec<u8> }
如果不想让调用方处理 lifetime,很多时候可以让结构体拥有数据,而不是保存引用。
‘static 不是“永远不释放”的万能标记
'static 容易被误解。
字符串字面量有 'static 生命周期:
let name: &'static str = "sensor";
因为这段字符串数据放在程序的静态区域,可以活到程序结束。
但不要把 'static 当成“修复 lifetime 错误”的万能标记。函数不能把局部变量引用强行标成 'static:
fn bad() -> &'static str {
let s = String::from("sensor");
&s
}
这仍然不成立。局部 s 函数结束就释放,引用不可能变成 'static。
看到 lifetime 错误时,正确问题通常不是“怎么加标注”,而是:
这个引用指向的数据,到底由谁拥有?
返回值或结构体要不要拥有数据?
这个引用是否被保存得太久?
这一篇先记住什么
C 和 Rust 的第一层对应关系是:
返回局部变量地址 -> Rust 拒绝返回悬垂引用
指针指向调用方 buffer -> 引用不能超过调用方数据
结构体保存外部指针 -> struct View<'a>
结构体拥有自己的 buffer -> struct Owned { data: Vec<u8> }
文档写“caller keeps alive” -> lifetime 写进类型关系
lifetime 不是对象真实存活时间,也不是给变量手写寿命。
它表达的是引用关系:一个引用能在多大范围内被使用,取决于它指向的数据能活多久。
下一篇可以进入 enum 和模式匹配。那会从 C 里的整数状态码、宏常量和 switch,走到 Rust 用类型表达状态和错误分支。