GAT 深度之旅 Part 1 : 什么是 GAT
“本系列文章预计共三篇,将会系统地探索 Rust GAT(Generic Associated Types)的机制到应用,从而达到深入理解 GAT 的学习目的。
本文目录
GAT 稳定历史 什么是关联类型 泛型 trait vs 关联类型 关联类型语言规范 为什么需要泛型关联类型(GAT) 泛型关联类型是什么以及新的设计模式介绍
GAT 稳定历史
目前 Generic Associated Types (GATs) 已于 2022-11-03 在 Rust 1.65 版中稳定。
下列链接梳理了 GAT 的稳定历史,看得出来,历时六年这个功能才彻底稳定下来:
On 2016-04-30, RFC opened[1] On 2017-09-02, RFC merged and tracking issue opened[2] On 2017-10-23, Move Generics from MethodSig to TraitItem and ImplItem[3] On 2017-12-01, Generic Associated Types Parsing & Name Resolution[4] On 2017-12-15, Lifetime Resolution for Generic Associated Types #46706[5] On 2018-04-23, Feature gate where clauses on associated types[6] On 2018-05-10, Extend tests for RFC1598 (GAT)[7] On 2018-05-24, Finish implementing GATs (Chalk)[8] On 2019-12-21, Make GATs less ICE-prone[9] On 2020-02-13, fix lifetime shadowing check in GATs[10] On 2020-06-20, Projection bound validation[11] On 2020-10-06, Separate projection bounds and predicates[12] On 2021-02-05, Generic associated types in trait paths[13] On 2021-02-06, Trait objects do not work with generic associated types[14] On 2021-04-28, Make traits with GATs not object safe[15] On 2021-05-11, Improve diagnostics for GATs[16] On 2021-07-16, Make GATs no longer an incomplete feature[17] On 2021-07-16, Replace associated item bound vars with placeholders when projecting[18] On 2021-07-26, GATs: Decide whether to have defaults for `where Self: 'a`[19] On 2021-08-25, Normalize projections under binders[20] On 2021-08-03, The push for GATs stabilization[21] On 2021-08-12, Detect stricter constraints on gats where clauses in impls vs trait[22] On 2021-09-20, Proposal: Change syntax of where clauses on type aliases[23] On 2021-11-06, Implementation of GATs outlives lint[24] On 2021-12-29. Parse and suggest moving where clauses after equals for type aliases[25] On 2022-01-15, Ignore static lifetimes for GATs outlives lint[26] On 2022-02-08, Don't constrain projection predicates with inference vars in GAT substs[27] On 2022-02-15, Rework GAT where clause check[28] On 2022-02-19, [Only mark projection as ambiguous if GAT substs are constrained](https://github.com/rust-lang/rust/pull/93892 On 2022-03-03, Support GATs in Rustdoc[29] On 2022-03-06, Change location of where clause on GATs[30] On 2022-05-04, A shiny future with GATs blog post[31] On 2022-05-04, Stabilization PR[32] On 2022-11-3, Rust 1.65 Stable release,include GAT[33]
你可能会想,为什么增加一个语言特性要经历六年这么久呢?
因为要引入 GAT 特性需要首先对 rustc 的 trait 系统进行大改,Niko 为此重新实现了新的 trait 系统(称为 Chalk[34]),只是这个工作就花费了四年。在 Chalk 集成到 rustc 以后官方再次推动 GAT 的稳定,又花了两年。
什么是关联类型
“提示:如果你已经很懂关联类型,可以跳过本节内容。
关联类型(Associated types) 的概念并不是 Rust 独创,在 2007 年的一篇名为 A Comparative Study of Language Support for Generic Programming[35]的论文中提到的 Multi-Type 理论就是关联类型这个概念的来源。在 Haskell、Swift 和 Rust 中都以不同形式支持此概念。
Rust 在 1.0 稳定版发布前通过 RFC 0195 [36] 引入了关联类型的概念。目的是为了提升 Rust 语言中 trait 的工程能力。
拿一个具体案例来说,在支持关联类型之前,如果想实现一个 Add
trait,就必须按下面的方式来写:
trait Add<Rhs, Sum> {
fn add(&self, rhs: &Rhs) -> Sum;
}
然后,为不同类型实现该 trait:
impl Add<int, int> for int { ... }
impl Add<Complex, Complex> for int { ... }
就会遇到编译器错误:"error: conflicting implementations for trait Add
"。这是因为编译器没有对泛型输入类型和输出类型进行区分。
引入了关联类型的概念,就是对泛型类型区分了输入和输出类型,关联类型即输出类型,这样就可以让 trait 更具工程优势。
// Self 和 Rhs 是输入类型
trait Add<Rhs> {
type Sum; // Sum 是输出类型
fn add(&self, &Rhs) -> Sum;
}
impl Add<int> for int {
type Sum = int;
fn add(&self, rhs: &int) -> int { ... }
}
impl Add<Complex> for int {
type Sum = Complex;
fn add(&self, rhs: &Complex) -> Complex { ... }
}
输入类型,用户在使用 trait 时可以指定,但是输出类型只有在 实现 trait 时才可以指定。
关联类型赋予了 trait 的工程优势:
可读性和可扩展性。高内聚低耦合,更利于扩展性。 易于重构。添加新的关联类型而不会破坏所有现有代码。
泛型 trait vs 关联类型
所以问题来了,什么时候使用 泛型 trait,什么时候使用关联类型呢?
拿标准库中内置 trait 做个比较: