Rust
01 / 03

Core Language & Ownership

Core Language & Ownership

Basics

// Variables (immutable by default!)
let x = 5;
let mut y = 10;   // mutable
y += 1;

// Type annotations
let n: i32 = 42;
let f: f64 = 3.14;
let b: bool = true;
let s: &str = "hello";          // string slice (borrowed)
let owned: String = String::from("hello");  // owned String

// Shadowing (re-declare, can change type)
let x = 5;
let x = x * 2;  // x is now 10
let x = "now a string";

// Constants (always typed, evaluated at compile time)
const MAX_SIZE: usize = 1024;

// Primitive types
// Integers: i8 i16 i32 i64 i128 isize u8 u16 u32 u64 u128 usize
// Floats: f32 f64
// bool, char (Unicode, 4 bytes)

// Tuples
let tup: (i32, f64, bool) = (500, 6.4, true);
let (a, b, c) = tup;   // destructure
let first = tup.0;

// Arrays (fixed size, same type)
let arr: [i32; 5] = [1, 2, 3, 4, 5];
let zeros = [0; 10];   // [0, 0, 0, 0, 0, 0, 0, 0, 0, 0]

// Vectors (dynamic)
let mut v: Vec<i32> = Vec::new();
v.push(1);
v.push(2);
let v2 = vec![1, 2, 3];

Ownership & Borrowing

Rust's ownership system eliminates data races and memory leaks at compile time — no garbage collector, no manual free().

// Rules:
// 1. Each value has exactly one owner
// 2. When the owner goes out of scope, the value is dropped (free'd)
// 3. There can only be one mutable reference OR many immutable references (not both)

// Move semantics
let s1 = String::from("hello");
let s2 = s1;     // s1 is MOVED to s2 — s1 is no longer valid
// println!("{}", s1);  // COMPILE ERROR: s1 moved

// Clone (deep copy)
let s1 = String::from("hello");
let s2 = s1.clone();  // s1 still valid

// Copy trait — primitives are copied, not moved
let x = 5;
let y = x;   // x is still valid (i32 implements Copy)

// References (borrowing) — doesn't take ownership
fn calculate_length(s: &String) -> usize {  // borrow s
    s.len()
}
let s = String::from("hello");
let len = calculate_length(&s);  // pass reference
// s is still valid here

// Mutable references
fn change(s: &mut String) {
    s.push_str(" world");
}
let mut s = String::from("hello");
change(&mut s);

// RULE: only one mutable reference at a time (prevents data races)
// let r1 = &mut s;
// let r2 = &mut s;  // COMPILE ERROR
// println!("{} {}", r1, r2);

// Slices — reference to part of a collection
let s = String::from("hello world");
let hello = &s[0..5];   // "hello"
let world = &s[6..11];  // "world"

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

Structs, Enums & Pattern Matching

// Struct
#[derive(Debug, Clone)]
struct User {
    username: String,
    email: String,
    age: u32,
    active: bool,
}

impl User {
    // Constructor convention
    fn new(username: String, email: String) -> User {
        User { username, email, age: 0, active: true }
    }
    // Method — borrows self
    fn greet(&self) -> String {
        format!("Hello, {}!", self.username)
    }
    // Mutable method
    fn deactivate(&mut self) {
        self.active = false;
    }
}

// Enum
#[derive(Debug)]
enum Message {
    Quit,
    Move { x: i32, y: i32 },
    Write(String),
    ChangeColor(u8, u8, u8),
}

// Pattern matching — exhaustive
fn process(msg: Message) {
    match msg {
        Message::Quit => println!("Quit"),
        Message::Move { x, y } => println!("Move to {x},{y}"),
        Message::Write(text) => println!("Write: {text}"),
        Message::ChangeColor(r, g, b) => println!("Color: {r},{g},{b}"),
    }
}

// if let — single-variant match
if let Message::Write(text) = msg {
    println!("Got text: {text}");
}

Keep your own version of these notes — editable, searchable, and organised by your stack.

Start free