本页目录
关联类型与 GAT
关联类型(
type Item)让 trait 的每个实现只能有一个 Item 类型,和泛型参数(每个实现可以有无数个 T)互补——关联类型强制唯一性,泛型参数允许多样性。GAT(Generic Associated Types,1.65+)让关联类型本身可以带泛型参数,打开了返回借用自身数据的 trait、泛型迭代器等之前无法表达的抽象。
关联类型: 每个 impl 指定一个具体类型
为什么不用泛型参数
你当然可以写成 trait Iterator<T> { fn next(&mut self) -> Option<T>; }。但这意味着 Counter 可以同时实现 Iterator<i32>, Iterator<String>, Iterator<f64>——每个 T 一个独立的 impl。对 Iterator 来说这不合理——一个类型只应该产出一种类型。
关联类型表达的是"一对一"的语义约束:每种类型最多一个 impl。泛型参数表达的是"一对多":每种类型可以对不同的泛型参数有多个 impl。
这不仅仅是语义偏好——它影响类型推断。使用关联类型时,Iterator::Item 从 Self 可以唯一确定。使用泛型参数时,编译器需要从上下文推断 T。关联类型让编译器的工作更简单,也让 API 更清晰。
GAT (Generic Associated Types): 关联类型带生命周期
Rust 1.65 引入了 GAT——关联类型可以有自己的生命周期或泛型参数:
GAT 解决了什么问题?传统的 Iterator trait 无法表达"返回借用 self 的引用"。你无法写 type Item = &[u8] 因为引用必须有生命周期——而关联类型上没有地方写生命周期。GAT 的 type Item<'a> 让关联类型可以使用生命周期,使 Item<'a> = &'a [u8] 成为可能——返回的引用与 &self 的生命周期关联。
这被称为 lending iterator——迭代器"借出"数据而不是转移所有权。std 的 Iterator 不是 lending 的(next 返回 Option<Self::Item>,不借用 &mut self),所以你不能在迭代器返回的元素上持有对迭代器内部的引用。GAT 解除了这个限制。
GAT 也是 async trait 的基础。async fn 生成的 Future 类型是匿名的,无法在 trait 中表达。GAT 允许 type Future<'a>: Future<Output = X> 这种形式——trait 可以有一个返回 Future 的方法,而不需要 Box<dyn Future> 的堆分配开销。
impl Trait in Return Position
调用者不知道返回的具体类型——只知道"它实现了 Iterator<Item = i32>"。编译器知道,所以可以做静态分发和内联。与 Box<dyn Iterator> 的区别:impl Trait 是静态分发(零开销,无虚函数调用),但调用者不能把不同返回类型的函数互相赋值——这是编译器抽象边界的代价。
参考
- RFC 1598: Generic Associated Types
- Rust Blog: "GATs are stable!" (2022)
- Rust Book: Chapter 19.4
Keywords: associated type, GAT, lending iterator, impl Trait, static dispatch, type inference