Rustc 特征系统重构计划更新

2023 年 7 月 17 日 · lcnr 代表 Rustc 特征系统重构计划

正如今年年初在类型团队公告中宣布的那样,类型团队已经开始重新实现 rustc 的特征求解器。这种重构类似于 Chalk,但直接集成到现有代码库中,使用了过去几年积累的经验。与 Chalk 不同,新的特征求解器的唯一目标是替换现有的实现。我们正在单独致力于在 a-mir-formality 中形式化类型系统。自该公告发布以来已经过去了半年,这与 我们的路线图的第一步相符。

通过重新实现 rustc 的特征求解器,我们能够修复现有实现的许多细微错误和低效率问题。这应该会导致更快的编译速度和更少的错误。新的特征求解器实现还应该为未来的许多更改扫清障碍,最值得注意的是关于隐含边界和协同归纳的更改。例如,它将允许我们删除许多 当前对 GAT 的限制,并修复许多长期存在的不健全问题,如 #25860。一些不健全的问题将在稳定时修复,而其他问题则需要后续的额外工作。新的求解器还将启用或大大简化其他仍处于实验阶段的类型系统扩展,例如 泛型常量表达式非生命周期绑定器

现状

可以通过使用 rustc 标志 -Ztrait-solver=next 在 nightly 版本上测试新的特征求解器实现。要仅将新实现用于一致性检查,可以使用 -Ztrait-solver=next-coherence。请在 其跟踪 issue 中查看新的特征求解器的当前实现进度。

我们现在已经到了一个完全依赖于启用功能标志时的新实现的阶段。这是一个重要的步骤,因为我们以前仍然依赖旧的求解器来进行 “深度规范化”“选择”。使用新的特征求解器时,许多 crate 和我们的大多数现有回归测试都成功编译。

虽然存在大量不太常见的错误,但我们目前有两个主要失败原因

首先,新的求解器对于 impl Trait 有稍微不同的方法。其实现对于嵌套返回位置 impl trait 的实例仍然存在问题,例如 fn foo() -> impl Trait<Assoc = impl Sized>。研究这种新方法帮助我们发现了现有实现的问题,这使我们能够在 type_alias_impl_trait 功能稳定之前改进其设计。

其次,隐式 Unsize 强制转换的推断,例如将 Box<String> 转换为 Box<dyn Display>,依赖于现有特征求解器的实现细节。我们最近开始在这里模拟现有行为,并应在未来几周内修复由此造成的大部分剩余损坏。

未来展望

在修复当前未解决的问题后,我们打算逐步将 rustc 的部分内容转移到新的特征求解器实现,首先是在一致性中使用它。我们预计将在今年年底将一致性检查转移到新的实现。将函数的类型检查转移到新的特征求解器实现将更具挑战性。这将是我们使用旧实现的最后一个地方。我们预计将在 2024 年更改默认设置,可能会依赖新版本来帮助迁移。

一个主要的挑战将是“不完备性”。我们使用不完备性作为技术术语,表示类型系统不必要地引导类型推断的情况。不完备性允许编译其他模糊的代码,但它也使特征系统依赖于顺序,并可能导致不正确和奇怪的错误。请考虑以下示例

fn impl_trait() -> impl Into<u32> {
    0u16
}

fn main() {
    // There are two possible types for `x`:
    // - `u32` by using the "alias bound" of `impl Into<u32>`
    // - `impl Into<u32>`, i.e. `u16`, by using `impl<T> From<T> for T`
    //
    // We infer the type of `x` to be `u32` even though this is not
    // strictly necessary and can even lead to surprising errors.
    let x = impl_trait().into();
    println!("{}", std::mem::size_of_val(&x));
}

我们如何以及何时进行此类推断跳跃与旧的特征求解器实现密切相关,而不是我们想要或甚至可以直接复制的东西。我们必须弄清楚如何在尽可能保持现有行为的同时,使规则适应新的实现。只有在更大的问题得到解决,并且我们开始启用新实现运行 crater 时,这个问题的复杂性才会变得明显。

另一个主要的障碍是错误诊断。我们目前依赖旧的特征求解器实现的许多内部细节,向用户发出可操作且有用的错误。通过依赖和解决这些内部细节,这些诊断已逐步得到改进。我们无法简单地将它们与新的特征求解器实现一起重用。我们的目标是改为选择性地发出“证明树”,这是一种特征求解如何发生的内存表示形式。我们打算在这些证明树之上提供一个简化的 API,然后将其用于诊断。