用于 Rust Trait 错误的交互式调试器
用于 Rust Trait 错误的交互式调试器
Gavin Gray · 2025年4月29日
人类开发者经常犯一些简单的错误。 值得庆幸的是,类型系统在静态地捕获这些简单错误并节省开发者的时间方面做得非常出色。 然而,随着类型错误变得越来越复杂,开发者可能会花费大量时间来理解错误信息本身。
想象一下用 Rust 编写一个社交媒体平台。 假设你的应用程序有两个表,users
和 posts
,后者有一个字段 author_id
,它是 users
表的外键。 你想要编写一个函数,从数据库中获取所有用户及其发布的帖子;快速输入或者接受来自 Copilot 的代码,你最终得到了以下有 bug 的函数。(完整代码在这里。)
fn get_all_published_posts(
conn: &mut PgConnection
) -> QueryResult<Vec<(i32, String, i32)>> {
users::table
.filter(posts::published.eq(true))
.select((users::id, users::name, posts::id))
.load(conn)
}
一个使用 Diesel 库从 SQL 表中选择列的 Rust 函数。
对于任何熟悉 SQL 的人来说,这个错误应该很容易发现。你忘记连接 users
和 posts
表了! 虽然这个错误很容易用英语描述,但对于编译器来说却很棘手。 下面超过 100 行、650 个单词、7.6k 个字符的错误信息,就是你会从 Rust 编译器那里得到的信息。(原始文本也在这里。)
Expand
error[E0277]: Cannot select `posts::columns::id` from `users::table`
--> src/main.rs:30:10
|
30 | .select((users::id, users::name, posts::id))
| ^^^^^^ the trait `SelectableExpression<users::table>` is not implemented for `posts::columns::id`
|
= note: `posts::columns::id` is no valid selection for `users::table`
= help: the following other types implement trait `SelectableExpression<QS>`:
`posts::columns::id` implements `SelectableExpression<JoinOn<Join, On>>`
`posts::columns::id` implements `SelectableExpression<Only<posts::table>>`
`posts::columns::id` implements `SelectableExpression<SelectStatement<FromClause<From>>>`
`posts::columns::id` implements `SelectableExpression<Tablesample<posts::table, TSM>>`
`posts::columns::id` implements `SelectableExpression<posts::table>`
`posts::columns::id` implements `SelectableExpression<query_source::joins::Join<Left, Right, Inner>>`
`posts::columns::id` implements `SelectableExpression<query_source::joins::Join<Left, Right, LeftOuter>>`
来自 Rust 编译器关于上述 Diesel 程序的错误信息。 问题在于,Diesel,这个 Rust ORM 库,包含一个复杂的 traits 系统,用于检查特定领域的正确性属性,例如 SQL 查询的有效性。 即使对于像 Rust 这样以良好的错误信息而闻名的语言来说,解释为什么这些 traits 会失败也是一项艰巨的任务。
我知道你,读者,在想什么: 我使用更好的语言,这似乎只是一个 Rust 问题。 不幸的是,这不仅仅是一个 Rust 问题。 许多语言都具有类似于 Rust 的 traits 的类型特征(Haskell 的 type classes, Scala 的 givens, Swift 的 protocols, C++ 的 concepts 等)。 此外,这甚至不仅仅是 traits 的问题,而是复杂类型机制的普遍问题。 例如,Rust trait 错误是由 trait solver 发现的,trait solver 是 Rust 开发者眼中的黑盒组件。 Rust 诊断程序使用来自 solver 内部数据结构(trait 推断树)的信息来生成诊断信息。 随着类型系统复杂性的增加,编译器黑盒内部的规模和复杂性也随之增加,这给编译器带来了更大的解释自身负担。 今天,对于现代语言来说,这是一项艰巨的任务。
作为回应,我们开发了 Argus,一个用于 Rust 的交互式 trait 调试器。 Argus 提供了一个接口,可以访问由 trait solver 生成的完整推断树。 Argus 使用 GUI 而不是 CLI,允许开发者逐步探索推断树的复杂性。 这是 Argus 在上述 Diesel 程序上运行的实时演示: Loading... 用于上面讨论的 Diesel 错误的 Argus 小部件。 尝试点击看看! Argus 提供了推断树的两种视图:自底向上(从叶子开始)和自顶向下(从根开始)。 请注意,消息的大部分内容都隐藏在各种交互操作之后:
- 可以通过单击各个 trait bounds 访问推断树的其余部分。
- 长类型和 impl bounds 已缩写为
[..]
,可以通过单击展开。 - 路径已被缩写,完整路径可以通过将鼠标悬停在路径上来查看(抱歉触摸屏读者)。
- 可以通过单击每个 trait bound 右侧的列表图标来访问 trait 的实现列表。
你可以在 VSCode Marketplace 或 Open VSX Registry 上将 Argus 试用为 VSCode 扩展。 Argus 源代码可在 Github 上找到。
此外,我们进行了一项用户研究,以测试我们的假设,即我们的交互式界面对开发者有用。 使用 Argus,Rust 开发者定位 trait 错误的速度提高了 3.3 倍!
如果你有兴趣了解更多关于这些概念与其他语言的关系、我们如何设计界面或我们如何评估该工具的信息,请阅读完整的论文在线可用,该论文将在 PLDI 2025 上发表。