Rust 错误 E0106:missing lifetime specifier(缺少生命周期标注)
E0106 的意思是:你在函数的返回类型或结构体里写了一个引用(&),而 Rust 看不出它借用的是谁的数据。 这不是你的逻辑有 bug。编译器只是需要你多写一个词,或者稍微换个思路。几乎每个人在学 Rust 的第一周都会遇到这个错误,修复通常只要一行。(本页属于 Rust 编译错误详解系列。)
error[E0106]: missing lifetime specifier --> src/main.rs:1:32 |1 | fn longer(a: &str, b: &str) -> &str { | ---- ---- ^ expected named lifetime parameter | = help: this function's return type contains a borrowed value, but the signature does not say whether it is borrowed from `a` or `b`help: consider introducing a named lifetime parameter |1 | fn longer<'a>(a: &'a str, b: &'a str) -> &'a str { | ++++ ++ ++ ++产生这个错误的程序:
fn longer(a: &str, b: &str) -> &str { if a.len() >= b.len() { a } else { b }}
fn main() { println!("{}", longer("apple", "fig"));}编译器说:缺少生命周期标注,这里需要一个具名的生命周期参数(“expected named lifetime parameter”)。第一条 help 解释了原因:函数的返回类型里有一个借用的值,但函数签名没有说明它是从 a 借来的还是从 b 借来的。第二条 help 建议你引入一个具名的生命周期参数 'a。
这个错误是什么意思
Section titled “这个错误是什么意思”引用就是借用:它指向别人拥有的数据。每当函数返回一个引用,Rust 都需要知道它指向的是谁的数据,这样才能检查调用者使用结果时,那份数据还活着。
看第 1 行下面的三个标记。编译器用 ---- 标出了 a 和 b,用 ^ 标出了返回类型。它是在说:“你返回了一个借来的 &str,它可能来自 a,也可能来自 b。到底是哪个?”编译器不会去猜。
想想图书馆的借书证。朋友递给你一本书,说“这是借来的”,你会想知道是从谁那儿借的,这样才知道什么时候必须还回去。生命周期就是贴在借用上的那张标签。
Rust 为什么要这样做
Section titled “Rust 为什么要这样做”Rust 保证引用永远不会比它指向的数据活得更久(不会有“悬垂指针”)。为了在函数调用处检查这一点,编译器只看函数的签名,不看函数体。所以签名本身必须说明返回的引用来自哪里。
大多数时候你根本不用写生命周期,因为 Rust 会用三条简单的省略规则(elision rules)自动补上:
- 每个引用参数都有它自己的生命周期。
- 如果正好只有一个引用参数,返回的引用就借用自它。
- 如果是带
&self或&mut self的方法,返回的引用就借用自self。
当这几条规则都给不出答案时,就会出现 E0106:有两个或更多引用参数(而且没有 self)、一个引用参数都没有,或者引用存放在结构体里。生命周期这一课会深入讲解,借用则讲的是引用本身。
修复 1:加上一个具名的生命周期('a)
Section titled “修复 1:加上一个具名的生命周期('a)”当函数确实返回某个输入的一部分时,选这个方法。'a 只是一个名字。把它写在输入和输出上,意思是“结果和这些输入活得一样久”:
// 'a:返回的 &str 借用自 a 或 b,所以它和两者活得一样久。fn longer<'a>(a: &'a str, b: &'a str) -> &'a str { if a.len() >= b.len() { a } else { b }}
fn main() { let first = String::from("apple"); let second = String::from("fig"); println!("更长的单词:{}", longer(&first, &second));}输出:
更长的单词:apple这里的单词保留英文,是因为 len() 数的是字节:一个汉字占 3 个字节,换成中文比较的就不是字数了。如果结果只会来自一个参数,就只在那个参数上写 'a:fn lookup<'a>(map: &'a HashMap<String, String>, key: &str) -> Option<&'a str>。编译器的建议是给所有地方都加上 'a,这样也能用,但比实际需要的更严格。
修复 2:改为返回拥有所有权的 String
Section titled “修复 2:改为返回拥有所有权的 String”当函数要生成新的文本,或者你不想让调用者被一个借用绑住时,选这个方法。拥有所有权的 String 属于调用者,所以没有什么需要追踪的:
fn greeting(name: &str, lang: &str) -> String { if lang == "zh" { format!("你好,{name}!") } else { format!("Hello, {name}!") }}
fn main() { println!("{}", greeting("Ana", "zh")); println!("{}", greeting("Sam", "en"));}输出:
你好,Ana!Hello, Sam!这样做要付出一次小小的内存分配。对大多数程序来说完全没问题,不用为此感到内疚。
修复 3:在结构体里存放自有数据
Section titled “修复 3:在结构体里存放自有数据”像 title: &str 这样的结构体字段也会报同样的错:
error[E0106]: missing lifetime specifier --> src/main.rs:2:12 |2 | title: &str, | ^ expected named lifetime parameter对结构体来说,最好的修复几乎总是让它拥有数据:用 String 代替 &str,用 Vec<T> 代替 &[T]。这样结构体想活多久就活多久:
#[derive(Debug)]struct Book { title: String, // 拥有所有权,而不是 &str pages: u32,}
fn main() { let book = Book { title: String::from("Dune"), pages: 412 }; println!("{}({} 页)", book.title, book.pages);}输出:
Dune(412 页)只有当结构体是一个短暂存在的视图,指向存放在别处的数据时(比如一个查看大段文本的解析器),才给它加上生命周期。这时要在结构体和字段上都写 <'a>:
struct Excerpt<'a> { text: &'a str, // 借用自下面的小说}
fn main() { let novel = String::from("Call me Ishmael. Some years ago..."); let first = novel.split('.').next().unwrap(); let excerpt = Excerpt { text: first }; println!("摘录:{}", excerpt.text);}输出:
摘录:Call me Ishmael小说的原文保留英文,因为代码用英文句号 '.' 来切分句子。
修复 4:字符串字面量用 'static
Section titled “修复 4:字符串字面量用 'static”如果函数一个引用参数都没有,报错会说“there is no value for it to be borrowed from”(没有可以借用的值):
error[E0106]: missing lifetime specifier --> src/main.rs:1:22 |1 | fn default_name() -> &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如果你只返回 "OK" 这样的字符串字面量,就写 &'static str。字面量直接编进了程序里,所以在整个运行期间都有效:
fn status_text(code: u16) -> &'static str { match code { 200 => "OK", 404 => "Not Found", _ => "Unknown", }}
fn main() { println!("200 表示 {}", status_text(200)); println!("404 表示 {}", status_text(404));}输出:
200 表示 OK404 表示 Not Found'static 只用于真正的字面量和常量。如果文本是在运行时生成的(用 format!,或者从文件里读出来),'static 帮不上忙:请返回 String(修复 2)。
- 有两个输入的辅助函数,比如
fn lookup(map: &HashMap<String, String>, key: &str) -> Option<&str>。修复:用'a只把输出和map绑在一起(修复 1)。 - 存放文本的结构体,比如用解析出来的输入构建的
User { name: &str }。修复:使用String字段(修复 3)。 - 返回固定消息的函数,比如
fn error_label() -> &str。修复:&'static str(修复 4)。
方法是个好消息:多亏了规则 3,fn name(&self) -> &str 完全不需要写生命周期。
- E0382:borrow of moved value:另一个所有权错误,讲的是把值交出去之后又使用它。
- E0502:可变借用与不可变借用冲突:生命周期写好之后,借用检查器就在这里发挥作用。
- E0499:两个可变借用:同一时间对同一份数据有两个
&mut。 - E0308:mismatched types:比如函数声明返回
&str,你却返回了String。 - E0515:cannot return reference to local variable:你对函数内部创建的数据用了修复 1。请改为返回拥有所有权的
String(修复 2)。 - 所有编译错误详解
小测验
1. 为什么 fn first_word(s: &str) -> &str 不写任何生命周期也能编译通过?
2. 你的结构体 Config { path: &str } 报了 E0106。通常最好的修复是什么?
3. 一个函数用 format! 生成一条消息并返回它。返回类型应该是什么?
得分: 0 / 3
- E0106 — Rust error codes index(英文):这个错误的官方解释。
- 生命周期确保引用有效 — Rust 程序设计语言:生命周期的完整讲解,包括
longest示例和带引用的结构体。 - Lifetime elision — The Rust Reference(英文):Rust 自动补全生命周期时用的精确规则。
- 省略 — 通过例子学 Rust:什么时候可以省略生命周期的简短示例。
- 生命周期 — 通过例子学 Rust:在函数、方法和结构体中显式标注生命周期的可运行示例。