Human developers frequently make simple mistakes. Thankfully, type systems do a great job of statically catching simple mistakes and saving developers time. However, as type errors become more complex, developers may spend significant time trying to understand the error message itself.
Imagine writing a social media platform in Rust. Say your application has two tables, users
and posts
, the latter has a field author_id
that is a foreign key into the users table. You want to write a function that fetches all users and their published posts from the database; typing hastily — or accepting code from Copilot — you end up with the following buggy function. (The full code is available here.)
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)
}
A Rust function that uses the Diesel library to select columns from a SQL table.
For anyone familiar with SQL, the error should be easy to spot. You've forgotten to join the users and posts tables! Although that error is easy to describe in English, it's tricky for the compiler. The >100 line, 650 word, 7.6k character error message below is the information you'd get back from the Rust compiler. (The raw text is also available here.)
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>>`
The error message from the Rust compiler for the Diesel program above.
The issue is that Diesel, the Rust ORM library being used, contains a complex system of traits to check domain-specific correctness properties, like the validity of SQL queries. Even for a language with famously good error messages like Rust, explaining why these traits fail is a tall order.
I know what you, the reader, are thinking: I use better languages and this seems like a Rust problem. Unfortunately, it isn't just a Rust problem. Many languages have type features similar to Rust's traits (Haskell type classes, Scala givens, Swift protocols, C++ concepts, etc). Moreover, it's not even a problem with just traits, it's a general problem with complex type mechanisms. For example, a Rust trait error is discovered by the trait solver, a component that is a black box to Rust developers. Rust diagnostics use information from the solver's internal data structure, the trait inference tree, to generate diagnostics. As the complexity of type systems increase, so does the size and complexity of the compiler's black-box innards, which places a greater burden on the compiler to explain itself. Today, this is a Herculean task for modern languages.
In response, we developed Argus, an interactive trait debugger for Rust. Argus provides an interface into the full inference tree generated by the trait solver. Argus uses a GUI rather than a CLI, allowing the developer to incrementally explore the complexity of the inference tree. Here is a live demo of Argus running on the Diesel program above:
An Argus widget for the Diesel error discussed above. Try clicking around!
Argus presents two views on the inference tree: bottom-up (starting from the leaves) and top-down (starting from the root). Notice that most of the content of the message is tucked behind various interactions:
[..]
which can be expanded by clicking.You can try Argus as a VSCode extension on the Marketplace and the Open VSX Registry.
In addition, we ran a user study to test our hypothesis that our interactive interface is useful to developers. Rust developers localized trait errors 3.3x faster using Argus!
If you're interested to learn more about how these concepts relate to other languages, how we designed the interface, or how we evaluated the tool, read the full paper available online, which will appear at PLDI 2025.