Learning Rust is like learning to ride a bike with training wheels that you can never remove because those training wheels are what keep you from crashing into memory safety bugs. Sure, the borrow checker might seem like it's nagging you constantly, but that's because it's preventing you from writing code that would blow up at runtime in other languages. The good news? Once you learn the rules, Rust's compiler becomes less of an adversary and more of a helpful (if strict) mentor. To help you get there faster, this cheat sheet covers the essential commands, rules, and error fixes that every Rust programmer needs to have at their fingertips.
Essential Cargo commands
Cargo is Rust's build system and package manager, and you'll use it constantly when working with Rust. Here are the Cargo commands you need to memorize:
Create a new project
Start a new Rust project with all the basic structure already set up:
cargo new project-nameThis command creates a new directory called project-name with a src/main.rs file and a Cargo.toml configuration file.
Create a new library
If you want to create a library instead of a binary:
cargo new project-name --lib This command creates the same structure, but with src/lib.rs instead of src/main.rs.
Build your project
Compile your code without running it:
cargo buildThis command creates an executable in target/debug/. The debug build includes debugging symbols and doesn't optimize your code.
Build for release
Compile your code with optimizations enabled:
cargo build --releaseThis command takes longer to compile but produces a faster executable in target/release/.
Run your project
Compile and run your code in one step:
cargo runThis command is equivalent to running cargo build followed by running the executable. If your code hasn't changed since the last build, Cargo just runs the existing executable.
Check your code
Verify that your code compiles without producing an executable:
cargo checkThis command is much faster than cargo build and is perfect for quick syntax checking.
Run tests
Execute all the tests in your project:
cargo testCargo runs all functions marked with the #[test] attribute.
Format your code
Automatically format your code according to Rust style guidelines:
cargo fmtThis command ensures that your code looks consistent and follows community standards.
Lint your code
Get helpful suggestions for improving your code:
cargo clippyClippy is Rust's official linter and will catch common mistakes and suggest more idiomatic code.
Update dependencies
Update your project's dependencies to their latest compatible versions:
cargo updateThis command respects the version requirements in your Cargo.toml file.
Add a dependency
Add a new crate to your project:
cargo add crate-nameThis command adds the crate to your Cargo.toml and downloads it.
Generate documentation
Build HTML documentation for your project and all its dependencies:
cargo doc --openThe --open flag automatically opens the documentation in your browser.
Ownership and borrowing: the rules
Rust's ownership system is what makes it special (and sometimes frustrating). Here are the rules you need to understand.
The three ownership rules
Mercifully, ownership in Rust operates based on just three simple rules:
- Each value in Rust has an owner.
- There can only be one owner at a time.
- When the owner goes out of scope, the value will be dropped.
Moving ownership
When you assign a heap-allocated value (like String) to another variable, ownership moves:
let s1 = String::from("hello");
let s2 = s1; // s1 is no longer valid!
// println!("{}", s1); // This would cause an error Cloning
To make a deep copy and avoid moving ownership, use clone():
let s1 = String::from("hello");
let s2 = s1.clone();
println!("{}, {}", s1, s2); // Both are validBorrowing with references
Instead of transferring ownership, you can borrow a reference to a value:
let s1 = String::from("hello");
let len = calculate_length(&s1);
println!("{} has length {}", s1, len); // s1 still valid Mutable borrowing
You can have one mutable reference to a value:
let mut s = String::from("hello");
change(&mut s); The golden rule of borrowing
Borrowing in Rust can be hard to get your head around. But just remember the inviolable rule of borrowing in Rust, which says that, at any time, you can have only one of the following:
- Multiple immutable borrows
- One mutable borrow
This rule prevents data races at compile time. With these restrictions, Rust guarantees that
- Multiple readers are fine (immutable references)
- One writer is fine (mutable reference)
- Readers and writers can't coexist (prevents reading inconsistent data)
Common borrowing patterns
Here are a few borrowing scenarios that you’ll see over and over in your Rust career:
// OK: Multiple immutable borrows
let s = String::from("hello");
let r1 = &s;
let r2 = &s;
println!("{} and {}", r1, r2);
// OK: One mutable borrow
let mut s = String::from("hello");
let r1 = &mut s;
r1.push_str(", world");
// ERROR: Can't mix immutable and mutable borrows
let mut s = String::from("hello");
let r1 = &s; // immutable borrow
let r2 = &mut s; // ERROR! can't borrow as mutable
println!("{}", r1); 10 common Rust compiler errors and their fixes
The Rust compiler is famous for its helpful error messages, but when you're starting out, they can be confusing. Here are ten common errors and how to fix them:
Error: "cannot borrow as mutable"
let s = String::from("hello");
s.push_str(", world"); // ERROR!The Fix: Declare the variable as mutable:
let mut s = String::from("hello");
s.push_str(", world"); // OK! Error: "cannot borrow 'x' as mutable more than once"
let mut s = String::from("hello");
let r1 = &mut s;
let r2 = &mut s; // ERROR!The Fix: Keep only one mutable reference in scope at a time:
let mut s = String::from("hello");
let r1 = &mut s;
r1.push_str(" world");
// r1 is no longer used, so we can create r2
let r2 = &mut s;Error: "cannot borrow 'x' as mutable because it is also borrowed as immutable"
let mut s = String::from("hello");
let r1 = &s;
let r2 = &mut s; // ERROR!
println!("{}", r1);The Fix: Don't mix immutable and mutable borrows. Let the immutable references go out of scope first:
let mut s = String::from("hello");
let r1 = &s;
println!("{}", r1); // r1 is no longer used after this
let r2 = &mut s; // OK now!Error: "use of moved value"
let s1 = String::from("hello");
let s2 = s1;
println!("{}", s1); // ERROR!The Fix: Either clone the value or use a reference:
// Option 1: Clone
let s1 = String::from("hello");
let s2 = s1.clone();
println!("{}, {}", s1, s2);
// Option 2: Use a reference
let s1 = String::from("hello");
let s2 = &s1;
println!("{}, {}", s1, s2);Error: "missing lifetime specifier"
fn longest(x: &str, y: &str) -> &str { // ERROR!
if x.len() > y.len() { x } else { y }
}The Fix: Add lifetime annotations to tell Rust how the output relates to the inputs:
fn longest<'a>(x: &'a str, y: &'a str) -> &'a str {
if x.len() > y.len() { x } else { y }
} Error: "expected X, found Y" (type mismatch)
let x: i32 = "5"; // ERROR! expected i32, found &strThe Fix: Parse the string or use the correct type:
let x: i32 = "5".parse().unwrap(); // Converts string to i32
// Or just use the right type
let x = "5"; // Now x is &strError: "the trait 'X' is not implemented for 'Y'"
let s = String::from("hello");
let c = s + 5; // ERROR! can't add i32 to StringThe Fix: Use the correct method for the operation:
let s = String::from("hello");
let c = format!("{}{}", s, 5); Error: "cannot find value 'x' in this scope"
println!("{}", my_var); // ERROR! my_var not foundThe Fix: Make sure you've declared the variable before using it:
let my_var = 42;
println!("{}", my_var);Error: "mismatched types" in match expressions
let x = Some(5);
let result = match x {
Some(i) => i,
None => "nothing", // ERROR! i32 vs &str
};The Fix: Ensure that all arms of the match return the same type:
let x = Some(5);
let result = match x {
Some(i) => i,
None => 0, // Both arms now return i32
};Error: "returns a value referencing data owned by the current function" (dangling reference)
fn dangle() -> &String { // ERROR!
let s = String::from("hello");
&s // s goes out of scope here
}The Fix: Return the owned value instead of a reference:
fn no_dangle() -> String {
let s = String::from("hello");
s // Ownership is moved to caller
}

