Rust Introduction

Rust is a systems programming language focused on safety, speed, and concurrency. It prevents memory bugs at compile time without a garbage collector, making it ideal for performance-critical applications.

Why Rust?

  • Memory safety — No null pointers, no dangling references, no data races
  • Zero-cost abstractions — High-level features with no runtime cost
  • Performance — Comparable to C/C++
  • Concurrency — Fearless concurrency through the type system
  • Modern tooling — Cargo (package manager), rustfmt, clippy
  • Growing fast — Linux kernel, Android, Windows, Firefox, Deno all use Rust

Ownership System

Rust's key innovation: every value has exactly one owner.

Rust
fn main() {
    let s1 = String::from("hello");  // s1 owns the String
    let s2 = s1;                     // Ownership moves to s2
    // println!("{}", s1);           // ERROR: s1 is no longer valid!
    
    let s3 = s2.clone();            // Deep copy (explicit)
    println!("{} {}", s2, s3);      // Both valid
    
    // Borrowing (references)
    let s4 = String::from("world");
    let len = calculate_length(&s4); // Borrow immutably
    println!("{} has length {}", s4, len);  // s4 still valid
    
    let mut s5 = String::from("hello");
    change(&mut s5);                  // Borrow mutably
    println!("{}", s5);              // "hello, world"
}

fn calculate_length(s: &String) -> usize {
    s.len()
}

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

Rules of Ownership

Rust
// Rule 1: Each value has one owner
let x = String::from("hello");

// Rule 2: When owner goes out of scope, value is dropped
{
    let y = String::from("inner");
}  // y is dropped here (memory freed)

// Rule 3: Ownership can be moved or borrowed
let s1 = String::from("hello");
let s2 = s1;           // Moved (s1 invalidated)
let s3 = &s2;          // Borrowed (s2 still valid)

// Borrowing rules:
// - Any number of immutable references: &T
// - OR exactly one mutable reference: &mut T
// - References must always be valid (no dangling)

Basic Syntax

Rust
// Variables (immutable by default)
let x = 5;
let mut y = 10;  // Mutable
y += 5;

// Constants
const MAX_POINTS: u32 = 100_000;

// Types
let name: &str = "Alice";    // String slice
let age: i32 = 28;           // Signed 32-bit integer
let pi: f64 = 3.14159;       // 64-bit float
let active: bool = true;

// Functions
fn add(a: i32, b: i32) -> i32 {
    a + b  // No semicolon = return expression
}

// If expression
let grade = if score >= 90 { "A" } else if score >= 80 { "B" } else { "F" };

// Pattern matching
match command {
    "start" => println!("Starting..."),
    "stop" => println!("Stopping..."),
    _ => println!("Unknown command"),
}

// Vectors
let mut nums = vec![1, 2, 3, 4, 5];
nums.push(6);
for n in &nums {
    println!("{}", n);
}

// HashMap
use std::collections::HashMap;
let mut scores = HashMap::new();
scores.insert("Alice", 95);
scores.insert("Bob", 87);

Error Handling

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

// Result type (no exceptions in Rust!)
fn read_file(path: &str) -> Result<String, io::Error> {
    fs::read_to_string(path)
}

// Pattern matching
match read_file("config.txt") {
    Ok(contents) => println!("File: {}", contents),
    Err(e) => println!("Error: {}", e),
}

// ? operator (propagate errors)
fn process_config(path: &str) -> Result<Config, Box<dyn std::error::Error>> {
    let contents = fs::read_to_string(path)?;  // Returns Err if failed
    let config: Config = serde_json::from_str(&contents)?;
    Ok(config)
}

// Unwrap (use only in tests/prototypes)
let contents = fs::read_to_string("config.txt").unwrap();  // Panics on error

Structs and Enums

Rust
// Struct
struct User {
    name: String,
    email: String,
    age: u32,
    active: bool,
}

impl User {
    fn new(name: String, email: String) -> Self {
        User { name, email, age: 0, active: true }
    }
    
    fn greet(&self) -> String {
        format!("Hi, I'm {}!", self.name)
    }
}

// Tuple struct
struct Color(u8, u8, u8);
let red = Color(255, 0, 0);

// Enum
enum Shape {
    Circle(f64),                    // radius
    Rectangle(f64, f64),           // width, height
    Triangle(f64, f64, f64),       // sides
}

fn area(shape: &Shape) -> f64 {
    match shape {
        Shape::Circle(r) => std::f64::consts::PI * r * r,
        Shape::Rectangle(w, h) => w * h,
        Shape::Triangle(a, b, c) => {
            let s = (a + b + c) / 2.0;
            (s * (s - a) * (s - b) * (s - c)).sqrt()
        }
    }
}

💡 Tip: The borrow checker feels restrictive at first, but it's catching bugs that would crash C/C++ programs. Trust the compiler — once it compiles, whole classes of bugs are impossible.

Next: Ownership and Borrowing