推动 GATs 稳定化
从何说起,从何说起...
首先要说的是:这是一篇非常令人兴奋的文章。一些读者可能会感到无比激动;一些人可能完全不知道 GATs(泛型关联类型)是什么;还有一些人可能会难以置信。这项功能的 RFC 实际上是在 2016 年 4 月提出的(并在大约一年半后合并)。事实上,这个 RFC 甚至比 const 泛型(其 MVP 版本 最近刚刚稳定)还要早。但这并不能欺骗你:这是一项强大的功能;Github 上跟踪问题的反应或许能让你了解它的受欢迎程度(它是 Rust 仓库中 *投票数最高* 的问题):
如果你不熟悉 GATs,它们允许你在关联类型上定义类型、生命周期或 const 泛型。例如:
现在,这可能看起来平平无奇,但我稍后会更详细地解释 为什么 这确实是一项强大的功能。
但就目前而言:究竟 发生 了什么?在 RFC 合并近四年后,generic_associated_types
功能不再标记为“不完整”(incomplete)。
(一片寂静)
等等... 就这样?? 嗯,是的!我将在本文稍后详细解释 为什么 这很重要。长话短说,为了让 GATs 工作,编译器进行了相当多的修改。而且,虽然仍有一些小的诊断问题有待解决,但该功能最终达到了一种我们认为可以不再将其标记为“不完整”的状态。
那么,这意味着什么呢?嗯,它 真正 意味着的是,当你在 Nightly 版本中使用此功能时,你将不再收到“generic_associated_types
功能不完整”的警告。然而,这件事之所以重要,真正的原因是:我们想稳定这项功能。但我们需要你的帮助。我们需要你测试这项功能,对于发现的任何 bug 或潜在的诊断改进,请提交 issue。另外,如果你发现了 GATs 所能实现的有趣模式,我们非常乐意听到,请到 Zulip 告诉我们!
虽然我们不能百分百确定,但我们非常希望能在接下来的几个月内稳定这项功能。但是,我们想确保没有遗漏任何显而易见的错误或缺陷。我们希望这次稳定化过程能够顺利进行。
好的。松一口气。这是本文的重点和最令人兴奋的消息。但正如我之前所说,我认为解释这项功能 是什么、你可以用它做什么,以及一些背景信息和我们是如何走到这一步的,也是合理的。
GATs 是什么?
注意:这只是一个简要概述。RFC 中包含更多细节。
GATs(泛型关联类型)最初是在 RFC 1598 中提出的。如前所述,它们允许你在关联类型上定义类型、生命周期或 const 泛型。如果你熟悉拥有“更高种类类型(higher-kinded types)”的语言,那么你可以将 GATs 称为 特性上的类型构造器 (type constructors on traits)。也许让你了解如何使用 GATs 的最简单方法是直接看一个例子。
这里有一个常见的用例:一个 LendingIterator
(之前称为 StreamingIterator
)
让我们来看一个它的实现,一个假设的 <[T]>::windows_mut
,它允许在一个切片上迭代重叠的可变窗口。如果你今天尝试使用 Iterator
来实现它,像这样:
那么你会得到一个错误。
error: cannot infer an appropriate lifetime for lifetime parameter in function call due to conflicting requirements
-/lib.rs:9:22
|
9 | let retval = self.slice.get_mut?;
note: first, the lifetime cannot outlive the lifetime `'a` as defined on the method body at 8:13...
-/lib.rs:8:13
|
8 |
简而言之,这个错误基本上告诉我们,为了能够返回 self.slice
的引用,它必须与 'a
的生命周期一样长,这就需要一个 'a: 't
约束(而我们无法提供)。没有这个约束,我们可以在已经持有切片引用的同时调用 next
,从而创建重叠的可变引用。然而,如果你使用前面提到的 LendingIterator
特性来实现它,就可以正常编译:
顺带一提,关于这个特性和实现,有一点你可能好奇:Item
上的 where Self: 'a
子句。简单地说,这允许我们使用 &'a mut [T]
;没有这个 where 子句,有人可能会尝试返回 Self::Item<'static>
并延长切片的生命周期。我们理解这有时会引起困惑,并且正在考虑潜在的替代方案,例如始终假定此约束或根据特性内部的使用情况来暗示它(参见 这个 issue)。我们非常希望听到你们在这里的用例,特别是当假定此约束会成为阻碍时。
再举一个例子,想象你想让一个结构体对指向特定类型的指针进行泛型。你可能会写出以下代码:
;
;
我们在这里不会深入探讨细节,但这个例子很好地说明了 GATs 中类型的使用,同时也展示了你仍然可以使用已有的、可在关联类型上使用的特性约束(trait bounds)。
这两个例子只是 GATs 支持的模式的冰山一角。如果你发现了任何看起来特别有趣或巧妙的模式,我们非常乐意到 Zulip 上听取你的分享!
为什么实现它花了这么长时间?
那么,是什么导致我们花费了将近四年的时间才达到现在这个阶段呢?嗯,很难用语言来形容现有的特性解析器(trait solver)需要进行多少改变和适应;但是,请考虑这一点:有一段时间,人们认为为了支持 GATs,我们将不得不将 rustc 切换到使用 Chalk,这是一个潜在的未来特性解析器,它使用逻辑谓词(logical predicates)来解决特性目标(trait goals)(尽管目前已经取得了一些进展,但它现在仍然非常具有实验性)。
供参考,以下是一些为支持 GATs 而进行的各种实现添加和更改:
- 在 AST 中解析 GATs (#45904)
- 解析 GATs 中的生命周期 (#46706)
- 特性解析器支持生命周期的初步工作 (#67160)
- 验证投影约束 (projection bounds)(并进行了允许类型和 const GATs 的更改)(#72788)
- 分离投影约束 (projection bounds) 和谓词 (predicates) (#73905)
- 允许 GATs 出现在特性路径中 (#79554)
- 部分用宇宙 (universes) 替换泄露检查 (leak check) (#65232)
- 将泄露检查 (leak check) 移至特性解析后阶段 (#72493)
- 在投影时用占位符 (placeholders) 替换 GATs 中的绑定变量 (bound vars) (#86993)
为了进一步强调上述工作:许多这些 PR 都很庞大,背后有大量的设计工作。在此过程中还有一些较小的 PR。但是,我们做到了。我只想祝贺所有以各种方式为此付出努力的人。你们太棒了。
目前有哪些限制?
好的,现在到了没人喜欢听的部分:限制。幸运的是,在这种情况下,GATs 只有一个限制:带有 GATs 的特性不是对象安全的(object safe)。这意味着你将无法做这样的事情:
做出这个决定的最大原因是,要真正使这一点可用,还需要一些设计和实现工作。虽然这是一个不错的功能,但将来添加它会是一个向后兼容的更改。我们认为最好是先稳定 大部分 GATs,然后再回来尝试解决这个问题,而不是为了这一点而进一步推迟 GATs 的稳定化。此外,即使没有对象安全,GATs 仍然非常强大,因此推迟这一点我们并没有损失太多。
正如本文前面提到的,还有一些剩余的诊断 issue。不过,如果你确实发现了 bug,请提交 issue!