The Most Common Rust Compiler Errors as Encountered in RustRover: Part 2

Read this post in other languages:

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 (⌥OptionReturn) 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 CodeDescriptionCategory
E0061An invalid number of arguments was passed when calling a function.other
E0063A struct’s or struct-like enum variant’s field was not provided.unresolved/nonexistent
E0106This error indicates that a lifetime is missing from a type.ownership and lifetimes
E0107An incorrect number of generic arguments was provided.types and traits
E0277You tried to use a type which doesn’t implement some trait in a place which expected that trait.types and traits
E0282The compiler could not infer a type and asked for a type annotation.types and traits
E0283An implementation cannot be chosen unambiguously because of lack of information.types and traits
E0308Expected type did not match the received type.types and traits
E0369A binary operation was attempted on a type which doesn’t support it.types and traits
E0382A variable was used after its contents have been moved elsewhere.ownership and lifetimes
E0412A used type name is not in scope.unresolved/nonexistent
E0423An 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
E0425An unresolved name was used.unresolved/nonexistent
E0432An import was unresolved.unresolved/nonexistent
E0433An undeclared crate, module, or type was used.unresolved/nonexistent
E0502A variable already borrowed as immutable was borrowed as mutable.ownership and lifetimes
E0507A borrowed value was moved out.ownership and lifetimes
E0515A reference to a local variable was returned.ownership and lifetimes
E0596This error occurs because you tried to mutably borrow a non-mutable variable.ownership and lifetimes
E0597This error occurs because a value was dropped while it was still borrowed.ownership and lifetimes
E0599This error occurs when a method is used on a type which doesn’t implement it:types and traits
E0609Attempted to access a nonexistent field in a struct.unresolved/nonexistent
E0614Attempted to dereference a variable which cannot be dereferenced.other
E0658An unstable feature was used.other
E0716A 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.

image description