查看原文
其他

GAT 深度之旅 Part 1 : 什么是 GAT

张汉东 觉学社 2022-11-06
前言
因觉学,求绝学。Rust 生态蜜蜂,是觉学社公众号开启的一个付费专栏。生态蜜蜂,顾名思义,是从 Rust 生态的中,汲取养分,供我们成长。计划从2022年7月第二周开始,到2022年12月最后一周结束。预计至少二十篇周刊,外加一篇Rust年度报告总结抢读版。本专栏可以在公众号菜单「生态蜜蜂」中直接进入。海外用户可以去 [https://rustbee.zhubai.love/](https://rustbee.zhubai.love/) 订阅。

本系列文章预计共三篇,将会系统地探索 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 做个比较:

您可能也对以下帖子感兴趣

文章有问题?点此查看未经处理的缓存