DEV Community

Mykhailo
Mykhailo

Posted on

The 22 Rust errors every beginner hits, in the order they hit them

Rust's compiler is the best teacher in the language and almost nobody reads it properly. Below are the 22 errors you will actually hit, roughly in the order you'll hit them, with what each one is really telling you.

Every diagnostic here is real. Each one was produced by compiling a small broken program with rustc 1.96.0 (ac68faa20 2026-05-25), not written from memory. You can reproduce all of them.

Two things to know before the list.

Most Rust errors underline two places. One is where the rule was broken. The other is where the decision that broke it was made. The error is reported at the first; the fix is almost always at the second.

The help: block is often a literal diff. When you see +++ under a span, rustc isn't describing the fix. It's writing it.

1. E0061: The function takes two arguments and you passed one.

error[E0061]: this function takes 2 arguments but 1 argument was supplied
 --> src/main.rs:6:20
  |
6 |     println!("{}", add(1));
  |                    ^^^--- argument #2 of type `i32` is missing
  |
note: function defined here
 --> src/main.rs:1:4
  |
1 | fn add(a: i32, b: i32) -> i32 {
  |    ^^^         ------
help: provide the argument
  |
6 |     println!("{}", add(1, /* i32 */));
  |                         +++++++++++
Enter fullscreen mode Exit fullscreen mode

The fix. Pass both: add(1, 2).

Rust has no default parameters and no overloading, so arity is exact. That sounds restrictive until you notice it means a function signature is a complete description of how to call it. There is never a hidden second way.

2. E0277: Vec has no single obvious way to print itself, so {} will not take it.

error[E0277]: `Vec<{integer}>` doesn't implement `std::fmt::Display`
 --> src/main.rs:3:20
  |
3 |     println!("{}", numbers);
  |               --   ^^^^^^^ `Vec<{integer}>` cannot be formatted with the default formatter
  |               |
  |               required by this formatting parameter
  |
  = help: the trait `std::fmt::Display` is not implemented for `Vec<{integer}>`
  = note: in format strings you may be able to use `{:?}` (or {:#?} for pretty-print) instead
Enter fullscreen mode Exit fullscreen mode

The fix. Use the debug formatter: println!("{:?}", numbers);

This is the first trait error most people meet, and the message names the exact missing capability: Vec<i32> doesn't implement std::fmt::Display. Read trait errors as a sentence (this type cannot do that thing) rather than as a wall.

3. E0282: Rust cannot work out what type this collection holds, because nothing was ever put in it.

error[E0282]: type annotations needed for `Vec<_>`
 --> src/main.rs:2:9
  |
2 |     let items = Vec::new();
  |         ^^^^^   ---------- type must be known at this point
  |
help: consider giving `items` an explicit type, where the type for type parameter `T` is specified
  |
2 |     let items: Vec<T> = Vec::new();
  |              ++++++++
Enter fullscreen mode Exit fullscreen mode

The fix. Say it: let items: Vec<i32> = Vec::new();

Rust infers types from use, not from declaration. Push one integer and the annotation becomes unnecessary, because the inference works forward from evidence, and here you gave it none.

4. E0308: You promised an i32 and returned nothing.

error[E0308]: mismatched types
 --> src/main.rs:1:14
  |
1 | fn five() -> i32 {
  |    ----      ^^^ expected `i32`, found `()`
  |    |
  |    implicitly returns `()` as its body has no tail or `return` expression
2 |     5;
  |      - help: remove this semicolon to return this value
Enter fullscreen mode Exit fullscreen mode

The fix. Delete the semicolon after 5.

() is Rust's word for "nothing". Whenever you see found (), look for a stray semicolon before you look at anything else. In Rust an expression without a semicolon is the value, and with one it becomes a statement that evaluates to nothing.

5. E0381: The variable was declared but never given a value, and you read it.

error[E0381]: used binding `count` isn't initialized
 --> src/main.rs:3:16
  |
2 |     let count: i32;
  |         ----- binding declared here but left uninitialized
3 |     println!("{count}");
  |                ^^^^^ `count` used here but it isn't initialized
  |
help: consider assigning a value
  |
2 |     let count: i32 = 42;
  |                    ++++
Enter fullscreen mode Exit fullscreen mode

The fix. Give it a value: let count: i32 = 0;

Other languages hand you a zero or a null here. Rust refuses to guess, and that refusal is the point: there is no such thing as an uninitialised read in safe Rust, so a whole category of bug cannot reach runtime.

6. E0384: let bindings do not change unless you say mut.

error[E0384]: cannot assign twice to immutable variable `x`
 --> src/main.rs:3:5
  |
2 |     let x = 5;
  |         - first assignment to `x`
3 |     x = 6;
  |     ^^^^^ cannot assign twice to immutable variable
  |
help: consider making this binding mutable
  |
2 |     let mut x = 5;
  |         +++
Enter fullscreen mode Exit fullscreen mode

The fix. Write let mut x = 5;

Two spans, not one: the ^^^^^ is where the rule broke, the - marks where the decision was made. The error is reported at the assignment, the thing you change is the declaration. That pattern holds across most Rust errors, and once you see it you cannot unsee it.

7. E0425: You used a name that was never declared in this scope.

error[E0425]: cannot find value `count` in this scope
 --> src/main.rs:2:20
  |
2 |     println!("{}", count);
  |                    ^^^^^ not found in this scope
Enter fullscreen mode Exit fullscreen mode

The fix. Declare it first: let count = 0;

Ninety percent of the time this is a typo, and rustc will often guess the name you meant in a help: line. Read that before you go looking for a real bug, because the compiler has already done the search.

8. E0596: You called a method that needs to modify the value, on a binding that cannot be modified.

error[E0596]: cannot borrow `numbers` as mutable, as it is not declared as mutable
 --> src/main.rs:3:5
  |
3 |     numbers.push(4);
  |     ^^^^^^^ cannot borrow as mutable
  |
help: consider changing this to be mutable
  |
2 |     let mut numbers = vec![1, 2, 3];
  |         +++
Enter fullscreen mode Exit fullscreen mode

The fix. let mut numbers = vec![1, 2, 3];

The interesting part is that push never says "mutable" in your code; you have to know its signature takes &mut self. The compiler is telling you a fact about a function you did not write, which is why the fix is on a different line from the error.

9. E0599: That type has no such method.

error[E0599]: no method named `push` found for type `{integer}` in the current scope
 --> src/main.rs:3:11
  |
3 |     count.push(1);
  |           ^^^^ method not found in `{integer}`
Enter fullscreen mode Exit fullscreen mode

The fix. Use a type that has it, or the right method for this one.

When the type is right but the method is missing, rustc often lists candidates with similar names, and sometimes tells you a trait exists but is not in scope, which is a use statement away, not a redesign.

10. E0601: Every Rust program starts at a function called main, and there isn't one.

error[E0601]: `main` function not found in crate `snippet`
 --> src/main.rs:3:2
  |
3 | }
  |  ^ consider adding a `main` function to src/main.rs`
Enter fullscreen mode Exit fullscreen mode

The fix. Rename the function to main.

The compiler is not looking for your code, it is looking for one specific name. Rust has no "top level" that runs. The entry point is a convention with no flexibility, and this is usually the first time a beginner learns the program has a designated front door.

11. E0618: You put call parentheses after something that is not a function.

error[E0618]: expected function, found `{integer}`
 --> src/main.rs:3:18
  |
2 |     let total = 5;
  |         ----- `total` has type `{integer}`
3 |     let result = total();
  |                  ^^^^^--
  |                  |
  |                  call expression requires function
Enter fullscreen mode Exit fullscreen mode

The fix. Drop the parentheses, or call the function you meant.

Usually a shadowed name: a variable and a function with the same identifier, and the variable won. The help: line will tell you what the thing actually is, which is faster than re-reading your own code.

12. E0765: A string was opened with a quote and never closed.

error[E0765]: unterminated double quote string
 --> src/main.rs:3:25
  |
3 |       println!("{greeting}");
  |  _________________________^
4 | | }
  | |_^
Enter fullscreen mode Exit fullscreen mode

The fix. Close the quote: let greeting = "hello";

Notice the error points at where the string started, not where the compiler gave up. That is the general pattern for unclosed things: the reported line is the opening, because the compiler cannot know which of the following lines you meant to close it on.

13. E0004: A match has to cover every possible case and yours misses one.

error[E0004]: non-exhaustive patterns: `Direction::South` not covered
  --> src/main.rs:8:11
   |
 8 |     match d {
   |           ^ pattern `Direction::South` not covered
   |
note: `Direction` defined here
  --> src/main.rs:1:6
   |
 1 | enum Direction {
   |      ^^^^^^^^^
 2 |     North,
 3 |     South,
   |     ----- not covered
   = note: the matched value is of type `Direction`
help: ensure that all possible cases are being handled by adding a match arm with a wildcard pattern or an explicit pattern as shown
   |
 9 ~         Direction::North => println!("up"),
10 ~         Direction::South => todo!(),
   |
Enter fullscreen mode Exit fullscreen mode

The fix. Add the missing arm, or a _ => {} catch-all.

This is the feature you will miss most in other languages. Add a variant to the enum a year later and every match that forgot to handle it fails to compile. The compiler maintains your switch statements for you.

14. E0005: That pattern can fail, and let has nowhere to go when it does.

error[E0005]: refutable pattern in local binding
 --> src/main.rs:3:9
  |
3 |     let Some(value) = maybe;
  |         ^^^^^^^^^^^ pattern `None` not covered
  |
  = note: `let` bindings require an "irrefutable pattern", like a `struct` or an `enum` with only one variant
  = note: for more information, visit https://doc.rust-lang.org/book/ch19-02-refutability.html
  = note: the matched value is of type `Option<i32>`
help: you might want to use `let...else` to handle the variant that isn't matched
  |
3 |     let Some(value) = maybe else { todo!() };
  |                             ++++++++++++++++
Enter fullscreen mode Exit fullscreen mode

The fix. Use if let Some(value) = maybe { … } or let Some(value) = maybe else { … };

let must always succeed, so it only accepts patterns that cannot fail. This is where if let and let … else come from: they are the versions with somewhere to put the failure.

15. E0063: You built a struct without all of its fields.

error[E0063]: missing field `y` in initializer of `Point`
 --> src/main.rs:7:13
  |
7 |     let p = Point { x: 1 };
  |             ^^^^^ missing `y`
Enter fullscreen mode Exit fullscreen mode

The fix. Supply y too.

There is no partially-built struct in Rust. Every field is set at construction or the value does not exist, which is why you never have to check whether a field was initialised.

16. E0070: The left side of = has to be something that can be assigned to.

error[E0070]: invalid left-hand side of assignment
 --> src/main.rs:2:7
  |
2 |     5 = 6;
  |     - ^
  |     |
  |     cannot assign to this expression
Enter fullscreen mode Exit fullscreen mode

The fix. Assign to a variable, or use == if you meant to compare.

Almost always a == typed as =. In C this class of typo silently compiles inside an if; Rust rejects it because a condition must be a bool and an assignment is not one.

17. E0382: The value moved to a new owner, and you used the old name afterwards.

error[E0382]: borrow of moved value: `name`
 --> src/main.rs:4:16
  |
2 |     let name = String::from("crab");
  |         ---- move occurs because `name` has type `String`, which does not implement the `Copy` trait
3 |     let other = name;
  |                 ---- value moved here
4 |     println!("{name}");
  |                ^^^^ value borrowed here after move
  |
help: consider cloning the value if the performance cost is acceptable
  |
3 |     let other = name.clone();
  |                     ++++++++
Enter fullscreen mode Exit fullscreen mode

The fix. Clone it, or borrow with &name, or use other.

Read the note: rather than the error: move occurs because String does not implement the Copy trait. That single line is the whole ownership model. An i32 in the same code would have been fine, because copying eight bytes is free and copying a heap allocation is not.

18. E0423: You used a struct's name where a value was expected.

error[E0423]: expected value, found struct `Config`
 --> src/main.rs:6:13
  |
1 | / struct Config {
2 | |     debug: bool,
3 | | }
  | |_- `Config` defined here
...
6 |       let c = Config;
  |               ^^^^^^ help: use struct literal syntax instead: `Config { debug: val }`
Enter fullscreen mode Exit fullscreen mode

The fix. Construct it: Config { debug: true }.

A struct name is a type, not a value, unless it is a unit struct, in which case the name is the value. That inconsistency is real and worth knowing, and the error message tells you which case you are in.

19. E0428: Two things with the same name in the same scope.

error[E0428]: the name `total` is defined multiple times
 --> src/main.rs:5:1
  |
1 | fn total() -> i32 {
  | ----------------- previous definition of the value `total` here
...
5 | fn total() -> i32 {
  | ^^^^^^^^^^^^^^^^^ `total` redefined here
  |
  = note: `total` must be defined only once in the value namespace of this module
Enter fullscreen mode Exit fullscreen mode

The fix. Rename one of them.

No overloading in Rust: one name, one item, per scope. This is the language deciding that "which one did I call?" should never be a question you have to answer.

20. E0432: The path in your use statement does not exist.

error[E0432]: unresolved import `std::collections::HashMapp`
 --> src/main.rs:1:5
  |
1 | use std::collections::HashMapp;
  |     ^^^^^^^^^^^^^^^^^^^^^^^^^^ no `HashMapp` in `collections`
  |
help: a similar name exists in the module
  |
1 - use std::collections::HashMapp;
1 + use std::collections::HashMap;
  |
Enter fullscreen mode Exit fullscreen mode

The fix. Fix the spelling: HashMap.

Import errors surface before type errors, so a single typo in a use line can produce a cascade of unrelated-looking failures below it. Always fix the topmost error first and recompile. Most of the rest often vanish.

21. E0433: The type exists in the standard library but has not been brought into scope.

error[E0433]: cannot find type `HashMap` in this scope
 --> src/main.rs:2:18
  |
2 |     let scores = HashMap::new();
  |                  ^^^^^^^ use of undeclared type `HashMap`
  |
help: consider importing this struct
  |
1 + use std::collections::HashMap;
  |
Enter fullscreen mode Exit fullscreen mode

The fix. use std::collections::HashMap;

Rust's prelude is deliberately tiny: only the handful of items nearly every program needs. Everything else you import by hand, and rustc will usually print the exact use line, which you can paste.

22. E0609: That struct has no field by that name.

error[E0609]: no field `z` on type `Point`
 --> src/main.rs:8:22
  |
8 |     println!("{}", p.z);
  |                      ^ unknown field
  |
help: a field with a similar name exists
  |
8 -     println!("{}", p.z);
8 +     println!("{}", p.x);
  |
Enter fullscreen mode Exit fullscreen mode

The fix. Use a field that exists, or add it to the struct.

rustc lists the fields that do exist, which makes this one of the errors you can fix without leaving the terminal. Worth noticing: the available-fields list is the compiler volunteering information you did not ask for.


The pattern behind all of them

Once you've read a few hundred of these, they stop being 22 separate errors and become four questions:

  1. Who owns this value, and did I give it away? Ownership errors.
  2. How long does this reference need to live? Lifetime errors.
  3. Does this type do the thing I'm asking of it? Trait errors.
  4. Did I say what I meant? Type, scope and mutability errors.

That's the language's whole difficulty curve, and the compiler tells you which of the four you're in every single time.


If you want to practise these against a real compiler without installing anything, that's what I built codecrab for: actual rustc output, the failing span underlined in your own source, one hint at a time. 94 exercises. Exercises derive from rustlings (MIT).

There'll be a paid tier later, chapters 6 and up, because compiling costs real money and I'd like this to still exist in 2028. Anyone with an account before that ships keeps the whole course.

https://codecrab.app/

Top comments (0)