</>
CodeLearn Pro
Start Learning Free โ†’

Rust

Advanced

Mozilla Research ยท since 2015 ยท Systems language

Rust is a systems programming language focused on speed, memory safety and concurrency โ€” without a garbage collector. It achieves memory safety through its unique ownership system enforced at compile time.

SystemsWebAssemblyCLINetworkingEmbeddedMemory-safeNo GC
95k+
GitHub Stars
#1 loved
StackOverflow
~1ร—
Speed vs C
Zero GC
Memory overhead
135k+
Crates.io pkgs
Mozilla, MS, Google
Used at

What is Rust?

Rust is a multi-paradigm, general-purpose programming language that emphasises performance, type safety, and concurrency. It was originally developed at Mozilla Research and hit version 1.0 in May 2015.

Rust's defining feature is its ownership system โ€” a set of compile-time rules that guarantee memory safety without needing a garbage collector. This means Rust programs have the raw speed of C and C++, but without the entire class of bugs (buffer overflows, use-after-free, data races) that plague those languages.

Rust has been voted the most loved / admired programming language on the Stack Overflow Developer Survey for nine consecutive years (2016โ€“2024). It is now used in the Linux kernel, Windows kernel (Microsoft), Android, WebAssembly runtimes, game engines, and more.

Systems / OS
Linux kernel, Windows drivers
WebAssembly
Fastest WASM compilation
Game engines
Bevy, Fyrox, Macroquad
Web backends
Actix-web, Axum, Rocket
CLI tools
ripgrep, fd, bat, exa
Networking
Tokio async runtime

Why Choose Rust?

Memory safety โ€” no GC
Ownership rules prevent dangling pointers, buffer overflows and use-after-free at compile time. Zero runtime cost.
C-level performance
Zero-cost abstractions. High-level code compiles to machine code as efficient as hand-written C. No garbage collection pauses.
Fearless concurrency
The type system prevents data races at compile time. Write multi-threaded code without the fear of race conditions.
Cargo โ€” best-in-class DX
Cargo handles builds, testing, documentation, formatting and publishing in one tool. The benchmark for package managers.
Excellent error messages
The Rust compiler produces the most helpful error messages of any language โ€” it often tells you exactly what to fix.
Runs everywhere
Targets bare metal, WASM, Linux, Windows, macOS, Android, iOS and embedded devices. One toolchain, all targets.

Setup & Installation

Rust is installed via rustup โ€” the official Rust toolchain installer that manages versions and targets.

Install rustup (all platforms)

Bash
# Linux / macOS
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh

# Windows โ€” download and run rustup-init.exe from rustup.rs
# Or via winget:
winget install Rustlang.Rustup

Verify & update

Bash
rustc --version    # rustc 1.78.0 (9b00956e5 2024-04-29)
cargo --version   # cargo 1.78.0 (54d8815d0 2024-04-18)
rustup update     # keep toolchain current

Create and run your first project

Bash
cargo new hello_rust   # scaffolds a new project
cd hello_rust
cargo run              # compiles + runs
# โ†’ Hello, world!

cargo build --release  # optimised binary in target/release/

Hello World

Every Rust program starts with a main() function. The println! macro prints to stdout. Notice the exclamation mark โ€” macros in Rust end with !.

Rustmain.rs
fn main() {
    println!("Hello, World! ๐Ÿฆ€");

    // Variables are immutable by default
    let name = "Rust";
    let year: u32 = 2015;

    println!("{name} was first released in {year}.");
    println!("It has been {} years since release.", 2026 - year);
}
Output
3 lines
Hello, World! ๐Ÿฆ€
Rust was first released in 2015.
It has been 11 years since release.

Ownership & Borrowing

Ownership is Rust's most unique feature. Every value has one owner; when the owner goes out of scope the value is dropped. References allow you to borrow values without taking ownership โ€” either immutably (many readers) or mutably (one writer at a time).

Rule 1
Each value has exactly one owner
Rule 2
Only one owner at a time
Rule 3
Value dropped when owner goes out of scope
Rustownership.rs
fn main() {
    // โ”€โ”€ Ownership โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
    let s1 = String::from("hello");
    let s2 = s1;          // s1 is MOVED into s2
    // println!("{s1}");  // โ† compile error: value used after move

    // โ”€โ”€ Cloning (deep copy) โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
    let s3 = String::from("world");
    let s4 = s3.clone();  // explicit deep copy
    println!("s3={s3}, s4={s4}");   // both valid

    // โ”€โ”€ Borrowing (references) โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
    let s5 = String::from("Rust");
    let len = calculate_length(&s5); // borrow, not move
    println!("Length of '{s5}' is {len}");  // s5 still valid

    // โ”€โ”€ Mutable reference โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
    let mut s6 = String::from("hello");
    change(&mut s6);
    println!("{s6}");  // "hello, world"
}

fn calculate_length(s: &String) -> usize {
    s.len()   // s is a reference โ€” does not take ownership
}   // s goes out of scope but doesn't drop the value

fn change(s: &mut String) {
    s.push_str(", world");
}
Output
3 lines
s3=world, s4=world
Length of 'Rust' is 4
hello, world

Types & Variables

Rust is statically typed with powerful type inference. Variables are immutable by default โ€” add mut to make them mutable.

Rusttypes.rs
fn main() {
    // โ”€โ”€ Scalar types โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
    let i:   i32   = -42;        // signed 32-bit integer
    let u:   u64   = 1_000_000;  // unsigned 64-bit (underscores ok)
    let f:   f64   = 3.14159;    // 64-bit float
    let b:   bool  = true;
    let c:   char  = '๐Ÿฆ€';       // Unicode scalar value

    // โ”€โ”€ Compound types โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
    let tup: (i32, f64, char) = (500, 6.4, 'z');
    let (x, y, z) = tup;         // destructuring
    println!("Tuple: {x}, {y}, {z}");

    let arr: [i32; 5] = [1, 2, 3, 4, 5];
    println!("Array[2] = {}", arr[2]);

    // โ”€โ”€ String types โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
    let s_lit:  &str   = "string slice โ€” borrowed, immutable";
    let s_own:  String = String::from("owned String โ€” heap allocated");
    let s_fmt:  String = format!("{} and {}", s_lit, s_own);
    println!("{s_fmt}");

    // โ”€โ”€ Collections โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
    let mut v: Vec<i32> = vec![1, 2, 3];
    v.push(4);
    v.push(5);
    println!("Vec: {:?}", v);

    use std::collections::HashMap;
    let mut map = HashMap::new();
    map.insert("Rust",       2015);
    map.insert("Go",         2009);
    map.insert("TypeScript", 2012);
    for (lang, year) in &map {
        println!("{lang}: {year}");
    }
}
Output
7 lines
Tuple: 500, 6.4, z
Array[2] = 3
string slice โ€” borrowed, immutable and owned String โ€” heap allocated
Vec: [1, 2, 3, 4, 5]
Rust: 2015
Go: 2009
TypeScript: 2012

Control Flow

Rust has if, loop, while and for, plus the powerful match expression for pattern matching. In Rust, if and match are expressions โ€” they return values.

Rustcontrol_flow.rs
fn main() {
    // โ”€โ”€ if / else if / else โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
    let score = 87;
    let grade = if score >= 90 { "A" }
                else if score >= 80 { "B" }
                else if score >= 70 { "C" }
                else { "F" };
    println!("Score {score} โ†’ Grade {grade}");

    // โ”€โ”€ loop (infinite, with break value) โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
    let mut counter = 0;
    let result = loop {
        counter += 1;
        if counter == 5 { break counter * 10; }
    };
    println!("Loop result: {result}");  // 50

    // โ”€โ”€ while โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
    let mut n = 3;
    while n > 0 { print!("{n}โ€ฆ "); n -= 1; }
    println!("Go! ๐Ÿš€");

    // โ”€โ”€ for over a range โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
    let sum: i32 = (1..=100).sum();
    println!("Sum 1โ€“100 = {sum}");  // 5050

    // โ”€โ”€ for over a collection โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
    let langs = ["Rust", "Go", "C++", "Zig"];
    for (i, lang) in langs.iter().enumerate() {
        println!("{}: {lang}", i + 1);
    }

    // โ”€โ”€ match (Rust's powerful pattern matching) โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
    let num = 7;
    match num {
        1       => println!("one"),
        2 | 3   => println!("two or three"),
        4..=6   => println!("four to six"),
        n if n % 2 == 0 => println!("{n} is even"),
        _       => println!("{num} is something else"),
    }
}
Output
9 lines
Score 87 โ†’ Grade B
Loop result: 50
3โ€ฆ 2โ€ฆ 1โ€ฆ Go! ๐Ÿš€
Sum 1โ€“100 = 5050
1: Rust
2: Go
3: C++
4: Zig
7 is something else

Structs & Enums

Rust uses structs for data with named fields and enums for types with distinct variants. Both can have methods via impl blocks. Rust's enums are algebraic data types โ€” variants can carry data, making them far more powerful than C enums.

Ruststructs_enums.rs
// โ”€โ”€ Structs โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
#[derive(Debug, Clone)]
struct Point {
    x: f64,
    y: f64,
}

impl Point {
    // Associated function (constructor)
    fn new(x: f64, y: f64) -> Self {
        Self { x, y }
    }

    // Method โ€” takes &self
    fn distance_from_origin(&self) -> f64 {
        (self.x * self.x + self.y * self.y).sqrt()
    }

    fn translate(&mut self, dx: f64, dy: f64) {
        self.x += dx;
        self.y += dy;
    }
}

// โ”€โ”€ Enums โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
#[derive(Debug)]
enum Shape {
    Circle { radius: f64 },
    Rectangle { width: f64, height: f64 },
    Triangle { base: f64, height: f64 },
}

impl Shape {
    fn area(&self) -> f64 {
        match self {
            Shape::Circle    { radius }        => std::f64::consts::PI * radius * radius,
            Shape::Rectangle { width, height } => width * height,
            Shape::Triangle  { base, height }  => 0.5 * base * height,
        }
    }
}

fn main() {
    let mut p = Point::new(3.0, 4.0);
    println!("Point: {:?}", p);
    println!("Distance: {:.2}", p.distance_from_origin()); // 5.00
    p.translate(1.0, 1.0);
    println!("After translate: {:?}", p);

    let shapes: Vec<Shape> = vec![
        Shape::Circle    { radius: 5.0 },
        Shape::Rectangle { width: 4.0, height: 6.0 },
        Shape::Triangle  { base: 3.0, height: 8.0 },
    ];

    for shape in &shapes {
        println!("{:?} โ†’ area = {:.2}", shape, shape.area());
    }
}
Output
6 lines
Point: Point { x: 3.0, y: 4.0 }
Distance: 5.00
After translate: Point { x: 4.0, y: 5.0 }
Circle { radius: 5.0 } โ†’ area = 78.54
Rectangle { width: 4.0, height: 6.0 } โ†’ area = 24.00
Triangle { base: 3.0, height: 8.0 } โ†’ area = 12.00

Error Handling

Rust has no exceptions. Instead it uses Result<T, E> for recoverable errors and Option<T> for values that may not exist. The ? operator propagates errors up the call stack, keeping error-handling concise without hiding it.

Rusterror_handling.rs
use std::num::ParseIntError;
use std::fmt;

// โ”€โ”€ Custom error type โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
#[derive(Debug)]
enum AppError {
    ParseError(ParseIntError),
    NegativeNumber(i32),
    TooBig(i32),
}

impl fmt::Display for AppError {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        match self {
            AppError::ParseError(e)     => write!(f, "Parse error: {e}"),
            AppError::NegativeNumber(n) => write!(f, "Negative number: {n}"),
            AppError::TooBig(n)         => write!(f, "Number too big: {n} (max 100)"),
        }
    }
}

impl From<ParseIntError> for AppError {
    fn from(e: ParseIntError) -> Self {
        AppError::ParseError(e)
    }
}

fn parse_positive(s: &str) -> Result<i32, AppError> {
    let n: i32 = s.parse()?;  // ? auto-converts ParseIntError via From
    if n < 0  { return Err(AppError::NegativeNumber(n)); }
    if n > 100 { return Err(AppError::TooBig(n)); }
    Ok(n)
}

fn main() {
    let inputs = ["42", "-5", "200", "abc", "99"];

    for input in inputs {
        match parse_positive(input) {
            Ok(n)  => println!("โœ“ '{input}' โ†’ {n}"),
            Err(e) => println!("โœ— '{input}' โ†’ {e}"),
        }
    }

    // Option<T> โ€” value that may or may not exist
    let numbers = vec![1, 2, 3, 4, 5];
    match numbers.iter().find(|&&x| x > 3) {
        Some(n) => println!("First > 3: {n}"),
        None    => println!("None found"),
    }
}
Output
6 lines
โœ“ '42' โ†’ 42
โœ— '-5' โ†’ Negative number: -5
โœ— '200' โ†’ Number too big: 200 (max 100)
โœ— 'abc' โ†’ Parse error: invalid digit found in string
โœ“ '99' โ†’ 99
First > 3: 4

Quick Reference Table

ConceptRust SyntaxNotes
Variable (immutable)let x = 5;
Variable (mutable)let mut x = 5;
Constantconst MAX: u32 = 100;
Shadowlet x = x + 1;
Functionfn add(a: i32, b: i32) -> i32 { a+b }
Closurelet add = |a, b| a + b;
Reference (borrow)&value / &mut value
Slice&arr[1..3]
String literal&str
Owned stringString::from("hello")
OptionSome(value) / None
ResultOk(value) / Err(error)
? operatorlet n = s.parse::<i32>()?;
Pattern matchmatch x { 1 => .., _ => .. }
Structstruct Point { x: f64, y: f64 }
Enum variant w/ dataenum Shape { Circle { r: f64 } }
Traittrait Animal { fn speak(&self); }
impl blockimpl Point { fn new() -> Self {} }
Genericfn largest<T: PartialOrd>(v: &[T])
Lifetime'a โ€” fn f<'a>(x: &'a str)
Veclet v: Vec<i32> = vec![1,2,3];
HashMapHashMap::new(); map.insert(k, v);
Async/awaitasync fn f() -> T { โ€ฆ await }
Macroprintln!(), vec![], format!()
Cargo commandsnew / build / run / test / fmt / clippy

What to Learn Next

Ready for the full tutorial?

10 hands-on examples โ€” ownership, traits, iterators, async, error handling and more.

Start Rust Tutorial โ€” 10 Examples โ†’

The Story of Rust

Real history, real companies, and an honest look at where Rustshines and where it doesn't โ€” not just a feature list.

๐Ÿ•ฐ๏ธ

Where it came from

Rust began as a personal project by Mozilla engineer Graydon Hoare in 2006 and became an officially sponsored Mozilla project in 2009, with the explicit goal of eliminating memory-safety bugs โ€” the kind that cause crashes and security vulnerabilities in C and C++ โ€” without needing a garbage collector, which would hurt performance. Its 'borrow checker' enforces memory rules at compile time instead.

๐Ÿข

How itโ€™s actually used today

Rust is used inside Firefox itself (Mozilla's original motivation for building it), and has been adopted for parts of Windows and Android's operating system internals specifically because of its memory-safety guarantees. Discord rewrote performance-critical services from Go to Rust for efficiency gains, and Dropbox uses it for its file-storage engine.

โš–๏ธ

Strengths & trade-offs

Rust's compiler catches entire categories of bugs โ€” the kind that in C or C++ might only surface as a security vulnerability years later โ€” before your program ever runs. The cost is a genuinely steep learning curve; the borrow checker takes real time to get comfortable with, and compile times are slower than Go's.

๐Ÿงญ

What to learn next

Because Rust is unforgiving about ownership and memory rules, it's usually easier to learn after some experience in C or C++, so you already understand what problem the borrow checker is solving. From there, most learners specialise into either systems/CLI tools or WebAssembly, both strong fits for Rust.