Rust All-in-One For Dummies
Rust All-in-One For Dummies book cover
Explore Book
Buy NowSubscribe on Perlego
Rust All-in-One For Dummies
Rust All-in-One For Dummies book coverExplore Book
Buy NowSubscribe on Perlego

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-name

This 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 build

This 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 --release

This 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 run

This 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 check

This command is much faster than cargo build and is perfect for quick syntax checking.

Run tests

Execute all the tests in your project:

cargo test

Cargo runs all functions marked with the #[test] attribute.

Format your code

Automatically format your code according to Rust style guidelines:

cargo fmt

This command ensures that your code looks consistent and follows community standards.

Lint your code

Get helpful suggestions for improving your code:

cargo clippy

Clippy 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 update

This command respects the version requirements in your Cargo.toml file.

Add a dependency

Add a new crate to your project:

cargo add crate-name

This 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 --open

The --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 valid

Borrowing 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 &str

The 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 &str

Error: "the trait 'X' is not implemented for 'Y'"

let s = String::from("hello");
let c = s + 5;  // ERROR! can't add i32 to String

The 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 found

The 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
}

About This Article

This article is from the book: 

About the book author:

Paul McFedries is a Google® Workspace administrator, a thankless job if ever there was one. Paul is also a full-time technical writer who has somehow found the time to write more than 100 books that have sold more than four million copies worldwide.