Gavin Gray · April 29, 2025
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<: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`: `posts::columns::id` implements `SelectableExpression >` `posts::columns::id` implements `SelectableExpression >` `posts::columns::id` implements `SelectableExpression >>` `posts::columns::id` implements `SelectableExpression >` `posts::columns::id` implements `SelectableExpression<:table>` `posts::columns::id` implements `SelectableExpression<:joins::join right="" inner="">>` `posts::columns::id` implements `SelectableExpression<:joins::join 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:
Loading…
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:
- The rest of the inference tree can be accessed by clicking the individual trait bounds.
- Long types and impl bounds have been abbreviated with a
[..]
which can be expanded by clicking. - Paths have been abbreviated, and full paths can be seen by mousing over the path (sorry touchscreen readers).
- A list of implementations of a trait can be accessed by clicking the list icon to the right of each trait bound.
You can try Argus as a VSCode extension on the VSCode Marketplace or the Open VSX Registry. The Argus source code is available on Github.
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.