What is Rust?
Rust is a systems programming language focused on safety, speed, and concurrency. It achieves memory safety without garbage collection through its ownership system.
Memory Safety
Guarantees memory safety at compile time. No null pointers, no dangling references, no data races.
Zero-Cost Abstractions
High-level features compile to efficient low-level code. You don't pay for what you don't use.
Fearless Concurrency
The type system prevents data races. Write concurrent code with confidence.
Great Tooling
Cargo for package management, rustfmt for formatting, clippy for linting. Batteries included.
Basics
Hello World
fn main() { println!("Hello, world!"); }
Variables
// Immutable by default let x: i32 = 42; let y = 42; // type inferred // Mutable variables let mut z = 0; z += 1; // Constants (must have type annotation) const MAX_POINTS: u32 = 100_000; // Shadowing let x = 5; let x = x + 1; // x is now 6
Functions
fn add(a: i32, b: i32) -> i32 { a + b // no semicolon = return value } // With explicit return fn divide(a: f64, b: f64) -> f64 { if b == 0.0 { return 0.0; } a / b } // No return value (returns unit type) fn greet(name: &str) { println!("Hello, {}!", name); }
Control Flow
// if is an expression let result = if x > 0 { "positive" } else { "non-positive" }; // loop (infinite, can return value) let result = loop { if condition { break 42; } }; // while while x < 10 { x += 1; } // for (iterate over iterator) for i in 0..10 { println!("{}", i); } for item in &items { println!("{}", item); }
Types
Primitive Types
| Category | Types | Notes |
|---|---|---|
| Signed Integers | i8, i16, i32, i64, i128, isize |
isize is pointer-sized |
| Unsigned Integers | u8, u16, u32, u64, u128, usize |
usize for indexing |
| Floats | f32, f64 |
Default is f64 |
| Boolean | bool |
true or false |
| Character | char |
4 bytes, Unicode scalar |
| Unit | () |
Empty tuple, like void |
Compound Types
// Tuples (fixed size, mixed types) let tup: (i32, f64, char) = (500, 6.4, 'z'); let (x, y, z) = tup; // destructuring let first = tup.0; // index access // Arrays (fixed size, same type) let arr: [i32; 5] = [1, 2, 3, 4, 5]; let zeros = [0; 5]; // [0, 0, 0, 0, 0] let first = arr[0]; // bounds-checked!
Strings
// String slice (borrowed, immutable) let s: &str = "hello"; // String (owned, growable) let mut s = String::from("hello"); s.push_str(", world"); s.push('!'); // Conversion let s: String = "hello".to_string(); let s: &str = &my_string; // deref coercion // Concatenation let s = format!("{} {}", "hello", "world");
Ownership & Borrowing
Rust's core feature. Every value has a single owner, and when the owner goes out of scope, the value is dropped.
Ownership Rules
// 1. Each value has one owner let s1 = String::from("hello"); // 2. Move: ownership transfers let s2 = s1; // s1 is now invalid! // println!("{}", s1); // ERROR: value moved // 3. Clone: explicit deep copy let s1 = String::from("hello"); let s2 = s1.clone(); // s1 still valid // 4. Copy types (stack-only) don't move let x = 5; let y = x; // x still valid (Copy trait)
References & Borrowing
let s = String::from("hello"); // Immutable reference (can have many) let r1 = &s; let r2 = &s; // Mutable reference (only one at a time) let mut s = String::from("hello"); let r = &mut s; r.push_str(", world"); // Function with reference fn len(s: &String) -> usize { s.len() } // Mutable reference parameter fn append(s: &mut String) { s.push_str("!"); }
Slices
let s = String::from("hello world"); // String slices let hello: &str = &s[0..5]; let world: &str = &s[6..]; // Array slices let arr = [1, 2, 3, 4, 5]; let slice: &[i32] = &arr[1..3]; // [2, 3]
Lifetimes
// Lifetime annotation: 'a fn longest<'a>(x: &'a str, y: &'a str) -> &'a str { if x.len() > y.len() { x } else { y } } // Struct with lifetime struct Excerpt<'a> { part: &'a str, } // Static lifetime (lives forever) let s: &'static str = "I live forever";
Structs & Enums
Structs
// Define struct struct User { username: String, email: String, active: bool, } // Create instance let user = User { username: String::from("alice"), email: String::from("alice@example.com"), active: true, }; // Field init shorthand fn build_user(email: String) -> User { User { email, // shorthand username: String::from("anonymous"), active: true, } } // Struct update syntax let user2 = User { email: String::from("bob@example.com"), ..user // rest from user };
Tuple Structs & Unit Structs
// Tuple struct struct Color(u8, u8, u8); let black = Color(0, 0, 0); // Unit struct (no fields) struct Marker;
Methods (impl blocks)
struct Rectangle { width: u32, height: u32, } impl Rectangle { // Associated function (no self) - constructor fn new(width: u32, height: u32) -> Self { Self { width, height } } // Method (takes &self) fn area(&self) -> u32 { self.width * self.height } // Mutable method fn scale(&mut self, factor: u32) { self.width *= factor; self.height *= factor; } } let rect = Rectangle::new(30, 50); println!("Area: {}", rect.area());
Enums
// Simple enum enum Direction { North, South, East, West, } // Enum with data enum Message { Quit, Move { x: i32, y: i32 }, Write(String), ChangeColor(u8, u8, u8), } let msg = Message::Write(String::from("hello"));
Pattern Matching
// match is exhaustive match msg { Message::Quit => println!("Quit"), Message::Move { x, y } => println!("Move to {}, {}", x, y), Message::Write(text) => println!("Text: {}", text), Message::ChangeColor(r, g, b) => println!("RGB: {}, {}, {}", r, g, b), } // if let (single pattern) if let Message::Write(text) = msg { println!("Got text: {}", text); } // while let while let Some(item) = stack.pop() { println!("{}", item); }
Option & Pattern Guards
// Option<T> - nullable type let some_number: Option<i32> = Some(5); let no_number: Option<i32> = None; // Pattern guards match x { Some(n) if n > 0 => println!("positive"), Some(n) if n < 0 => println!("negative"), Some(_) => println!("zero"), None => println!("nothing"), }
Traits
Traits define shared behavior. Similar to interfaces in other languages.
Defining & Implementing Traits
// Define trait trait Summary { fn summarize(&self) -> String; // Default implementation fn preview(&self) -> String { format!("Read more: {}", self.summarize()) } } // Implement trait impl Summary for Article { fn summarize(&self) -> String { format!("{} by {}", self.title, self.author) } }
Trait Bounds
// Function with trait bound fn notify(item: &impl Summary) { println!("Breaking: {}", item.summarize()); } // Generic with trait bound fn notify<T: Summary>(item: &T) { println!("Breaking: {}", item.summarize()); } // Multiple bounds fn process<T: Summary + Clone>(item: &T) { } // where clause (cleaner) fn process<T>(item: &T) where T: Summary + Clone, { // ... }
Common Traits
// Derive common traits #[derive(Debug, Clone, PartialEq, Eq, Hash)] struct Point { x: i32, y: i32, } // Debug: {:?} formatting println!("{:?}", point); // Display: {} formatting (manual impl) impl std::fmt::Display for Point { fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { write!(f, "({}, {})", self.x, self.y) } }
- Clone
- Explicit deep copy via
.clone() - Copy
- Implicit bitwise copy (stack-only types)
- Debug
- Format with
{:?} - Default
- Create default value via
Default::default() - PartialEq/Eq
- Equality comparison (
==) - PartialOrd/Ord
- Ordering comparison (
<,>)
Error Handling
Rust has no exceptions. Errors are values, handled via Result and Option types.
Result Type
// Result<T, E> - success or error enum Result<T, E> { Ok(T), Err(E), } fn divide(a: f64, b: f64) -> Result<f64, String> { if b == 0.0 { Err(String::from("division by zero")) } else { Ok(a / b) } }
Handling Results
// match match divide(10.0, 2.0) { Ok(result) => println!("Result: {}", result), Err(e) => println!("Error: {}", e), } // unwrap (panics on Err) let result = divide(10.0, 2.0).unwrap(); // expect (panics with message) let result = divide(10.0, 2.0).expect("division failed"); // unwrap_or (default value) let result = divide(10.0, 0.0).unwrap_or(0.0); // unwrap_or_else (closure for default) let result = divide(10.0, 0.0).unwrap_or_else(|e| { println!("Error: {}", e); 0.0 });
The ? Operator
// Propagate errors with ? fn read_file() -> Result<String, std::io::Error> { let mut file = File::open("file.txt")?; // returns Err if fails let mut contents = String::new(); file.read_to_string(&mut contents)?; Ok(contents) } // Also works with Option (returns None) fn first_char(s: &str) -> Option<char> { s.lines().next()?.chars().next() }
Custom Error Types
use std::error::Error; use std::fmt; #[derive(Debug)] struct MyError { message: String, } impl fmt::Display for MyError { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { write!(f, "{}", self.message) } } impl Error for MyError {}
anyhow crate for application code, thiserror for library code to simplify error handling.
Collections
Vec (Dynamic Array)
// Create let mut v: Vec<i32> = Vec::new(); let v = vec![1, 2, 3]; // Modify v.push(4); let last = v.pop(); // Option<i32> // Access let third = &v[2]; // panics if out of bounds let third = v.get(2); // Option<&i32> // Iterate for item in &v { println!("{}", item); } // Mutate while iterating for item in &mut v { *item += 10; }
HashMap
use std::collections::HashMap; // Create let mut scores = HashMap::new(); // Insert scores.insert(String::from("Blue"), 10); scores.insert(String::from("Red"), 50); // Access let score = scores.get("Blue"); // Option<&i32> // Insert only if key doesn't exist scores.entry(String::from("Yellow")).or_insert(30); // Update based on old value let count = scores.entry(String::from("Blue")).or_insert(0); *count += 1; // Iterate for (key, value) in &scores { println!("{}: {}", key, value); }
HashSet
use std::collections::HashSet; let mut set = HashSet::new(); set.insert(1); set.insert(2); let contains = set.contains(&1); // true let removed = set.remove(&1); // true
Iterators & Closures
Closures
// Basic closure let add = |a, b| a + b; let result = add(2, 3); // With type annotations let add = |a: i32, b: i32| -> i32 { a + b }; // Capturing environment let x = 4; let equals_x = |z| z == x; // captures x // move keyword (take ownership) let s = String::from("hello"); let closure = move || println!("{}", s);
Iterator Methods
let v = vec![1, 2, 3, 4, 5]; // map: transform each element let doubled: Vec<_> = v.iter().map(|x| x * 2).collect(); // filter: keep matching elements let evens: Vec<_> = v.iter().filter(|x| *x % 2 == 0).collect(); // fold: reduce to single value let sum: i32 = v.iter().fold(0, |acc, x| acc + x); // find: first matching element let first_even = v.iter().find(|x| *x % 2 == 0); // Chain methods let result: i32 = v.iter() .filter(|x| *x % 2 == 0) .map(|x| x * 2) .sum();
Iterator Types
// iter(): borrows elements (&T) for item in v.iter() { } // iter_mut(): mutable borrow (&mut T) for item in v.iter_mut() { } // into_iter(): takes ownership (T) for item in v.into_iter() { } // Ranges for i in 0..10 { } // 0 to 9 for i in 0..=10 { } // 0 to 10 (inclusive)
Cargo & Project Structure
Common Commands
# Create new project cargo new myproject cargo new --lib mylibrary # Build cargo build cargo build --release # Run cargo run cargo run --release # Test cargo test cargo test test_name # Check (fast compile check) cargo check # Format & lint cargo fmt cargo clippy # Documentation cargo doc --open # Add dependency cargo add serde cargo add tokio --features full
Cargo.toml
[package] name = "myproject" version = "0.1.0" edition = "2024" [dependencies] serde = { version = "1.0", features = ["derive"] } tokio = { version = "1", features = ["full"] } [dev-dependencies] criterion = "0.5"
Project Structure
myproject/ โโโ Cargo.toml โโโ src/ โ โโโ main.rs # Binary entry point โ โโโ lib.rs # Library root โ โโโ module/ โ โโโ mod.rs โโโ tests/ # Integration tests โโโ benches/ # Benchmarks โโโ examples/ # Example programs
Modules
// In lib.rs or main.rs mod utils; // loads utils.rs or utils/mod.rs pub mod api; // public module // Use items use crate::utils::helper; use std::collections::HashMap; use serde::{Serialize, Deserialize}; // Re-export pub use self::types::Config;