programming3 min read

Rust Tutorial: Learn Safe Systems from Scratch (2026)

Rust Tutorial: Learn Safe Systems from Scratch (2026)

Published:  |  Category: Programming  |  Reading time: ~15 min
Rust Tutorial: Learn Safe Systems from Scratch (2026)

Rust is the first systems language that made me feel like the compiler truly has my back. After years of debugging use-after-free and data races in C and C++, Rust's ownership model eliminates entire categories of bugs at compile time. I have shipped Rust in CLI tools, WebAssembly modules, and high-throughput network services.

The borrow checker will frustrate you before it clicks. Once you internalize the rules, you will wonder how you ever lived without compile-time memory safety.

Ownership

Every value in Rust has exactly one owner. When the owner goes out of scope, the value is dropped. Ownership moves on assignment, function calls, and returns. After a move, the compiler rejects use of the original variable. This single-owner rule eliminates double-frees, use-after-free, and dangling pointers at compile time.

fn main() {
    let s1 = String::from("hello");
    let s2 = s1; // s1 moved to s2
    // println!("{}", s1); // compile error
    let s3 = takes(s2);
}

fn takes(s: String) -> String {
    println!("{}", s);
    s
}

Borrowing

Borrowing creates references without transferring ownership. Immutable references &T allow multiple readers; mutable references &mut T provide exclusive write access. The compiler enforces: either one mutable reference or any number of immutable references, never both. This eliminates data races at compile time.

fn main() {
    let mut data = vec![1, 2, 3];
    let r1 = &data;
    let r2 = &data;
    println!("{} {}", r1[0], r2[1]);

    let r3 = &mut data;
    r3.push(4);
}

Lifetimes

Lifetimes track how long references are valid. The compiler annotates every reference with a lifetime, usually elided for you. When functions return references, explicit lifetime parameters tell the compiler how the output relates to the inputs. Lifetimes prevent dangling references without runtime cost.

fn longest<'a>(x: &'a str, y: &'a str) -> &'a str {
    if x.len() > y.len() { x } else { y }
}

fn main() {
    let a = String::from("long");
    let b = String::from("longer");
    let r = longest(&a, &b);
    println!("{}", r);
}

Traits

Traits define shared behavior, similar to interfaces in other languages. Any type can implement a trait. Traits can have default method implementations and associated types. The impl Trait syntax in function arguments enables static dispatch without writing explicit generics.

trait Area {
    fn area(&self) -> f64;
}

struct Circle { r: f64 }
struct Rect { w: f64, h: f64 }

impl Area for Circle {
    fn area(&self) -> f64 { 3.14159 * self.r * self.r }
}
impl Area for Rect {
    fn area(&self) -> f64 { self.w * self.h }
}

Pattern Matching

Pattern matching with match is exhaustive: the compiler forces you to handle every variant. This eliminates null pointer errors and unhandled enum cases. Use if let for single-pattern matches. Patterns can destructure tuples, structs, and enums, binding variables to inner fields.

enum Status { Active, Inactive, Banned }

fn describe(s: Status) -> &'static str {
    match s {
        Status::Active => "active",
        Status::Inactive => "inactive",
        Status::Banned => "banned",
    }
}

if let Status::Active = user.status {
    println!("user is active");
}

Unsafe Rust

unsafe blocks let you dereference raw pointers, call foreign functions, and access mutable statics. The borrow checker still applies to the surrounding safe code. Isolate unsafe code in small, auditable modules with safe abstractions. Most Rust projects use zero or minimal unsafe code.

unsafe {
    let ptr = 0x1000 as *mut i32;
    *ptr = 42;
}

extern "C" {
    fn abs(input: i32) -> i32;
}

unsafe { println!("{}", abs(-3)); }

Frequently Asked Questions

When is unwrap acceptable?

In examples, tests, and when you know the Option is always Some. In production, handle errors with ? or match.

String vs &str?

String is owned, heap-allocated, mutable. &str is a borrowed reference to a string slice, either from a String or a string literal.

Why does Rust not have a garbage collector?

The ownership system achieves memory safety at compile time through static analysis, eliminating GC runtime overhead.

What is the ? operator?

? unwraps a Result or returns the error to the caller. It is syntactic sugar for a match that returns on Err.

Originally published on Ayodhyyya. Last updated June 1, 2026.