Rust Basics
Rust is a compiled, statically typed language that focuses on safety and zero‑cost abstractions. By the end of this primer you’ll recognise the core syntax, read production code, and write simple functions—ready for deeper topics like ownership, lifetimes, and async.
Variables & Mutability
fn main() { let x = 5; // immutable by default let mut y = 5; // mutable y += 1; println!("{x} {y}"); }
Values are immutable unless you add mut. Shadowing (let x = x + 1;) lets you reuse a name while leaving the original binding unchanged.
Data Types
#![allow(unused)] fn main() { let a: i32 = 42; // 32‑bit signed let b: f64 = 3.14; // 64‑bit float let c: char = '🦀'; // Unicode scalar let d: bool = true; let tup: (i32, f64, bool) = (a, b, d); let (x, y, z) = tup; // destructuring let arr = [1, 2, 3]; // fixed‑size array (stack) let mut vec = vec![1, 2, 3]; // growable vector (heap) }
Functions
fn add(a: i32, b: i32) -> i32 { a + b // implicit return (no semicolon) } fn main() { println!("{}", add(2, 3)); }
A function starts with fn; parameters are type‑annotated. The last expression, without a semicolon, becomes the return value.
Comments
#![allow(unused)] fn main() { // single‑line /* block */ //! crate‑level documentation /// item‑level docs collected by `rustdoc` }
Line comments explain why, not what. Doc comments (/// and //!) generate HTML docs via cargo doc.
Control Flow
#![allow(unused)] fn main() { for n in 1..=5 { if n % 2 == 0 { println!("{n} even"); } } let mut i = 0; while i < 3 { i += 1; } }
Rust offers if/else, while, and for; any loop can break or continue.
Structs
struct User { id: u32, email: String, active: bool, } fn main() { let mut user = User { id: 1, email: "a@b.com".into(), active: true }; user.active = false; }
Structs bundle related fields. The whole instance must be mutable to change any field.
Rust’s structs Rust’s struct design patterns
Methods
#![allow(unused)] fn main() { impl User { fn deactivate(&mut self) { self.active = false; } } }
Methods live in impl blocks and take self, &self, or &mut self as the first parameter.
Enums & Pattern Matching
#![allow(unused)] fn main() { enum Direction { North, East, South, West } fn compass(dir: Direction) { match dir { Direction::North => println!("Up"), Direction::South => println!("Down"), _ => println!("Sideways"), } } }
match is exhaustive, forcing you to handle every variant. Use if let or let else for concise single‑pattern checks.
Rust’s enums Rust’s match Rust’s if let
Packages, Crates & Modules
cargo new hello-cli # binary crate
cargo new util --lib # library crate
A package is one or more crates plus Cargo.toml. Use mod to declare modules and use to bring items into scope; split large modules into multiple files for clarity.
#![allow(unused)] fn main() { mod db { pub fn connect() {} } use crate::db::connect; }
Error Handling
#![allow(unused)] fn main() { fn read() -> std::io::Result<String> { /* ... */ } if read().is_err() { eprintln!("failed"); } match read() { Ok(text) => println!("{text}"), Err(e) => eprintln!("error: {e}"), } }
Use is_err() for quick checks; prefer match (or the ? operator) when you need the Ok value or fine‑grained error logic. Reserve panic! for unrecoverable bugs.
Rust’s error handling Rust’s Result type Rust’s ? operator
Generics, Traits & Lifetimes — a preview
#![allow(unused)] fn main() { fn largest<T: PartialOrd>(slice: &[T]) -> &T { let mut max = &slice[0]; for item in slice { if item > max { max = item; } } max } }
Generics enable type‑agnostic code, traits define shared behaviour, and lifetimes tell the compiler how references relate. You’ll dive deeper in later chapters.