// comprehensive guide · 2024 edition

The Rust Programming
Language

A fast, safe, and concurrent systems language.
For developers who already know how to code — and want to know how to code better.

Overview & Philosophy

Rust is a systems programming language built on three pillars: performance, reliability, and productivity. It competes with C and C++ in raw speed while eliminating entire classes of bugs at compile time.

⚡ Performance

No garbage collector. No runtime overhead. Compiled to native machine code. Zero-cost abstractions means you pay only for what you use.

🛡 Reliability

The ownership system guarantees memory safety and thread safety without a GC. If it compiles, it almost certainly won't segfault or race.

🔧 Productivity

Expressive type system, pattern matching, great tooling (Cargo), and error messages that actually help you fix the problem.

🔬 Use Cases

WebAssembly, OS kernels, game engines, CLI tools, networking, embedded systems, and anything that used to need C or C++.

How Rust differs from languages you know

ConceptC / C++Python / JS / JavaRust
Memory managementManual malloc/freeGarbage collectorOwnership rules (compile-time)
Null pointersYes — undefined behaviorYes — NullPointerExceptionNo null; use Option<T>
Runtime errorsSegfaults, UBExceptions at runtimeMost caught at compile time
Concurrency safetyData races possibleGIL / runtime checksCompile-time thread safety
AbstractionsOften have costRuntime costZero-cost (erased at compile time)
Package managerNone standardpip / npmCargo (built-in, excellent)

Installation & Tooling

Rust ships with the rustup toolchain manager, the rustc compiler, and Cargo — its build system and package manager. All three install together in one command.

Install (all platforms)

# macOS / Linux — run in terminal:
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh

# Windows — download rustup-init.exe from rustup.rs

# Verify installation:
rustc --version
cargo --version

Essential Cargo Commands

CommandWhat it does
cargo new my_projectCreate a new binary project
cargo new --lib my_libCreate a library crate
cargo buildCompile (debug mode)
cargo build --releaseCompile with full optimizations
cargo runBuild and execute
cargo testRun all tests
cargo checkType-check without producing a binary (fast!)
cargo add serdeAdd a dependency from crates.io
cargo doc --openBuild and open documentation
cargo fmtAuto-format your code
cargo clippyLint for common mistakes and anti-patterns

Project Structure

my_project/
├── Cargo.toml       ← project manifest (dependencies, metadata)
├── Cargo.lock       ← locked dependency versions (commit this!)
└── src/
    ├── main.rs      ← entry point for binaries
    └── lib.rs       ← root of a library crate
💡 Tip — Editor Setup
Use VS Code with the rust-analyzer extension for best-in-class autocompletion, inline errors, and refactoring. JetBrains RustRover and Zed also provide excellent Rust support.

Syntax Quick-Reference

A dense cheat-sheet — every construct you'll use daily, side by side with the equivalent in C/Python to make the comparison concrete.

Comments

// Single-line comment

/* Multi-line comment */

/// Doc comment (generates HTML docs for the item below)
/// Supports **Markdown**.
fn my_function() {}

Variables & Mutability

let x = 5;               // immutable by default — like const in JS
let mut y = 10;          // mutable — you must be explicit
const MAX: u32 = 100_000; // constant — type annotation required

// Shadowing — re-declare a name in same scope
let x = x + 1;           // x is now 6, still immutable
let x = x.to_string();   // can even change type via shadowing

Primitive Types

CategoryTypesNotes
Signed integersi8 i16 i32 i64 i128 isizei32 is default
Unsigned integersu8 u16 u32 u64 u128 usizeusize for indexing
Floatsf32 f64f64 is default
Boolbooltrue / false
CharactercharUnicode scalar, 4 bytes, 'A'
Tuple(i32, f64, bool)Fixed length, mixed types
Array[i32; 5]Fixed length, same type, stack-allocated

Type Annotations & Casting

let n: i64 = 42;
let f: f32 = 3.14;
let b: bool = true;
let c: char = '🦀';

// Explicit casting with `as`
let x: i32 = 5;
let y: f64 = x as f64;    // Rust never implicitly casts

Operators

CategoryOperators
Arithmetic+ - * / %
Comparison== != < > <= >=
Logical&& || !
Bitwise& | ^ << >> !
Assignment= += -= *= /=
Reference& &mut * (borrow / dereference)
Range0..5 (exclusive), 0..=5 (inclusive)

Strings

// &str — string slice (borrowed, immutable view into string data)
let s1: &str = "Hello, world!";

// String — heap-allocated, growable
let mut s2: String = String::from("Hello");
s2.push_str(", world!");
s2.push('!');              // push a single char

// String formatting
let name = "Rustacean";
let greeting = format!("Hello, {}!", name);

// Common methods
s1.len()          // byte length
s1.is_empty()    // true if ""
s1.contains("lo") // substring check
s1.to_uppercase()
s1.trim()         // strip whitespace
s1.split(' ')     // returns an Iterator

Types & Variables

Rust uses static, strong typing with powerful inference. Once a variable has a type, it never changes — but shadowing lets you rebind a name to a new value of a different type.

Tuples

let tup: (i32, f64, bool) = (42, 3.14, true);

// Access by index
let x = tup.0;   // 42

// Destructuring
let (a, b, c) = tup;
println!("a={} b={} c={}", a, b, c);

Arrays

let arr: [i32; 5] = [1, 2, 3, 4, 5];
let zeros = [0; 10];      // [0, 0, 0, 0, 0, 0, 0, 0, 0, 0]

arr[0]                   // first element (panics if out of bounds)
arr.len()               // 5
&arr[1..3]              // slice reference: [2, 3]

Type Inference in Action

// Rust infers types from context
let v = Vec::new();     // ❌ error — can't infer T yet
let v: Vec<i32> = Vec::new();  // ✅ annotate explicitly
let v = Vec::<i32>::new(); // ✅ turbofish syntax
let mut v = Vec::new();
v.push(1_i32);          // ✅ inferred from first push

Control Flow

Rust's control flow features are expression-oriented — most constructs return values. This is different from C/Java where they are purely statements.

if / else if / else

let n = 7;

// if is an expression — can return a value
let label = if n % 2 == 0 { "even" } else { "odd" };

if n < 0 {
    println!("negative");
} else if n == 0 {
    println!("zero");
} else {
    println!("positive");
}

Loops

// loop — unconditional, like while(true)
let mut count = 0;
let result = loop {
    count += 1;
    if count == 10 { break count * 2; } // break with a value!
};

// while loop
let mut i = 0;
while i < 5 {
    print!("{} ", i);
    i += 1;
}

// for loop — iterate over a range or collection
for n in 0..5 {           // 0, 1, 2, 3, 4
    print!("{} ", n);
}

for n in 0..=5 {          // 0, 1, 2, 3, 4, 5
    print!("{} ", n);
}

// Iterate over a collection
let fruits = ["apple", "banana", "cherry"];
for fruit in fruits.iter() {
    println!("I like {}", fruit);
}

// Loop with index using enumerate()
for (i, fruit) in fruits.iter().enumerate() {
    println!("{}: {}", i, fruit);
}

// Loop labels — break outer loops
'outer: for x in 0..5 {
    for y in 0..5 {
        if x + y == 6 { break 'outer; }
    }
}

Functions

Functions in Rust use the fn keyword. The last expression in a function body is its return value — no return keyword needed (though you can use it for early returns).

// Basic function
fn greet(name: &str) {
    println!("Hello, {}!", name);
}

// Function with return type
fn add(a: i32, b: i32) -> i32 {
    a + b     // no semicolon = return this expression
}

// Explicit return (for early exit)
fn divide(a: f64, b: f64) -> f64 {
    if b == 0.0 { return 0.0; }
    a / b
}

// Multiple return values via tuple
fn min_max(v: &[i32]) -> (i32, i32) {
    let mut min = v[0];
    let mut max = v[0];
    for &n in &v[1..] {
        if n < min { min = n; }
        if n > max { max = n; }
    }
    (min, max)
}

let (lo, hi) = min_max(&[3, 1, 9, 2, 7]);  // lo=1, hi=9
ℹ️ Statements vs Expressions
In Rust, statements end with ; and don't return a value. Expressions evaluate to a value. Adding a ; to an expression turns it into a statement. This is why function bodies end without a semicolon on the return value.

Ownership

Ownership is Rust's most unique feature — it's how memory safety is achieved at compile time with no garbage collector. Master this and you master Rust.

The Three Rules

Rule 1

Every value in Rust has exactly one owner — a variable that "owns" the data.

Rule 2

There can only be one owner at a time. You can't have two variables simultaneously owning the same heap data.

Rule 3

When the owner goes out of scope, the value is automatically dropped (memory freed). No GC needed.

Move Semantics

let s1 = String::from("hello");
let s2 = s1;   // s1 is MOVED into s2. s1 is no longer valid.
// println!("{}", s1);  ❌ compile error: value borrowed after move
println!("{}", s2);   // ✅ s2 owns the string now

// Primitive types (i32, f64, bool, char) implement Copy
// — they are copied rather than moved.
let x = 5;
let y = x;    // x is COPIED, not moved. Both x and y are valid.
println!("{} {}", x, y);  // ✅ fine

Clone — Explicit Deep Copy

let s1 = String::from("hello");
let s2 = s1.clone();    // deep copy — both are valid but independent
println!("{} and {}", s1, s2);  // ✅

Ownership and Functions

fn takes_ownership(s: String) {  // s is moved in
    println!("{}", s);
}   // s is dropped here — memory freed

fn gives_ownership() -> String {
    String::from("mine now")  // moved to the caller
}

let my_str = String::from("hello");
takes_ownership(my_str);    // my_str is MOVED, invalid after this

let new_str = gives_ownership();  // new_str owns the returned String

Borrowing & References

Instead of transferring ownership, you can borrow a value by creating a reference. References let you use a value without taking ownership of it.

Immutable References

fn calculate_length(s: &String) -> usize {
    s.len()   // we borrow s — we can read but not modify it
}   // s goes out of scope but is NOT dropped (we don't own it)

let s = String::from("hello");
let len = calculate_length(&s);  // & creates a reference
println!("'{}' has {} bytes", s, len);  // s still valid!

Mutable References

fn append_world(s: &mut String) {
    s.push_str(", world!");
}

let mut s = String::from("hello");
append_world(&mut s);
println!("{}", s);  // "hello, world!"

The Borrowing Rules

🔒 Rust's Borrowing Rules (enforced at compile time)
  • You may have any number of immutable references at one time.
  • OR you may have exactly one mutable reference — but not both simultaneously.
  • References must always be valid (no dangling pointers).
let mut s = String::from("hello");

let r1 = &s;
let r2 = &s;
// let r3 = &mut s;  ❌ Cannot borrow as mutable while immutably borrowed
println!("{} {}", r1, r2);  // r1 and r2 scope ends here

let r3 = &mut s;  // ✅ now fine — r1, r2 are no longer in use
r3.push_str(" world");

Slices — References to Contiguous Data

let s = String::from("hello world");
let hello: &str = &s[0..5];   // string slice
let world: &str = &s[6..];    // to end

let arr = [1, 2, 3, 4, 5];
let slice: &[i32] = &arr[1..3];  // [2, 3] — array slice

Lifetimes

Lifetimes tell the compiler how long references are valid. In most cases the compiler infers them (lifetime elision). You only write explicit lifetimes when the compiler needs help relating multiple references.

// Without lifetime annotations, this would be ambiguous:
// Which reference lives longer — x or y?
fn longest<'a>(x: &'a str, y: &'a str) -> &'a str {
    if x.len() > y.len() { x } else { y }
}
// 'a means: the returned reference lives as long as the shorter of x and y.

// Lifetimes in structs holding references
struct Excerpt<'a> {
    part: &'a str,
}

let text = String::from("Call me Ishmael. Some years ago...");
let first_sentence = text.split('.').next().unwrap();
let e = Excerpt { part: first_sentence };
// e.part cannot outlive `text`
💡 Lifetime Elision Rules
The compiler automatically adds lifetime annotations following three rules, so you often don't write them. Write explicit lifetimes only when the compiler asks you to.

Structs

Structs group related fields together. They're Rust's primary mechanism for creating custom data types — similar to classes, but without inheritance.

Defining and Using Structs

// Define a struct
struct Player {
    name:     String,
    health:   u32,
    level:    u32,
    is_alive: bool,
}

// Instantiate
let mut hero = Player {
    name:     String::from("Ferris"),
    health:   100,
    level:    1,
    is_alive: true,
};

// Access and modify fields
hero.health -= 20;
println!("{} has {} HP", hero.name, hero.health);

// Struct update syntax (like spread in JS)
let hero2 = Player {
    name: String::from("Ferris Jr."),
    level: 5,
    ..hero    // copy remaining fields from hero
};

Methods with impl

impl Player {
    // Associated function (constructor pattern)
    fn new(name: &str) -> Self {
        Player {
            name:     name.to_string(),
            health:   100,
            level:    1,
            is_alive: true,
        }
    }

    // Method (takes self by reference)
    fn describe(&self) {
        println!("[Lv{}] {} — {} HP", self.level, self.name, self.health);
    }

    // Mutable method (takes self by mutable reference)
    fn take_damage(&mut self, dmg: u32) {
        self.health = self.health.saturating_sub(dmg);
        if self.health == 0 { self.is_alive = false; }
    }

    // Consuming method (takes ownership)
    fn retire(self) -> String {
        format!("{} has retired!", self.name)
    }
}

let mut p = Player::new("Ferris");
p.describe();
p.take_damage(30);
p.describe();

Tuple Structs and Unit Structs

// Tuple struct — named tuple type
struct Color(u8, u8, u8);
let red = Color(255, 0, 0);
println!("R={}", red.0);

// Unit struct — no fields, used for traits
struct AlwaysReady;
let _ = AlwaysReady;

Enums & Option<T>

Rust's enums are far more powerful than C enums — each variant can hold different types and amounts of data. They're algebraic data types.

Basic Enums

enum Direction { North, South, East, West }

let dir = Direction::North;
match dir {
    Direction::North => println!("Going north!"),
    Direction::South => println!("Going south!"),
    _ => println!("Going somewhere else"),
}

Rich Enums — Variants with Data

enum Message {
    Quit,                          // no data
    Move { x: i32, y: i32 },     // named fields
    Write(String),                 // single String
    ChangeColor(u8, u8, u8),      // three u8s
}

fn handle(msg: Message) {
    match msg {
        Message::Quit                   => println!("Quitting"),
        Message::Move { x, y }          => println!("Move ({},{})", x, y),
        Message::Write(text)            => println!("Write: {}", text),
        Message::ChangeColor(r, g, b)  => println!("Color ({},{},{})", r, g, b),
    }
}

Option<T> — Replacing null

// Option is defined as: enum Option<T> { Some(T), None }
let some_number: Option<i32> = Some(42);
let no_number:   Option<i32> = None;

// Using Option values
if let Some(n) = some_number {
    println!("Got: {}", n);
}

let value = some_number.unwrap_or(0);      // 42
let value = no_number.unwrap_or(0);        // 0
let value = some_number.unwrap_or_else(|| compute_default());
let doubled = some_number.map(|n| n * 2); // Some(84)

Pattern Matching

match is one of Rust's crown jewels. It's exhaustive — the compiler forces you to handle every case — and can destructure complex data in one line.

match

let x: i32 = 7;
match x {
    1        => println!("one"),
    2 | 3    => println!("two or three"),
    4..=6   => println!("four to six"),
    n if n % 2 == 0 => println!("{} is even", n),   // guard
    n        => println!("other: {}", n),            // catch-all binding
}

// match on a struct
struct Point { x: i32, y: i32 }
let p = Point { x: 0, y: 7 };
match p {
    Point { x: 0, y } => println!("on y-axis at {}", y),
    Point { x, y: 0 } => println!("on x-axis at {}", x),
    Point { x, y }    => println!("at ({}, {})", x, y),
}

if let — Single-arm match

let config = Some("dark_mode");

// Verbose match:
match config {
    Some(val) => println!("Setting: {}", val),
    None      => {},
}

// Concise if let:
if let Some(val) = config {
    println!("Setting: {}", val);
}

while let

let mut stack = Vec::new();
stack.push(1); stack.push(2); stack.push(3);

while let Some(top) = stack.pop() {
    println!("{}", top);   // prints 3, 2, 1
}

Traits

Traits define shared behavior — they're similar to interfaces in Java/Go, or abstract base classes in Python. They're the foundation of Rust's polymorphism.

// Define a trait
trait Describable {
    fn describe(&self) -> String;

    // Default method — can be overridden
    fn print_description(&self) {
        println!("{}", self.describe());
    }
}

struct Car { make: String, year: u32 }
struct Bike { brand: String }

impl Describable for Car {
    fn describe(&self) -> String {
        format!("{} ({})", self.make, self.year)
    }
}

impl Describable for Bike {
    fn describe(&self) -> String {
        format!("Bike: {}", self.brand)
    }
}

// Trait bounds — accept any type implementing Describable
fn print_item(item: &impl Describable) {    // impl Trait syntax
    item.print_description();
}

// Generic form (equivalent)
fn print_item<T: Describable>(item: &T) {
    item.print_description();
}

Important Standard Traits

TraitPurposeNote
Displayprintln!("{}", x)Human-readable output
Debugprintln!("{:?}", x)Debugging output — can derive
Clonex.clone()Explicit deep copy — can derive
CopyImplicit copy on assignmentOnly for stack types — can derive
PartialEq== operatorCan derive
Ord / PartialOrdOrdering / comparisonCan derive
DefaultT::default()Zero-value constructor
IteratorIteration protocolImplement next()
// Deriving common traits automatically
#[derive(Debug, Clone, PartialEq)]
struct Point { x: f64, y: f64 }

let p = Point { x: 1.0, y: 2.0 };
println!("{:?}", p);         // Point { x: 1.0, y: 2.0 }
let p2 = p.clone();
println!("{}", p == p2);   // true

Generics

Generics let you write code that works with many different types while keeping all type-safety. Rust generics are monomorphized — the compiler creates specialized versions for each type used, so there's zero runtime cost.

// Generic function
fn largest<T: PartialOrd>(list: &[T]) -> &T {
    let mut largest = &list[0];
    for item in list {
        if item > largest { largest = item; }
    }
    largest
}

println!("{}", largest(&[34, 50, 25, 100]));  // 100
println!("{}", largest(&['y', 'm', 'a']));     // y

// Generic struct
struct Pair<T> { first: T, second: T }

impl<T: Display + PartialOrd> Pair<T> {
    fn cmp_display(&self) {
        if self.first >= self.second {
            println!("first is larger: {}", self.first);
        } else {
            println!("second is larger: {}", self.second);
        }
    }
}

Error Handling

Rust has no exceptions. Instead it uses the Result<T, E> type for recoverable errors and panic! for unrecoverable ones. This forces you to think about every error path.

Result<T, E>

// Result is: enum Result<T, E> { Ok(T), Err(E) }
use std::num::ParseIntError;

fn parse_and_double(s: &str) -> Result<i32, ParseIntError> {
    let n: i32 = s.parse()?;   // ? propagates the error if Err
    Ok(n * 2)
}

match parse_and_double("21") {
    Ok(n)  => println!("Result: {}", n),     // 42
    Err(e) => println!("Error: {}", e),
}

The ? Operator

use std::fs;
use std::io;

fn read_username_from_file() -> Result<String, io::Error> {
    // ? unwraps Ok or returns Err early — equivalent to:
    // match result { Ok(v) => v, Err(e) => return Err(e) }
    let content = fs::read_to_string("username.txt")?;
    Ok(content.trim().to_string())
}

Common Result Methods

let r: Result<i32, &str> = Ok(42);

r.unwrap()                  // get value or panic
r.unwrap_or(0)              // get value or default
r.unwrap_or_else(|e| 0)    // get value or compute default
r.expect("Failed to get n") // unwrap with custom panic message
r.is_ok()                   // true
r.is_err()                  // false
r.map(|n| n * 2)           // Ok(84) — transform Ok value
r.map_err(|e| e.len())     // transform Err value

Custom Error Types

use std::fmt;

#[derive(Debug)]
enum AppError {
    IoError(std::io::Error),
    ParseError(String),
}

impl fmt::Display for AppError {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        match self {
            AppError::IoError(e)    => write!(f, "IO error: {}", e),
            AppError::ParseError(s) => write!(f, "Parse error: {}", s),
        }
    }
}

Collections

The standard library provides three key heap-allocated collections: Vec<T>, HashMap<K,V>, and HashSet<T>.

Vec<T> — Dynamic Array

let mut v: Vec<i32> = Vec::new();
let mut v = vec![1, 2, 3];          // macro shorthand

v.push(4);
v.pop();                            // returns Option<T>
v.insert(1, 10);                    // insert at index 1
v.remove(1);                        // remove at index 1
v.len();
v.is_empty();
v.contains(&3);
v.sort();
v.sort_by(|a, b| b.cmp(a));       // reverse sort
v.iter().sumi32>()             // sum all elements

// Safe access
match v.get(10) {
    Some(val) => println!("{}", val),
    None      => println!("out of bounds"),
}

HashMap<K, V>

use std::collections::HashMap;

let mut scores: HashMap<String, u32> = HashMap::new();

scores.insert(String::from("Alice"), 100);
scores.insert(String::from("Bob"),   85);

// Only insert if key doesn't exist
scores.entry(String::from("Alice")).or_insert(50); // no-op
scores.entry(String::from("Carol")).or_insert(75); // inserts 75

// Access
if let Some(score) = scores.get("Alice") {
    println!("Alice: {}", score);
}

// Iterate
for (name, score) in &scores {
    println!("{}: {}", name, score);
}

// Word frequency counter pattern
let text = "hello world hello rust hello";
let mut freq: HashMap<&str, u32> = HashMap::new();
for word in text.split_whitespace() {
    let count = freq.entry(word).or_insert(0);
    *count += 1;   // dereference to modify
}
// freq: {"hello": 3, "world": 1, "rust": 1}

Closures & Iterators

Closures are anonymous functions that can capture their environment. The iterator pattern in Rust is lazy and composable — chains of iterator adapters produce no intermediate allocations.

Closures

// Basic closure syntax
let add = |a, b| a + b;
println!("{}", add(3, 4));  // 7

// Closures capture their environment
let threshold = 5;
let is_big = |n| n > threshold;  // captures threshold
println!("{}", is_big(10));        // true

// move closure — takes ownership of captured values
let name = String::from("world");
let greeting = move || println!("Hello, {}!", name);
greeting();  // name is moved into the closure

Iterator Adapter Chain

let numbers = vec![1, 2, 3, 4, 5, 6, 7, 8, 9, 10];

let result: Vec<i32> = numbers
    .iter()
    .filter(|&&n| n % 2 == 0)   // keep evens: [2,4,6,8,10]
    .map(|&n| n * n)            // square: [4,16,36,64,100]
    .take(3)                    // first 3: [4,16,36]
    .collect();                 // materialize into Vec

// Common terminal operations:
let sum: i32 = numbers.iter().sum();
let product: i32 = numbers.iter().product();
let max = numbers.iter().max();        // Option<&i32>
let count = numbers.iter().count();

// find / any / all
let first_even = numbers.iter().find(|&&n| n % 2 == 0);
let has_big = numbers.iter().any(|&n| n > 5);
let all_pos = numbers.iter().all(|&n| n > 0);

// flat_map, zip, chain, enumerate
let words = vec!["hello world", "foo bar"];
let all_words: Vec<&str> = words.iter()
    .flat_map(|s| s.split(' '))
    .collect();  // ["hello", "world", "foo", "bar"]

Modules & Crates

Rust organizes code into modules (namespaces within a crate) and crates (compilation units/packages). Everything is private by default.

// src/lib.rs or src/main.rs

mod animals {
    // pub = visible outside the module
    pub struct Dog {
        pub name: String,
        age: u32,         // private field
    }

    impl Dog {
        pub fn new(name: &str, age: u32) -> Dog {
            Dog { name: name.to_string(), age }
        }
        pub fn bark(&self) { println!("Woof! I'm {}", self.name); }
    }

    mod internals {              // nested module
        pub fn helper() {}
        use super::*;              // access parent module
    }
}

// Using the module
use animals::Dog;               // bring into scope
let d = Dog::new("Rex", 3);
d.bark();

// External crates — add to Cargo.toml first:
// [dependencies]
// rand = "0.8"
use rand::Rng;
let n: u32 = rand::thread_rng().gen_range(1..=100);

File-based modules

// src/main.rs
mod utils;   // loads from src/utils.rs  OR  src/utils/mod.rs
use utils::some_function;

Hello, World!

The obligatory first program. Even this simple example reveals several Rust fundamentals worth understanding.

main.rsfn main() {
    println!("Hello, world!");
}
Hello, world!

What's happening here?

  • fn main() — the program entry point. Every Rust binary must have exactly one.
  • println! — note the !: this is a macro, not a function. Macros are identified by the exclamation mark and are expanded at compile time.
  • Statements end with ;.
  • No return type on main means it returns () — the unit type, Rust's equivalent of void.

Format String Variants

let name = "Ferris";
let age  = 3;

println!("Hello, {}! You are {} years old.", name, age);
println!("Hello, {name}! You are {age} years old.");  // named args
println!("{:?}", (name, age));    // debug format
println!("{:#?}", (name, age));   // pretty debug
println!("{:>10}", name);         // right-align in 10 chars
println!("{:05}", age);           // zero-pad: "00003"
println!("{:.2}", 3.14159);       // 2 decimal places: "3.14"
eprintln!("Error: something bad"); // print to stderr

Beginner Project: Adventure RPG Engine

This single-file program is a text-based mini RPG that exercises ownership, structs, enums, traits, pattern matching, closures, iterators, error handling, and collections — all in a cohesive, runnable project.

📦 What This Program Covers

Structs & impl blocks · Enums with data · Traits (Display, Debug, custom) · Pattern matching · Vec and HashMap · Closures & iterators · Option and Result · Loops · Format strings · rand crate usage · Modules-in-miniature

Setup

1

Create the project

cargo new rust_rpg
cd rust_rpg
2

Add the rand dependency in Cargo.toml

[dependencies]
rand = "0.8"
3

Replace src/main.rs with the full program below

4

Run it

cargo run

Full Source — src/main.rs

main.rs// ═══════════════════════════════════════════════════════════
//  Rust RPG Engine — A comprehensive beginner Rust project
// ═══════════════════════════════════════════════════════════

use rand::Rng;
use std::collections::HashMap;
use std::fmt;

// ── SECTION 1: ENUMS WITH DATA ───────────────────────────
// Rust enums can carry different data per variant — this is
// what makes them "algebraic data types".

#[derive(Debug, Clone, PartialEq)]
enum Element { Fire, Ice, Lightning, Physical }

#[derive(Debug, Clone)]
enum Item {
    Potion { heal: u32 },                   // named fields
    Weapon { name: String, damage: u32, element: Element },
    KeyItem(String),                         // tuple variant
}

// Implement Display for our Item enum
impl fmt::Display for Item {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        match self {
            Item::Potion { heal }
                => write!(f, "Potion (+{} HP)", heal),
            Item::Weapon { name, damage, element }
                => write!(f, "{} [{:?}] ({}dmg)", name, element, damage),
            Item::KeyItem(s)
                => write!(f, "★ {}", s),
        }
    }
}

// ── SECTION 2: TRAIT DEFINITION ─────────────────────────
// Traits define shared behavior. Any type can implement
// this trait to participate in combat.

trait Combatant {
    fn name(&self) -> &str;
    fn health(&self) -> u32;
    fn max_health(&self) -> u32;
    fn attack_power(&self) -> u32;
    fn take_damage(&mut self, amount: u32);
    fn is_alive(&self) -> bool { self.health() > 0 }

    // Default method using other trait methods
    fn health_bar(&self) -> String {
        let ratio = self.health() as f32 / self.max_health() as f32;
        let filled = (ratio * 20.0) as usize;
        let empty  = 20.saturating_sub(filled);
        format!("[{}{}] {}/{}",
            "█".repeat(filled),
            "░".repeat(empty),
            self.health(), self.max_health())
    }
}

// ── SECTION 3: STRUCTS AND impl BLOCKS ──────────────────
// Hero struct — owns heap data (String, Vec, HashMap)

#[derive(Debug)]
struct Hero {
    name:       String,
    hp:         u32,
    max_hp:     u32,
    level:      u32,
    xp:         u32,
    gold:       u32,
    base_atk:   u32,
    inventory:  Vec<Item>,
    equipped:   Option<Item>,       // Option = might have a weapon
    kill_log:   HashMap<String, u32>, // tracks kills per monster type
}

impl Hero {
    // Associated function (constructor) — no `self`
    fn new(name: &str) -> Hero {
        Hero {
            name:      name.to_string(),
            hp:        100, max_hp: 100,
            level:     1,   xp:     0,
            gold:      50,  base_atk: 10,
            inventory: vec![
                Item::Potion { heal: 30 },
                Item::Potion { heal: 30 },
            ],
            equipped: None,
            kill_log: HashMap::new(),
        }
    }

    // Heal the hero — saturating arithmetic prevents overflow
    fn heal(&mut self, amount: u32) {
        self.hp = (self.hp + amount).min(self.max_hp);
        println!("  ✨ {} healed for {} HP! ({})",
            self.name, amount, self.health_bar());
    }

    // Try to use a potion from inventory
    // Returns Result to demonstrate error handling
    fn use_potion(&mut self) -> Result<(), String> {
        // find_position of a Potion in inventory using iterator
        let pos = self.inventory
            .iter()
            .position(|item| matches!(item, Item::Potion { .. }));

        match pos {
            None => Err("No potions in inventory!".to_string()),
            Some(i) => {
                if let Item::Potion { heal } = self.inventory.remove(i) {
                    self.heal(heal);
                }
                Ok(())
            }
        }
    }

    // Equip a weapon from inventory by name
    fn equip_weapon(&mut self, weapon_name: &str) -> Result<(), String> {
        let pos = self.inventory
            .iter()
            .position(|item| matches!(item,
                Item::Weapon { name, .. } if name == weapon_name));

        match pos {
            None => Err(format!("No weapon named '{}' in inventory", weapon_name)),
            Some(i) => {
                let weapon = self.inventory.remove(i);
                println!("  ⚔ Equipped: {}", weapon);
                self.equipped = Some(weapon);
                Ok(())
            }
        }
    }

    // Gain experience and level up if threshold reached
    fn gain_xp(&mut self, amount: u32) {
        self.xp += amount;
        let xp_needed = self.level * 100;
        if self.xp >= xp_needed {
            self.xp -= xp_needed;
            self.level += 1;
            self.max_hp += 20;
            self.hp     = self.max_hp;
            self.base_atk += 5;
            println!("  🌟 LEVEL UP! {} is now level {}! HP restored.",
                self.name, self.level);
        }
    }

    // Record a kill in our HashMap
    fn record_kill(&mut self, monster: &str) {
        let count = self.kill_log.entry(monster.to_string()).or_insert(0);
        *count += 1;
    }

    // Print kill log using iterator methods
    fn print_kill_log(&self) {
        println!("\n  📜 Kill Log:");
        if self.kill_log.is_empty() {
            println!("     (none yet)");
            return;
        }
        // Sort entries by kill count descending — closures + iterators
        let mut entries: Vec<(&String, &u32)> = self.kill_log.iter().collect();
        entries.sort_by(|a, b| b.1.cmp(a.1));
        for (monster, count) in &entries {
            println!("     {:<20} ×{}", monster, count);
        }
        let total: u32 = self.kill_log.values().sum();
        println!("     Total kills: {}", total);
    }

    // Show full status screen
    fn status(&self) {
        println!("\n╔═══════════ HERO STATUS ═══════════╗");
        println!("║  Name:  {}", self.name);
        println!("║  Level: {}  XP: {}/{}",
            self.level, self.xp, self.level * 100);
        println!("║  HP:    {}", self.health_bar());
        println!("║  ATK:   {}", self.attack_power());
        println!("║  Gold:  {} gp", self.gold);
        println!("║  Weapon:{}",
            self.equipped.as_ref()
                .map(|w| format!(" {}", w))
                .unwrap_or_else(|| " (none)".to_string()));
        // Iterator: count potions with filter
        let potion_count = self.inventory.iter()
            .filter(|i| matches!(i, Item::Potion { .. }))
            .count();
        println!("║  Bag:   {} item(s), {} potion(s)",
            self.inventory.len(), potion_count);
        println!("╚════════════════════════════════════╝");
    }
}

// Implement the Combatant trait for Hero
impl Combatant for Hero {
    fn name(&self)       -> &str { &self.name }
    fn health(&self)     -> u32  { self.hp }
    fn max_health(&self) -> u32  { self.max_hp }
    fn attack_power(&self) -> u32 {
        // Base attack + equipped weapon bonus, using Option::map
        let weapon_bonus = self.equipped.as_ref()
            .and_then(|item| {
                if let Item::Weapon { damage, .. } = item {
                    Some(*damage)
                } else { None }
            })
            .unwrap_or(0);
        self.base_atk + weapon_bonus
    }
    fn take_damage(&mut self, amount: u32) {
        self.hp = self.hp.saturating_sub(amount);
    }
}

// ── SECTION 4: MONSTER STRUCT ────────────────────────────

#[derive(Debug, Clone)]
struct Monster {
    name:      String,
    hp:        u32,
    max_hp:    u32,
    attack:    u32,
    xp_reward: u32,
    gold_drop: u32,
    element:   Element,
    loot:      Option<Item>,  // Some monsters drop loot
}

impl Combatant for Monster {
    fn name(&self)         -> &str { &self.name }
    fn health(&self)       -> u32  { self.hp }
    fn max_health(&self)   -> u32  { self.max_hp }
    fn attack_power(&self) -> u32  { self.attack }
    fn take_damage(&mut self, amount: u32) {
        self.hp = self.hp.saturating_sub(amount);
    }
}

// ── SECTION 5: MONSTER CATALOG ───────────────────────────
// A function returning a Vec of all available monsters.
// Demonstrates struct initialization and Vec construction.

fn monster_catalog() -> Vec<Monster> {
    vec![
        Monster {
            name:      "Goblin Scout".into(),
            hp: 30,    max_hp: 30,
            attack:    8,
            xp_reward: 40,  gold_drop: 15,
            element:   Element::Physical,
            loot:      None,
        },
        Monster {
            name:      "Fire Imp".into(),
            hp: 45,    max_hp: 45,
            attack:    14,
            xp_reward: 65,  gold_drop: 20,
            element:   Element::Fire,
            loot: Some(Item::Weapon {
                name: "Ember Blade".into(),
                damage: 18,
                element: Element::Fire,
            }),
        },
        Monster {
            name:      "Ice Wraith".into(),
            hp: 60,    max_hp: 60,
            attack:    11,
            xp_reward: 80,  gold_drop: 30,
            element:   Element::Ice,
            loot: Some(Item::Potion { heal: 50 }),
        },
        Monster {
            name:      "Storm Dragon".into(),
            hp: 120,   max_hp: 120,
            attack:    22,
            xp_reward: 200, gold_drop: 100,
            element:   Element::Lightning,
            loot: Some(Item::KeyItem("Dragon's Heart".into())),
        },
    ]
}

// ── SECTION 6: COMBAT ENGINE ─────────────────────────────
// Generic over any type that implements Combatant.
// Returns true if the attacker wins (target dies).

fn combat_round<A: Combatant, D: Combatant>(
    attacker: &A,
    defender: &mut D,
    rng: &mut impl rand::Rng,
) -> bool {
    // 10% miss chance using random number
    if rng.gen_bool(0.1) {
        println!("  ✗ {} missed!", attacker.name());
        return false;
    }
    // Damage variance: base ± 20%
    let base = attacker.attack_power();
    let damage = rng.gen_range((base * 80 / 100)..=(base * 120 / 100));
    defender.take_damage(damage);
    println!("  ⚔ {} hits {} for {} damage! {}",
        attacker.name(), defender.name(), damage,
        defender.health_bar());
    !defender.is_alive()
}

// Full battle loop
fn battle(hero: &mut Hero, monster: &mut Monster,
           rng: &mut impl rand::Rng) -> bool {
    println!("\n🗡 BATTLE: {} vs {}!", hero.name(), monster.name());
    println!("   Monster HP: {}", monster.health_bar());

    let mut turn = 0;
    loop {
        turn += 1;
        println!("\n  — Turn {} —", turn);

        // Hero attacks first each turn
        if combat_round(hero, monster, rng) {
            println!("  💀 {} was defeated!", monster.name());
            break;
        }

        // Monster counterattack
        // NOTE: combat_round borrows hero as mutable — monster as immutable
        let monster_atk  = monster.attack_power();
        let monster_name = monster.name().to_string();
        if rng.gen_bool(0.1) {
            println!("  ✗ {} missed!", monster_name);
        } else {
            let dmg = rng.gen_range(
                (monster_atk * 80 / 100)..=(monster_atk * 120 / 100));
            hero.take_damage(dmg);
            println!("  ⚔ {} hits {} for {} damage! {}",
                monster_name, hero.name(), dmg, hero.health_bar());
        }

        if !hero.is_alive() {
            println!("  💀 {} has fallen!", hero.name());
            return false;    // hero lost
        }

        // Hero auto-uses potion if HP below 30%
        let hp_pct = hero.hp as f32 / hero.max_hp as f32;
        if hp_pct < 0.3 {
            match hero.use_potion() {
                Ok(())  => {},
                Err(e)  => println!("  ⚠ {}", e),
            }
        }
    }
    true   // hero won
}

// ── SECTION 7: MAIN GAME LOOP ────────────────────────────

fn main() {
    let mut rng = rand::thread_rng();

    println!("╔══════════════════════════════════════╗");
    println!("║     ⚔  RUST RPG ENGINE  ⚔          ║");
    println!("║  A Comprehensive Rust Demo Program  ║");
    println!("╚══════════════════════════════════════╝\n");

    // Struct construction via associated function
    let mut hero = Hero::new("Ferris");
    println!("Welcome, {}! Your adventure begins...", hero.name());

    // Get the monster catalog (Vec<Monster>)
    let catalog = monster_catalog();

    // Print available enemies using iterator + formatting
    println!("\n📖 You will face these enemies:");
    for (i, m) in catalog.iter().enumerate() {
        println!("  {}. {} [{:?}] — {} HP",
            i + 1, m.name, m.element, m.max_hp);
    }

    // Show starting status
    hero.status();

    // Fight each monster in sequence — clone so we own the instance
    for template in &catalog {
        let mut monster = template.clone();

        let hero_won = battle(&mut hero, &mut monster, &mut rng);

        if !hero_won {
            println!("\n💀 GAME OVER — {} was defeated.", hero.name());
            hero.print_kill_log();
            return;
        }

        // Victory — award loot using pattern matching on Option
        hero.gain_xp(monster.xp_reward);
        hero.gold += monster.gold_drop;
        println!("  🏆 Earned {} XP and {} gold!",
            monster.xp_reward, monster.gold_drop);

        // Pattern match on Option<Item>
        if let Some(item) = monster.loot.take() {
            println!("  🎁 Loot dropped: {}", item);
            match &item {
                Item::Weapon { name, .. } => {
                    println!("     Auto-equipping {}...", name);
                    hero.inventory.push(item);
                    let wname = name.clone();
                    match hero.equip_weapon(&wname) {
                        Ok(()) => {},
                        Err(e) => println!("  ⚠ {}", e),
                    }
                }
                _ => { hero.inventory.push(item); }
            }
        }

        hero.record_kill(&monster.name);
        hero.status();
    }

    // ── VICTORY SCREEN ──────────────────────────────────────
    println!("\n\n🎉 VICTORY! {} has conquered all enemies!", hero.name());
    println!("═══════════════════════════════════════════");

    hero.print_kill_log();

    // Final stats with iterator methods
    let total_kills: u32 = hero.kill_log.values().sum();
    let total_monsters = catalog.len();
    let pct = total_kills as f64 / total_monsters as f64 * 100.0;
    println!("\n  Final level:  {}", hero.level);
    println!("  Final gold:   {} gp", hero.gold);
    println!("  Completion:   {:.1}%", pct);

    // Demonstrate closures in inventory summary
    println!("\n  Final Inventory:");
    if hero.inventory.is_empty() {
        println!("    (empty)");
    } else {
        hero.inventory.iter().for_each(|i| println!("    • {}", i));
    }

    println!("\n  Thanks for playing Rust RPG!");
    println!("  Written in 🦀 Rust.");
}

Expected Output

╔══════════════════════════════════════╗ ║ ⚔ RUST RPG ENGINE ⚔ ║ ║ A Comprehensive Rust Demo Program ║ ╚══════════════════════════════════════╝ Welcome, Ferris! Your adventure begins... 📖 You will face these enemies: 1. Goblin Scout [Physical] — 30 HP 2. Fire Imp [Fire] — 45 HP 3. Ice Wraith [Ice] — 60 HP 4. Storm Dragon [Lightning] — 120 HP ╔═══════════ HERO STATUS ═══════════╗ ║ Name: Ferris ║ Level: 1 XP: 0/100 ║ HP: [████████████████████] 100/100 ║ ATK: 10 ║ Gold: 50 gp ║ Weapon: (none) ╚════════════════════════════════════╝ 🗡 BATTLE: Ferris vs Goblin Scout! — Turn 1 — ⚔ Ferris hits Goblin Scout for 11 damage! [░░░░░░░░░░░░░░░] 19/30 ⚔ Goblin Scout hits Ferris for 8 damage! [██████████████████░░] 92/100 ... 🏆 Earned 40 XP and 15 gold! 🎉 VICTORY! Ferris has conquered all enemies!

Concepts Demonstrated — Cross-Reference

Rust ConceptWhere in the Program
Structs + implHero, Monster + all their methods
Enums with dataItem, Element variants
Trait definitiontrait Combatant
Trait implementationimpl Combatant for Hero/Monster
Default trait methodshealth_bar(), is_alive()
Genericsfn combat_round<A: Combatant, D: Combatant>
Ownership + movemonster.loot.take(), function args
Borrowing (&, &mut)Every function parameter in combat
Option<T>equipped, loot, find_position returns
Result<T, E>use_potion(), equip_weapon()
Pattern matchingmatch on Item, Option, Result
if letLoot handling, potion usage
Vec<T>inventory, catalog
HashMap<K,V>kill_log
Closuresfilter, map, sort_by, for_each
Iteratorsiter(), position(), filter(), sum(), enumerate()
Derive macros#[derive(Debug, Clone, PartialEq)]
Display traitimpl fmt::Display for Item
Saturating arithmeticsaturating_sub(), .min()
External cratesrand crate for RNG
Format stringsformat!, println! with {:?}, {:.1}
loop / for / breakCombat loop, monster iteration