The Most Common Rust Compiler Errors as Encountered in RustRover: Part 2
In Part 1 of this blog series, we dug into our list of the top 10 most common Rust compiler errors according to RustRover’s usage data, and we made it halfway through the list. In this part, we’ll explore the top five most frequently occurring errors and then make some more general observations regarding the aspects of the Rust programming language that have the biggest tendency to put developers “at odds” with the compiler.
Common error #5: E0433 (An undeclared crate, module, or type was used)
This error is similar to E0432 (An import was unresolved), which we discussed in the previous part. The only difference is that a path with a problematic component is used directly in a name, without the use
clause. 17.5% of RustRover users encountered this error. For example, in the code below, the compiler is unable to figure out what we actually mean (a crate or a module) and complains:
This is an example of why the Rust compiler is widely praised for its error messaging. Even without reading the message, the fix is obvious: verify the names, add the required dependencies to Cargo.toml if needed, or introduce proper imports.
RustRover is also helpful. It can give us a choice of imported names.
Using code completion while writing code lets us avoid this error altogether.
Common error #4: E0425 (An unresolved name was used)
Let’s continue exploring the unknown with yet another “unresolved” error. 20.5% of RustRover users have been in a situation where they were using an unresolved name that was syntactically not a crate or module name. Here’s what pops up when this error occurs.:
Of course, when this happens to you, chances are it won’t be as clear-cut as in the image above.
Sadly, even the glorious Rust compiler can’t help much with this error. There are a few approaches you can take: revise the name, provide a definition, or introduce proper imports.
Common error #3: E0599 (A method is used on a type which doesn’t implement it)
At the risk of spoiling the surprise: it’s nothing but type-checking errors from here on out! That’s right – all three top spots were taken by errors relating to incorrect type usage. The specific error here is that 27.5% of RustRover users tried to call a method on a value of a type that doesn’t implement that method. Let’s look at the following example:
fn main() { let numbers: Vec<usize> = vec![4, 11, 15, 12]; let sum: usize = numbers.sum(); println!("Answer = {sum}"); }
Seems legit, except there’s no sum
method to Vec
. Here’s what the error message looks like specifically:
error[E0599]: `Vec<usize>` is not an iterator
The fix in this case is to call an iter
method first and then use sum
on the resulting iterator.
By the way, RustRover has a feature that prevents errors like this one. It’s called chained completion. To see the benefits in action, look no further than the following example:
When the completion suggestion is taken, both iter
and sum
methods are added to the call chain, fixing the code immediately.
In general, code completion helps you avoid getting an E0599 message. If you’re not seeing a suggestion for the method call you need, the real issue might be an incorrect callee type.
There’s another aspect of this error worth mentioning: in many contexts, you have control over your own type declarations. One option is to call a method first and then ask RustRover to scaffold its implementation by pressing Alt-Enter (⌥Option–Return) and executing the action suggested:
As a result of that action, RustRover generates the method implementation template, taking into account all the context information regarding types:
impl Info { pub(crate) fn summary(&self) -> String { todo!() } }
Running this code results in a runtime error, but at least the compiler doesn’t complain anymore.
Common error #2: E0308 (Expected type did not match the received type)
Imagine this scenario: You’re using an expression of type A
in a context where a value of type B
is expected. “Context” here can mean a function’s parameter at a call site, a variable declaration, a control-flow statement/expression, etc. If you’ve dealt with this, then you’re in the same boat as 30% of RustRover users.
Conceptually, we have two choices in this case: tweak a value or tweak the context. Tweaking a value may involve casting a type, referencing/dereferencing, calling a transforming method on it, or replacing it with something else. Tweaking the context is not always possible, but sometimes we can change an expected type to match a received one.
RustRover supports both modes of operating by providing a choice of quick-fixes, for example:
Note the second suggestion in the list: In this case, RustRover has come up with quite a sophisticated transformation of a value that suits the compiler. Following the first suggestion would instead fix the context, namely the function definition.
Common error #1: E0277 (You tried to use a type which doesn’t implement some trait in a place which expected that trait)
Finally, we’ve reached the summit! The most common Rust compiler error as encountered in RustRover is E0277. 32% of RustRover users have experienced this types-and-traits-themed nightmare for developers. The official explanation does a good job of providing examples and explaining possible fixes, as usual. Let’s focus on RustRover’s behavior instead.
Here’s my favorite RustRover feature dealing with this sort of error:
The compiler complains as follows:
error[E0277]: `Info` doesn't implement `std::fmt::Display`
Of course, implementing the Display
trait is one of the options, and RustRover is happy to scaffold an implementation. But in this case, I’d prefer to apply the first suggestion, which involves two simultaneous steps:
- Deriving an implementation for the
Debug
trait. - Changing the format string to invoke the
Debug
trait’s formatting facilities.
Unfortunately, in many other cases, RustRover isn’t able to discover and highlight this error by itself. As I’ve already mentioned in the previous part, its type-checking functionality is not that powerful yet, but we’re working on it. As the most commonly encountered error message, E0277 is certainly on our radar.
Observing Rust errors in general
Let’s look at the bigger picture. Last week, we asked the X (formerly known as Twitter) community what aspect of the Rust programming language is the primary source of errors in code. Here’s what we got in response:
To compare the community’s answers with what we see from our data, we’ve looked at the top 25 most common Rust compiler errors and roughly categorized them into five different categories:
- Types and traits
- Ownership and lifetimes
- Macros
- Unresolved names or nonexistent elements
- Other
The results are as follows (in ascending order of error code number):
Error Code | Description | Category |
E0061 | An invalid number of arguments was passed when calling a function. | other |
E0063 | A struct’s or struct-like enum variant’s field was not provided. | unresolved/nonexistent |
E0106 | This error indicates that a lifetime is missing from a type. | ownership and lifetimes |
E0107 | An incorrect number of generic arguments was provided. | types and traits |
E0277 | You tried to use a type which doesn’t implement some trait in a place which expected that trait. | types and traits |
E0282 | The compiler could not infer a type and asked for a type annotation. | types and traits |
E0283 | An implementation cannot be chosen unambiguously because of lack of information. | types and traits |
E0308 | Expected type did not match the received type. | types and traits |
E0369 | A binary operation was attempted on a type which doesn’t support it. | types and traits |
E0382 | A variable was used after its contents have been moved elsewhere. | ownership and lifetimes |
E0412 | A used type name is not in scope. | unresolved/nonexistent |
E0423 | An identifier was used like a function name or a value was expected and the identifier exists but it belongs to a different namespace. | unresolved/nonexistent |
E0425 | An unresolved name was used. | unresolved/nonexistent |
E0432 | An import was unresolved. | unresolved/nonexistent |
E0433 | An undeclared crate, module, or type was used. | unresolved/nonexistent |
E0502 | A variable already borrowed as immutable was borrowed as mutable. | ownership and lifetimes |
E0507 | A borrowed value was moved out. | ownership and lifetimes |
E0515 | A reference to a local variable was returned. | ownership and lifetimes |
E0596 | This error occurs because you tried to mutably borrow a non-mutable variable. | ownership and lifetimes |
E0597 | This error occurs because a value was dropped while it was still borrowed. | ownership and lifetimes |
E0599 | This error occurs when a method is used on a type which doesn’t implement it: | types and traits |
E0609 | Attempted to access a nonexistent field in a struct. | unresolved/nonexistent |
E0614 | Attempted to dereference a variable which cannot be dereferenced. | other |
E0658 | An unstable feature was used. | other |
E0716 | A temporary value is being dropped while a borrow is still in active use. | ownership and lifetimes |
Unfortunately, the data we have doesn’t say much about macros. We are neither able to reliably detect macro-expanding issues nor identify other errors that originated in successfully expanded macros. Maybe this is a sign that we should introduce more fine-grained categorization into the data we collect.
Ignoring macros, it seems that we have no clear winner among the top 25 common errors, meaning that we love all Rust parts without exception:
- Types and traits – 7 errors
- Ownership and lifetimes – 8 errors
- Unresolved names or nonexistent elements – 7 errors
- Other – 3 errors
Summary
In this part of the series, we’ve looked at the five most common Rust compiler errors as encountered in RustRover. Three of them were related to types and traits, suggesting that this category of errors is somewhat critical to get Rust code right. We’ve concluded by observing more general data regarding the 25 most common errors and realized that other aspects of Rust also contribute to errors we often encounter.