When the red text stops being helpful
You write a function that expects a string slice. You pass an owned String instead. The compiler paints your terminal red and hands you error[E0308]: mismatched types. It even suggests adding an ampersand. You apply the suggestion, and your program crashes at runtime because you accidentally moved a value you still needed. The quick fix broke your logic. You need to understand the exact rule the compiler is enforcing, not just a surface-level hint.
You already have the answer. You just need to ask the right question. The compiler does not just reject code. It carries a complete documentation engine inside its binary. Every error code maps to a detailed explanation file. You can pull that explanation out with a single command. No browser tabs. No guessing which Stack Overflow thread matches your version. The tool that stopped you also holds the manual.
The compiler carries its own manual
rustc --explain turns the compiler into a reference guide. When you run rustc --explain E0308, the compiler stops compiling and starts printing the documentation attached to that error code. The output contains the rule definition, common causes, concrete examples, and the exact conditions that trigger the rejection.
The command is locked to your installed compiler version. If you run Rust 1.78, you get the explanation for Rust 1.78. Error messages evolve. The Rust team refines diagnostics, adds new suggestions, and occasionally changes how traits interact. The explanation you receive matches the rules your compiler is actually enforcing. You get the truth for your environment, not a cached blog post from three years ago.
Error codes are the stable anchor. E0308 has always meant mismatched types, and it will continue to mean mismatched types. The surrounding text improves over time, but the code stays fixed. This stability turns the code into a reliable lookup handle. Treat the code as a page number in a living manual.
Minimal example: pulling a page from the manual
Start with a straightforward type mismatch. The compiler emits a code. You use that code to retrieve the full explanation.
Here is the smallest case that triggers a type mismatch.
/// This function expects a borrowed string slice.
fn greet(name: &str) {
// &str is a view into string data. It does not own the memory.
println!("Hello, {name}");
}
fn main() {
// String owns its heap allocation. It is a different type than &str.
let name = String::from("World");
// The compiler rejects this with E0308.
greet(name);
}
Compile this file. The terminal prints a diagnostic block.
error[E0308]: mismatched types
--> src/main.rs:9:11
|
9 | greet(name);
| ^^^^ expected `&str`, found `String`
|
= note: expected reference `&str`
found struct `String`
help: consider borrowing here
|
9 | greet(&name);
| +
The code is E0308. Run the explain command.
rustc --explain E0308
The terminal prints a structured document. It starts with the error title. It explains that mismatched types occur when the compiler expects one type but finds another. It lists common scenarios. It shows code that triggers the error and code that resolves it. It tells you exactly why the compiler refuses to automatically coerce a String into an &str in this specific context.
The output is plain text. It can span several pages. Pipe it to a pager if your terminal scrolls too fast.
rustc --explain E0308 | less
Treat the error code as a lookup key. Turn it in the lock.
How the lookup actually works
The --explain flag does not query a remote server. It reads files generated during the compiler build. The Rust compiler source contains a directory of markdown files, one for every error code. When rustc is compiled, those files are extracted, formatted, and embedded directly into the binary. When you run --explain, the compiler loads the stored text and prints it to standard output.
This design makes the command fast. It works offline. It works on a machine with no internet connection. The documentation is always available because it lives inside the tool you already have installed. You never wait for a network request to understand why your code failed.
The output structure follows a consistent pattern. You will see sections like "Error Explanation", "Causes", and "Examples". The explanation section describes the rule. The causes section lists why the rule might be violated. The examples section shows code that triggers the error and code that fixes it. Read the examples carefully. They often reveal edge cases you have not considered.
Convention aside: The community treats rustc --explain as the source of truth for error semantics. If a tutorial claims an error means something different, check --explain. The compiler documentation overrides external sources. Trust the binary over the blog.
Reading the output like a developer
The output contains more than a definition. It includes metadata and version information. Look for the header at the top. It shows the Rust version and the error code. Verify the version matches your toolchain. If you are following a guide and the error code is the same but the explanation differs, the guide is outdated.
The explanation often references other error codes. If you see a mention of E0599 inside the E0308 explanation, it means the two errors interact. You can chain lookups. Run rustc --explain E0599 to get the full context. This helps you build a mental model of how errors relate to each other.
Some explanations include "Help" sections that mirror the suggestions the compiler gives inline. The inline suggestion is a quick patch. The explanation tells you why the patch works. Use the explanation to understand the underlying mechanism, not just to silence the red text.
The output also includes a link to the online documentation. If you prefer reading in a browser, copy the URL from the output. The online version is the same content, just rendered with HTML. The command-line version is faster for quick checks. Keep your workflow in the terminal when you are debugging.
Read the causes section before the examples. The causes section tells you what the compiler was looking for. The examples section shows you how to give it what it wants.
Realistic example: trait bounds and design intent
Inline errors can be terse when trait bounds are involved. The compiler tells you a trait is missing, but it might not explain why the trait is required. --explain fills the gap.
Here is a generic function that tries to print any type.
/// This function attempts to print a value of any type.
fn print_value<T>(value: T) {
// The compiler rejects this with E0277.
// T does not implement std::fmt::Display.
println!("{value}");
}
fn main() {
// Passing an integer triggers the missing trait bound.
print_value(42);
}
The error message says the trait bound T: std::fmt::Display is not satisfied. It suggests adding a bound. You add the bound, and the code compiles. You might not understand why println! requires Display. You might not know why you cannot print a custom struct without implementing a trait.
Run the explain command.
rustc --explain E0277
The output explains trait bounds in depth. It describes how println! uses the Display trait to format values. It explains that Display is for user-facing output, while Debug is for developer-facing output. It shows how to implement Display for a custom type. It clarifies the difference between the two formatting traits.
This context changes how you write code. You learn that println! requires Display, but dbg! requires Debug. You learn that adding #[derive(Debug)] to a struct lets you use dbg! without writing boilerplate. The explanation turns a syntax error into a lesson on Rust's formatting system.
Use the explanation to understand the design, not just the syntax.
Pitfalls and workflow habits
The --explain flag lives in rustc, not cargo. cargo is a build manager. It delegates compilation to rustc. If you run cargo --explain E0308, you get a help message for cargo, not the error explanation. You must use rustc directly.
This distinction matters in your daily workflow. When you are debugging, you might be using cargo check to get fast error messages. cargo check calls rustc under the hood. When you need an explanation, switch to rustc --explain. You do not need to change your build process. You just use a different command for the lookup.
Error codes are stable, but new codes appear in every release. If you see an error code you have not seen before, --explain still works. The compiler includes explanations for all current codes. If a code is brand new, the explanation might be brief. The Rust team adds detailed explanations for new errors quickly.
Some errors do not have codes. This is rare in modern Rust. Most errors have codes. If you see an error without a code, --explain will not help. Search the error message text instead. The lack of a code usually indicates a compiler internal error or a very specific plugin issue.
Convention aside: Copy the error code from the terminal. Do not type it manually. The code is usually highlighted in the error output. Selecting it avoids typos. A typo in the code results in a "no explanation found" message, which wastes time.
The output can be verbose. Use grep to search the output if you are looking for a specific keyword.
rustc --explain E0277 | grep -i "derive"
This helps you find relevant sections quickly. The explanation is a document. Treat it like one. Search it, skim it, and read the parts that matter.
Don't fight the red text. Decode it.
When to use --explain vs other tools
Use rustc --explain when you have a specific error code and want the definitive explanation for your compiler version. Use web search when the error code is obscure, the explanation is unclear, or you need a community workaround that the compiler does not mention. Use the Rust Book when you need to understand the underlying concept, like ownership or lifetimes, rather than the syntax of a specific error. Use cargo doc --open when you need API documentation for a type or function, not error explanations.
The compiler knows why it rejected your code. Ask it directly.