泛型、trait 与生命周期:三件套¶
这一章讲什么: 三个互相咬合的机制。泛型(generic):让一段代码对多种类型生效; trait:定义"一组可共享的行为",并约束泛型"必须会干什么";生命周期(lifetime): 给编译器提供引用之间关系的信息,让它能验证"返回值不会比入参先死"。 这是全书理论密度最高的一章,也是之前所有谜团的结账处: 第 5 章结构体里的 E0106、第 9 章
?的错误转换,答案都在这里。
1. 顶层全景:一根藤上的三个葫芦¶
主线例子:largest(找切片里最大元素)函数的三次进化。
第 1 步:两个具体函数 largest_i32 / largest_char —— 函数体一模一样,只是类型不同
第 2 步:合成 fn largest<T>(list: &[T]) -> &T —— 泛型抽象
↳ 编译错:`>` 不能用在 &T 上 → 需要约束"T 可比较"
第 3 步:加 trait bound T: PartialOrd —— 抽象有了行为担保,编译通过
平行的另一条线(生命周期):
fn longest(x: &str, y: &str) -> &str —— 编译错:返回值借自 x 还是 y?
fn longest<'a>(x: &'a str, y: &'a str) -> &'a str —— 把三者的寿命拴在一起
图说:泛型解决"代码重复",trait 解决"抽象太宽",生命周期解决"引用关系说不清"。
2. 泛型:抽象类型,零运行时代价¶
套路和第 3 章提取函数完全一样:找重复 → 提取成带参数的定义 → 调用点换成调用。
只是这次参数化的不是值,是类型:类型参数声明在函数名后的尖括号里,惯例单大写字母,T(type)最常见1。
Point<T> 结构体同理:x、y 都是 T——同一个 T,所以 x 是整数时 y 不能是浮点(E0308);要不同就加参数 Point<T, U>。参数多于几个就该考虑拆类型了2。枚举早见过:Option<T>、Result<T, E> 就是泛型枚举3。impl 块也要声明 impl<T> Point<T>,还可以只给具体类型实现:impl Point<f32> 里的方法只属于 Point<f32>,其他实例没有4。
关键承诺:泛型没有运行时开销。 靠的是单态化(monomorphization):编译器把泛型代码按调用处的具体类型展开成专门版本——Some(5) 和 Some(5.0) 在编译产物里就是两个不同的枚举(Option_i32 / Option_f64)。运行起来和你手写重复代码一样快;代价是编译产物变大5。
3. trait:共享行为的定义¶
trait 把一组方法签名收拢成一个"行为契约";类型实现(impl)这个 trait,就承诺提供这些方法。 它像其他语言的接口,但不完全一样6。
pub trait Summary {
fn summarize(&self) -> String;
}
impl Summary for NewsArticle {
fn summarize(&self) -> String {
format!("{}, by {} ({})", self.headline, self.author, self.location)
}
}
trait 里的方法只有签名加分号;实现时 impl 行为 for 类型,在块里填方法体。调用方要把 trait 也引进作用域(use aggregator::{SocialPost, Summary};)才能调用 trait 方法7。
孤儿规则(orphan rule):trait 和类型至少有一个是本地(crate 内)的,才允许写 impl。 可以在本地类型上实现外部 trait(Display for SocialPost),也可以在外部类型上实现本地 trait(Summary for Vec<T>),但不能给外部类型实现外部 trait(Display for Vec<T> 不行)。理由:否则两个 crate 可以给同一类型实现同一 trait,编译器无法取舍——这条一致性(coherence)规则保证"别人的代码不会悄悄弄坏你的代码"8。
默认实现:trait 方法可以带方法体作为默认值;实现者可以留空吃默认(impl Summary for NewsArticle {} 输出 "(Read more...)"),也可以覆盖。默认实现还能调用同 trait 的其他方法——经典配方:一个必实现的小方法(summarize_author)+ 一个基于它的默认大方法(summarize)。一个小限制:覆盖实现里不能回头调用默认实现9。
4. trait 的三种用法¶
① 约束参数(写法的起点):
pub fn notify(item: &impl Summary) { ... } // impl Trait 语法糖
pub fn notify<T: Summary>(item: &T) { ... } // trait bound 完整形式
两者等价。区别在多个参数:两个 &impl Summary 允许传不同类型;<T: Summary> 强制两个参数同型10。多个 bound 用 +:T: Summary + Display11。bound 太多签名会挤爆,挪到 where 子句:
② 约束返回值:fn returns_summarizable() -> impl Summary——只承诺"返回某实现了 Summary 的类型",调用方不必知道具体类型。对闭包和迭代器(第 13 章)尤其有用,因为它们生成的类型只有编译器说得清。限制:只能返回单一类型——想按条件返回 NewsArticle 或 SocialPost 不行,那要 trait 对象(第 18 章)12。
③ 有条件地实现(impl 带 bound):
impl<T> Pair<T> { fn new(...) } // 所有 Pair<T> 都有 new
impl<T: Display + PartialOrd> Pair<T> { fn cmp_display(...) } // 只有 T 可比较可打印才有
再进一步是 blanket implementation(铺毯式实现):impl<T: Display> ToString for T {}——标准库用一行给"所有实现了 Display 的类型"自动配上 to_string。所以 3.to_string() 能用。文档里每个 trait 的 "Implementors" 一节能看到这些13。
这一节的设计落点:动态语言里"对一个没这方法的类型调方法"是运行时才炸;Rust 把这类错提到编译期,运行时不需要再检查——灵活性(泛型)和安全(检查)两头都要到了14。
5. 生命周期:泛型的"时间"维度¶
每个引用都有一个生命周期(它有效的范围),大多数时候编译器自己推断,你无感。
只在"引用的关系可能有多种解释"时,才必须显式标注——语法上和泛型同构:生命周期也是泛型参数,只是名字以 ' 开头('a)15。
5.1 借用检查器在看什么¶
let r; // 'a(外层)
{
let x = 5; // 'b(内层)
r = &x;
}
println!("{r}"); // E0597:`x` does not live long enough
借用检查器(borrow checker)比较各引用的作用域:被引用的数据('b)必须活得比引用('a)长。 这里 'b 嵌在 'a 里,反了,拒编。修正版让 x 先于 r 声明、后于 r 死亡,就合法16。
5.2 主线例子:longest 为什么要 'a¶
fn longest(x: &str, y: &str) -> &str { // E0106:missing lifetime specifier
if x.len() > y.len() { x } else { y }
}
报错的核心:"返回类型里含借来的值,但签名没说它借自 x 还是 y。" 确实不知道——if 返回 x,else 返回 y;而调用处传进来的具体寿命也无从预知。修法:给三者拴同一个参数:
读法:存在某个寿命 'a,两个入参都至少活到 'a,返回值也至少活到 'a。 实际效果:调用时 'a 被代入两个实参寿命的交集(较短者)——返回值的有效期跟着短的那个走17。
一个常引用的后续例子(Listing 10-23):string1 活全程,string2 只活内层;即使人眼看 result 实际指向更长的 string1,借用检查器按签名只能判"result 受较短寿命限制"——println! 放在内层外,拒编。教训:标注是契约,不是推理——编译器按契约保守行事18。
标注的边界:① y 与返回值无关时不必标(fn longest<'a>(x: &'a str, y: &str) -> &'a str);② 返回值若引用的不是任何入参,必是函数内的局部变量——那必是悬垂引用,无解,正确修法是返回 owned 值。生命周期的全部语法就是在"连接"参数与返回值的寿命;连上之后,安全的放行、不安全的拒编19。
5.3 结构体里的生命周期¶
含义:实例不能比它 part 字段里的引用活得更久——这正是第 5 章 E0106 的完整答案20。
5.4 生命周期省略:三条规则¶
fn first_word(s: &str) -> &str 没标也编译过,为什么?因为有生命周期省略(elision)规则——编译器内置的三条确定性推断,是 1.0 之前"程序员重复劳动太多"沉淀下来的历史补丁21:
- 每个引用参数各分一个独立寿命(
fn foo(x: &i32, y: &i32)→<'a, 'b>); - 只有一个输入寿命时,输出全部继承它(
fn foo(x: &i32) -> &i32→ 返回值也是 &'a); - 方法(&self/&mut self)时,输出的寿命全部取自 self。
对 longest 套规则:规则一给 x、y 各一个寿命;规则二不适用(输入多于一个);规则三不适用(不是方法)——走完全部规则仍剩未定的输出寿命,编译器不猜,报错让你标22。方法签名因为规则三,几乎不用标。
5.5 'static¶
'static 表示"能活整个程序运行期";所有字符串字面量都是 &'static str(文本编在二进制里,永远可用)。看到报错建议加 'static 时先别加——多半说明你真正的问题是悬垂引用或寿命不匹配,该修问题而不是贴胶布23。
6. 合体:泛型 + trait bound + 生命周期¶
fn longest_with_an_announcement<'a, T>(
x: &'a str,
y: &'a str,
ann: T,
) -> &'a str
where
T: Display,
{ ... }
三种参数共用一个尖括号列表:生命周期 'a、类型 T 声明在一起,bound 放 where。语法复杂度的每一次增加,都对应一次表达能力的实在扩展——这是本章所有记法的统一辩护理由24。
7. 作者的判断与证据¶
- "泛型零运行时成本(单态化)"——机制事实,代价(二进制膨胀)也明写5。
- "孤儿规则保证别人的代码不弄坏你的"——设计论证8。
- "生命周期只写在签名上、成了契约,所以编译器报错能指得更准"——设计决策的辩护,很值得注意:它解释了为什么 Rust 选择了"少推断、多显式"19。
- elision 三规则——历史事实,且明说未来可能加更多规则21。
判断(我们的,不是书里的): 把三件套翻译回第 4 章的那句话: 泛型让你写"对一切类型成立"的代码,trait 让"一切类型"缩到"行为达标的类型", 生命周期让"成立"延伸到时间轴上。 三者都是"把运行时信息搬到编译期"的同一种努力, 只是搬的维度不同(类型 / 行为 / 时间)。记住这个统一视角,本章的几十条规则就有了主干。 如果错,会错在: 这个视角解释不了 trait 对象(第 18 章)——那里故意把行为检查推迟到 运行时,换表达能力;所以"全部在编译期"只是 safe Rust 的默认,不是铁律。
8. 边界与局限¶
- lifetime 只覆盖常见场景;"非常高级的标注"整段指给 Rust Reference。
impl Trait只能返回单类型的限制,解法(trait object)要等第 18 章。- 单态化的二进制体积代价一笔带过,没有量化。
- 关联类型(associated type)——trait 语法里和泛型并列的另一种抽象——本章没讲,第 20 章补。
9. 可带走的¶
- 泛型 = 类型参数化;函数/结构体/枚举/impl 都能用;单态化保证零运行时开销。
- 泛型函数体里对 T 做任何操作,都要 trait bound 担保(T: PartialOrd 才能比大小)。
- trait 是行为契约;
impl Trait for Type;调用方要把 trait 引进作用域。 - 孤儿规则:trait 或类型至少一个本地,才许写 impl。
- 默认实现可覆盖;小方法必实现 + 大方法默认实现,是 trait 设计的常用配方。
&impl Summary与<T: Summary>等价;多参数要同型时只能用后者;bound 多了用 where。- 返回
impl Trait只能单一类型;要多样返回走 trait 对象。 - blanket impl(
impl<T: Display> ToString for T)给一族类型批量配行为。 - 生命周期是泛型;标注不改变寿命,只声明关系;借用检查器按契约保守验证。
- 返回值引用的寿命必须"连得上"某个入参;连不上就是悬垂,只能返回 owned。
- elision 三规则:参数各一命;单输入则输出继承;方法输出取自 self。
- 别拿
'static堵报错——那是在掩盖真正的寿命问题。
10. 原文地图¶
| 主题 | 原书章 | 原文位置 |
|---|---|---|
| 提取函数的套路 | Chapter 10: Generic Types, Traits, and Lifetimes | text/17-ch10-chapter-10-generic-types-traits-and-lifetimes.txt:145(搜「Identify duplicate code」) |
| E0369 与 PartialOrd | 同上 | text/17-ch10-chapter-10-generic-types-traits-and-lifetimes.txt:268(搜「cannot be applied」) |
| 单态化 | 同上 | text/17-ch10-chapter-10-generic-types-traits-and-lifetimes.txt:512(搜「monomorphization」) |
| trait 定义与实现 | 同上 | text/17-ch10-chapter-10-generic-types-traits-and-lifetimes.txt:582(搜「pub trait Summary」) |
| 孤儿规则 | 同上 | text/17-ch10-chapter-10-generic-types-traits-and-lifetimes.txt:678(搜「only if either」) |
| 默认实现 | 同上 | text/17-ch10-chapter-10-generic-types-traits-and-lifetimes.txt:694(搜「Default Implementations」) |
| impl Trait 与 trait bound | 同上 | text/17-ch10-chapter-10-generic-types-traits-and-lifetimes.txt:794(搜「&impl Summary」) · text/17-ch10-chapter-10-generic-types-traits-and-lifetimes.txt:805(搜「Trait Bound Syntax」) |
| where 子句 | 同上 | text/17-ch10-chapter-10-generic-types-traits-and-lifetimes.txt:848(搜「where Clauses」) |
| 返回 impl Trait | 同上 | text/17-ch10-chapter-10-generic-types-traits-and-lifetimes.txt:868(搜「Returning Types」) |
| 条件实现与 blanket | 同上 | text/17-ch10-chapter-10-generic-types-traits-and-lifetimes.txt:927(搜「Conditionally」) · text/17-ch10-chapter-10-generic-types-traits-and-lifetimes.txt:969(搜「ToString」) |
| 生命周期概念与借用检查器 | 同上 | text/17-ch10-chapter-10-generic-types-traits-and-lifetimes.txt:994(搜「Validating References」) · text/17-ch10-chapter-10-generic-types-traits-and-lifetimes.txt:1065(搜「Borrow Checker」) |
| longest 与 E0106 | 同上 | text/17-ch10-chapter-10-generic-types-traits-and-lifetimes.txt:1144(搜「missing lifetime specifier」) · text/17-ch10-chapter-10-generic-types-traits-and-lifetimes.txt:1207(搜「&'a str」) |
| 'a = 较短寿命 | 同上 | text/17-ch10-chapter-10-generic-types-traits-and-lifetimes.txt:1243(搜「smaller of the lifetimes」) |
| Listing 10-23 的反直觉拒编 | 同上 | text/17-ch10-chapter-10-generic-types-traits-and-lifetimes.txt:1306(搜「the compiler can’t see」) |
| elision 三规则 | 同上 | text/17-ch10-chapter-10-generic-types-traits-and-lifetimes.txt:1445(搜「elision rules」) |
| 'static | 同上 | text/17-ch10-chapter-10-generic-types-traits-and-lifetimes.txt:1551(搜「Static Lifetime」) |
-
出处:「Chapter 10: Generic Types, Traits, and Lifetimes」第 221 段(
text/17-ch10-chapter-10-generic-types-traits-and-lifetimes.txt:221,搜「default choice」)。 ↩ -
出处:「Chapter 10: Generic Types, Traits, and Lifetimes」第 316 段(
text/17-ch10-chapter-10-generic-types-traits-and-lifetimes.txt:316,搜「same type」)与第 366 段(text/17-ch10-chapter-10-generic-types-traits-and-lifetimes.txt:366,搜「needs restructuring」)。 ↩ -
出处:「Chapter 10: Generic Types, Traits, and Lifetimes」第 373 段(
text/17-ch10-chapter-10-generic-types-traits-and-lifetimes.txt:373,搜「enum Option」)与第 387 段(text/17-ch10-chapter-10-generic-types-traits-and-lifetimes.txt:387,搜「enum Result」)。 ↩ -
出处:「Chapter 10: Generic Types, Traits, and Lifetimes」第 432 段(
text/17-ch10-chapter-10-generic-types-traits-and-lifetimes.txt:432,搜「declare T just after impl」)与第 447 段(text/17-ch10-chapter-10-generic-types-traits-and-lifetimes.txt:447,搜「impl Point<f32>」)。 ↩ -
出处:「Chapter 10: Generic Types, Traits, and Lifetimes」第 512 段(
text/17-ch10-chapter-10-generic-types-traits-and-lifetimes.txt:512,搜「monomorphization」)。 ↩↩ -
出处:「Chapter 10: Generic Types, Traits, and Lifetimes」第 557 段(
text/17-ch10-chapter-10-generic-types-traits-and-lifetimes.txt:557,搜「Defining Shared Behavior」)。 ↩ -
出处:「Chapter 10: Generic Types, Traits, and Lifetimes」第 582 段(
text/17-ch10-chapter-10-generic-types-traits-and-lifetimes.txt:582,搜「pub trait Summary」)与第 655 段(text/17-ch10-chapter-10-generic-types-traits-and-lifetimes.txt:655,搜「bring the trait into scope」)。 ↩ -
出处:「Chapter 10: Generic Types, Traits, and Lifetimes」第 678 段(
text/17-ch10-chapter-10-generic-types-traits-and-lifetimes.txt:678,搜「only if either」)。 ↩↩ -
出处:「Chapter 10: Generic Types, Traits, and Lifetimes」第 694 段(
text/17-ch10-chapter-10-generic-types-traits-and-lifetimes.txt:694,搜「Default Implementations」)与第 783 段(text/17-ch10-chapter-10-generic-types-traits-and-lifetimes.txt:783,搜「isn’t possible to call」)。 ↩ -
出处:「Chapter 10: Generic Types, Traits, and Lifetimes」第 805 段(
text/17-ch10-chapter-10-generic-types-traits-and-lifetimes.txt:805,搜「Trait Bound Syntax」)。 ↩ -
出处:「Chapter 10: Generic Types, Traits, and Lifetimes」第 833 段(
text/17-ch10-chapter-10-generic-types-traits-and-lifetimes.txt:833,搜「+ Syntax」)。 ↩ -
出处:「Chapter 10: Generic Types, Traits, and Lifetimes」第 868 段(
text/17-ch10-chapter-10-generic-types-traits-and-lifetimes.txt:868,搜「Returning Types」)与第 893 段(text/17-ch10-chapter-10-generic-types-traits-and-lifetimes.txt:893,搜「single type」)。 ↩ -
出处:「Chapter 10: Generic Types, Traits, and Lifetimes」第 964 段(
text/17-ch10-chapter-10-generic-types-traits-and-lifetimes.txt:964,搜「blanket implementations」)。 ↩ -
出处:「Chapter 10: Generic Types, Traits, and Lifetimes」第 987 段(
text/17-ch10-chapter-10-generic-types-traits-and-lifetimes.txt:987,搜「at runtime」)。 ↩ -
出处:「Chapter 10: Generic Types, Traits, and Lifetimes」第 994 段(
text/17-ch10-chapter-10-generic-types-traits-and-lifetimes.txt:994,搜「Validating References」)。 ↩ -
出处:「Chapter 10: Generic Types, Traits, and Lifetimes」第 1039 段(
text/17-ch10-chapter-10-generic-types-traits-and-lifetimes.txt:1039,搜「does not live long enough」)与第 1065 段(text/17-ch10-chapter-10-generic-types-traits-and-lifetimes.txt:1065,搜「Borrow Checker」)。 ↩ -
出处:「Chapter 10: Generic Types, Traits, and Lifetimes」第 1207 段(
text/17-ch10-chapter-10-generic-types-traits-and-lifetimes.txt:1207,搜「&'a str」)与第 1243 段(text/17-ch10-chapter-10-generic-types-traits-and-lifetimes.txt:1243,搜「smaller of the lifetimes」)。 ↩ -
出处:「Chapter 10: Generic Types, Traits, and Lifetimes」第 1306 段(
text/17-ch10-chapter-10-generic-types-traits-and-lifetimes.txt:1306,搜「the compiler can’t see」)。 ↩ -
出处:「Chapter 10: Generic Types, Traits, and Lifetimes」第 1331 段(
text/17-ch10-chapter-10-generic-types-traits-and-lifetimes.txt:1331,搜「match the lifetime parameter」)与第 1364 段(text/17-ch10-chapter-10-generic-types-traits-and-lifetimes.txt:1364,搜「connecting the lifetimes」)。 ↩↩ -
出处:「Chapter 10: Generic Types, Traits, and Lifetimes」第 1376 段(
text/17-ch10-chapter-10-generic-types-traits-and-lifetimes.txt:1376,搜「ImportantExcerpt」)。 ↩ -
出处:「Chapter 10: Generic Types, Traits, and Lifetimes」第 1429 段(
text/17-ch10-chapter-10-generic-types-traits-and-lifetimes.txt:1429,搜「In early versions」)。 ↩↩ -
出处:「Chapter 10: Generic Types, Traits, and Lifetimes」第 1445 段(
text/17-ch10-chapter-10-generic-types-traits-and-lifetimes.txt:1445,搜「elision rules」)。 ↩ -
出处:「Chapter 10: Generic Types, Traits, and Lifetimes」第 1551 段(
text/17-ch10-chapter-10-generic-types-traits-and-lifetimes.txt:1551,搜「Static Lifetime」)。 ↩ -
出处:「Chapter 10: Generic Types, Traits, and Lifetimes」第 1568 段(
text/17-ch10-chapter-10-generic-types-traits-and-lifetimes.txt:1568,搜「Generic Type Parameters, Trait Bounds, and Lifetimes」)。 ↩