Rust Programming
Systems programming with memory safety and zero-cost abstractions
IT & Tech Reports | Class IM24A | 2026
1. Why Rust?
Rust is a systems programming language focused on memory safety, concurrency, and performance. It
achieves memory safety without a garbage collector through its ownership system — a set of rules
checked at compile time. Rust has been the most loved programming language on Stack Overflow
Developer Surveys for 9 consecutive years (2016-2024).
Feature C/C++ Go Rust
Memory safety Manual (undefined behaviour
GC (pauses)
risk) Compile-time (no GC, no bugs)
Performance Native Near-native Native (zero-cost abstractions)
Concurrency Unsafe (data races possible)
Goroutines + GC Fearless (compiler prevents races)
Build time Fast Fast Slow (complex type system)
Error handling Return codes / exceptions
error interface Result<T,E> — explicit
Package manager None (vcpkg, conan) go modules Cargo (excellent)
2. Ownership, Borrowing, and Lifetimes
The ownership system is Rust's most unique feature. Three rules: (1) every value has an owner; (2)
there can only be one owner at a time; (3) when the owner goes out of scope, the value is dropped
(memory freed automatically).
fn main() {
// Ownership: s1 owns the String
let s1 = String::from("hello");
// MOVE: ownership transferred to s2
let s2 = s1;
// println!("{}", s1); // compile error: s1 was moved!
// CLONE: deep copy if you need both
let s3 = [Link]();
println!("s2={s2}, s3={s3}"); // both valid
// Stack types (Copy trait) are copied, not moved
let x = 5;
let y = x;
println!("{x} {y}"); // both valid — integers implement Copy
// BORROWING: borrow without taking ownership
let len = calculate_length(&s3;); // pass reference
println!("'{s3}' has length {len}"); // s3 still valid
// MUTABLE BORROW: only one at a time
let mut s = String::from("hello");
let r1 = &s; // immutable borrow
let r2 = &s; // second immutable borrow — OK
println!("{r1} {r2}");
// r1 and r2 no longer used here
let r3 = &mut; s; // mutable borrow — OK (r1,r2 no longer active)
r3.push_str(", world");
}
fn calculate_length(s: &String;) -> usize { // s is a reference
[Link]()
} // s goes out of scope but does NOT own the data — nothing dropped
3. Structs, Enums, and Pattern Matching
// Struct with methods
#[derive(Debug, Clone)]
struct Student {
name: String,
score: f32,
class: String,
}
impl Student {
// Associated function (like static method)
fn new(name: &str;, score: f32, class: &str;) -> Self {
Student {
name: name.to_owned(),
score,
class: class.to_owned(),
}
}
// Method — takes &self; (immutable borrow)
fn grade(&self;) -> &str; {
match [Link] {
s if s >= 5.5 => "Excellent",
s if s >= 4.0 => "Pass",
_ => "Fail",
}
}
fn is_passing(&self;) -> bool { [Link] >= 4.0 }
}
// Enum with data — Rust enums are sum types (algebraic data types)
#[derive(Debug)]
enum Shape {
Circle { radius: f64 },
Rectangle { width: f64, height: f64 },
Triangle(f64, f64, f64), // sides
}
impl Shape {
fn area(&self;) -> f64 {
match self {
Shape::Circle { radius } => std::f64::consts::PI * radius * radius,
Shape::Rectangle { width, height } => width * height,
Shape::Triangle(a, b, c) => {
let s = (a + b + c) / 2.0;
(s * (s-a) * (s-b) * (s-c)).sqrt()
}
}
}
}
fn main() {
let mut students = vec![
Student::new("Ivan", 5.8, "IM24A"),
Student::new("Anna", 4.2, "IM24A"),
Student::new("Bob", 3.5, "IM24B"),
];
// Pattern matching with if let
if let Some(top) = [Link]().max_by(|a,b| [Link].partial_cmp(&[Link];).unwrap())
{
println!("Top student: {} ({})", [Link], [Link]);
}
// Filter and collect
let passing: Vec<&Student;> = [Link]().filter(|s| s.is_passing()).collect();
println!("Passing: {}", [Link]());
}
4. Error Handling with Result and Option
use std::fs::File;
use std::io::{self, BufRead};
use std::num::ParseIntError;
// Custom error type
#[derive(Debug)]
enum AppError {
Io(io::Error),
Parse(ParseIntError),
NotFound(String),
}
impl std::fmt::Display for AppError {
fn fmt(&self;, f: &mut; std::fmt::Formatter) -> std::fmt::Result {
match self {
AppError::Io(e) => write!(f, "IO error: {e}"),
AppError::Parse(e) => write!(f, "Parse error: {e}"),
AppError::NotFound(s) => write!(f, "Not found: {s}"),
}
}
}
impl From for AppError { fn from(e: io::Error) -> Self { AppError::Io(e) } }
impl From for AppError { fn from(e: ParseIntError) -> Self { AppError::Parse(e) } }
// ? operator propagates errors automatically
fn read_score(path: &str;) -> Result {
let file = File::open(path)?; // io::Error -> AppError::Io
let line = io::BufReader::new(file)
.lines()
.next()
.ok_or(AppError::NotFound("empty file".into()))??;
let score: i32 = [Link]().parse()?; // ParseIntError -> AppError::Parse
Ok(score)
}
fn main() {
match read_score("[Link]") {
Ok(score) => println!("Score: {score}"),
Err(e) => eprintln!("Error: {e}"),
}
// Option — value that might be absent
let names = vec!["Ivan", "Anna", "Bob"];
let found: Option<&&str;> = [Link]().find(|&&n;| n.starts_with('A'));
let name = found.unwrap_or(&"unknown"); // safe unwrap with default
println!("Found: {name}");
// Chaining Option
let score: Option = Some("5.8").and_then(|s| [Link]().ok());
let doubled = [Link](|s| s * 2.0).unwrap_or(0.0);
}
5. Traits — Rust's Interface System
// Trait definition
trait Printable {
fn print(&self;);
fn summary(&self;) -> String { format!("{:?}", self as *const Self) } // default impl
}
// Trait with generic bound
fn print_all(items: &[T]) {
for item in items { [Link](); }
}
// Multiple bounds
fn debug_and_print(item: &T;) {
println!("{:?}", item);
[Link]();
}
// impl Trait syntax (simpler for return types)
fn make_printable(name: &str;) -> impl Printable {
struct Named(String);
impl Printable for Named {
fn print(&self;) { println!("Named: {}", self.0); }
}
Named(name.to_owned())
}
// Common standard traits
// Display — for user-facing formatting (println!("{}", x))
// Debug — for debug formatting (println!("{:?}", x)) — derive with #[derive(Debug)]
// Clone — explicit deep copy — derive with #[derive(Clone)]
// Copy — implicit bitwise copy (integers, bools, tuples of Copy types)
// PartialEq — == and != operators
// Ord — total ordering (sorting)
// Iterator — enables for loops and iterator adapters
// From / Into — type conversions
// Default — zero/empty value (String::default() = "")
6. Closures and Iterators
fn main() {
let numbers = vec![1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
// Closures: anonymous functions that capture their environment
let threshold = 5;
let is_big = |x: &i32;| *x > threshold; // captures threshold
// Iterator adapters (lazy — no computation until collected)
let result: Vec = [Link]()
.filter(|&&x;| x % 2 == 0) // keep evens: [2,4,6,8,10]
.map(|&x;| x * x) // square: [4,16,36,64,100]
.filter(is_big) // > 5: [16,36,64,100]
.take(3) // first 3: [16,36,64]
.collect();
println!("{result:?}"); // [16, 36, 64]
// fold — like reduce
let sum: i32 = (1..=100).fold(0, |acc, x| acc + x); // 5050
// flatten
let nested = vec![vec![1,2], vec![3,4], vec![5,6]];
let flat: Vec = nested.into_iter().flatten().collect();
// zip
let names = vec!["Ivan", "Anna"];
let scores = vec![5.8, 4.2];
let pairs: Vec<_> = [Link]().zip([Link]()).collect();
// Custom iterator
struct Counter { count: u32, max: u32 }
impl Iterator for Counter {
type Item = u32;
fn next(&mut; self) -> Option {
if [Link] < [Link] {
[Link] += 1;
Some([Link])
} else { None }
}
}
let sum: u32 = Counter { count: 0, max: 5 }.sum(); // 15
}
7. Concurrency — Fearless Multithreading
use std::thread;
use std::sync::{Arc, Mutex, mpsc};
// Thread spawning
let handle = thread::spawn(|| {
println!("Hello from thread {:?}", thread::current().id());
});
[Link]().unwrap();
// Sharing data: Arc>
let counter = Arc::new(Mutex::new(0));
let mut handles = vec![];
for _ in 0..10 {
let counter = Arc::clone(&counter;); // clone the Arc (increments ref count)
let h = thread::spawn(move || {
let mut num = [Link]().unwrap();
*num += 1;
});
[Link](h);
}
for h in handles { [Link]().unwrap(); }
println!("Counter: {}", *[Link]().unwrap()); // 10
// Message passing with channels
let (tx, rx) = mpsc::channel::();
let tx2 = [Link]();
thread::spawn(move || { [Link]("Hello from thread 1".to_owned()).unwrap(); });
thread::spawn(move || { [Link]("Hello from thread 2".to_owned()).unwrap(); });
for msg in [Link]().take(2) {
println!("Received: {msg}");
}
// Async Rust with tokio
use tokio;
#[tokio::main]
async fn main_async() {
let a = tokio::spawn(async { fetch_data("url1").await });
let b = tokio::spawn(async { fetch_data("url2").await });
let (ra, rb) = tokio::join!(a, b);
}
8. Common Rust Patterns and Idioms
// Builder pattern
#[derive(Debug)]
struct Config {
host: String,
port: u16,
max_connections: usize,
}
struct ConfigBuilder {
host: String,
port: u16,
max_connections: usize,
}
impl ConfigBuilder {
fn new() -> Self {
Self { host: "localhost".into(), port: 8080, max_connections: 100 }
}
fn host(mut self, host: &str;) -> Self { [Link] = [Link](); self }
fn port(mut self, port: u16) -> Self { [Link] = port; self }
fn max_conn(mut self, n: usize) -> Self { self.max_connections = n; self }
fn build(self) -> Config {
Config { host: [Link], port: [Link], max_connections: self.max_connections }
}
}
let config = ConfigBuilder::new()
.host("[Link]")
.port(3000)
.max_conn(200)
.build();
// Newtype pattern — type safety without runtime cost
struct Metres(f64);
struct Seconds(f64);
fn speed(distance: Metres, time: Seconds) -> f64 {
distance.0 / time.0 // compiler prevents mixing up units
}
// Type state pattern — enforce protocol at compile time
struct Locked;
struct Unlocked;
struct Safe { contents: String, _state: std::marker::PhantomData }
impl Safe {
fn unlock(self, _pin: &str;) -> Safe {
Safe { contents: [Link], _state: std::marker::PhantomData }
}
}
impl Safe {
fn retrieve(&self;) -> &str; { &[Link]; }
fn lock(self) -> Safe {
Safe { contents: [Link], _state: std::marker::PhantomData }
}
}
9. Cargo — Rust's Build System and Package Manager
# Create new project
cargo new my-project # binary
cargo new my-lib --lib # library
# [Link] — project manifest
[package]
name = "my-project"
version = "0.1.0"
edition = "2021"
[dependencies]
serde = { version = "1", features = ["derive"] }
tokio = { version = "1", features = ["full"] }
anyhow = "1"
[dev-dependencies]
criterion = "0.5"
# Common cargo commands
cargo build # debug build
cargo build --release # optimised build
cargo run # build + run
cargo test # run all tests
cargo test -- --nocapture # show println! in tests
cargo check # fast type check (no code gen)
cargo clippy # linter — more warnings than rustc
cargo fmt # format code (rustfmt)
cargo doc --open # generate and open documentation
cargo add serde # add dependency
cargo update # update [Link]
cargo publish # publish to [Link]
10. Where Rust Excels
Domain Why Rust? Examples
Systems programming No GC, no overhead, full control Linux kernel modules, device drivers
WebAssembly Compiles to small, fast WASM bundles
Figma, Cloudflare Workers, game engines
Domain Why Rust? Examples
CLI tools Fast startup, static binary ripgrep, fd, bat, exa, starship prompt
Networking Async tokio runtime, zero-copy axum web framework, quinn QUIC
Embedded no_std support, tiny binary size RTOS, IoT firmware, automotive
Game engines Performance + safety Bevy, Fyrox
Cryptography Timing-safe operations, no memoryrustls
bugs TLS, ring crypto
Data engineering Speed comparable to C Polars (faster than pandas), Apache Arrow