Ownership, Borrowing, Types & Structs, Pattern Matching, Error Handling, Traits & Generics, Collections, Async/Await, Cargo — systems programming mastery.
// ── Ownership Rules ──
// 1. Each value has exactly ONE owner
// 2. When the owner goes out of scope, the value is dropped
// 3. Assignment moves ownership (except Copy types)
fn main() {
let s1 = String::from("hello"); // s1 owns the String
let s2 = s1; // ownership MOVED to s2
// println!("{}", s1); // ERROR: s1 no longer valid!
let s3 = s2.clone(); // deep copy — both valid
println!("{} {}", s2, s3); // OK
// Copy types (stack-only): i32, f64, bool, char, tuples of Copy types
let x = 42;
let y = x; // COPY, not move
println!("{} {}", x, y); // both valid
}
// ── Functions and Ownership ──
fn takes_ownership(s: String) { // s takes ownership
println!("{}", s);
} // s is dropped here
fn makes_copy(n: i32) { // i32 is Copy — no ownership transfer
println!("{}", n);
}
fn gives_ownership() -> String {
String::from("hello") // ownership moves to caller
}
fn takes_and_gives(s: String) -> String {
s // ownership returned
}// ── References & Borrowing ──
fn main() {
let s1 = String::from("hello");
// Immutable borrow — multiple allowed
let r1 = &s1; // &String (reference)
let r2 = &s1; // OK: multiple immutable borrows
println!("{} and {}", r1, r2); // r1, r2 go out of scope
// Mutable borrow — ONLY ONE at a time
let mut s2 = String::from("hello");
let r3 = &mut s2; // mutable reference
// let r4 = &mut s2; // ERROR: only ONE mutable borrow
r3.push_str(", world");
println!("{}", r3); // "hello, world"
// Cannot mix mutable and immutable borrows
let mut s3 = String::from("hello");
let r5 = &s3; // immutable borrow
// let r6 = &mut s3; // ERROR: cannot borrow mutably
println!("{}", r5); // r5 used here, then dropped
let r6 = &mut s3; // NOW OK — r5 no longer in scope
}
// ── Borrowing in Functions ──
fn calculate_length(s: &String) -> usize { // borrow, don't take ownership
s.len()
}
fn add_world(s: &mut String) { // mutable borrow
s.push_str(", world");
}
fn main() {
let mut s = String::from("hello");
let len = calculate_length(&s); // immutable borrow
println!("{} is {} long", s, len);
add_world(&mut s); // mutable borrow
println!("{}", s); // "hello, world"
}// ── Lifetimes — prevent dangling references ──
// Most lifetimes are inferred; explicit when compiler can't tell
fn longest<'a>(x: &'a str, y: &'a str) -> &'a str {
if x.len() > y.len() { x } else { y }
}
// Lifetime on struct references
struct ImportantExcerpt<'a> {
part: &'a str,
}
fn main() {
let novel = String::from("Call me Ishmael. Some years ago...");
let first_sentence;
{ // new scope
let words = novel.as_str(); // &str borrowed from novel
let excerpt = ImportantExcerpt { part: words };
first_sentence = excerpt.part;
}
println!("{}", first_sentence); // OK — novel still alive
// Static lifetime — lives for entire program
let s: &'static str = "I have a static lifetime.";
// Lifetime elision rules
// Rule 1: each param gets its own lifetime
// Rule 2: if there's ONE input lifetime, it's assigned to all outputs
// Rule 3: if &self or &mut self, self's lifetime is assigned to all outputs
}| Concept | Syntax | Rules |
|---|---|---|
| Move | let y = x | Ownership transfers, original invalidated |
| Clone | .clone() | Deep copy, both valid (heap data) |
| Copy | let y = x | Stack copy, both valid (i32, bool, etc.) |
| Immutable ref | &T | Multiple allowed, read-only |
| Mutable ref | &mut T | Only ONE, exclusive access |
| Deref | *r | Follow the reference to get the value |
// ── Scalar Types ──
let integer: i32 = 42; // i8, i16, i32, i64, i128, isize
let unsigned: u64 = 100; // u8, u16, u32, u64, u128, usize
let float: f64 = 3.14; // f32, f64
let boolean: bool = true;
let character: char = '🦀'; // 4 bytes, Unicode scalar value
let byte: u8 = b'A'; // ASCII byte literal
// ── Type Inference ──
let x = 5; // inferred as i32
let y = 2.0; // inferred as f64
let z: i64 = 5; // explicit type annotation
// ── String Types ──
let s1: &str = "hello"; // &str — string slice (borrowed, fixed size)
let s2: String = String::from("world"); // String — heap-allocated, growable, owned
let s3 = s1.to_string(); // &str → String
let s4: &str = &s2; // String → &str (deref coercion)
// ── Compound Types ──
let tup: (i32, f64, &str) = (1, 3.14, "hello");
let (x, y, z) = tup; // destructuring
let first = tup.0; // index access
let arr: [i32; 5] = [1, 2, 3, 4, 5];
let arr2 = [0; 100]; // [0; 100] — 100 zeros
let first = arr[0];
let slice: &[i32] = &arr[1..3]; // [2, 3]// ── Structs ──
#[derive(Debug)] // auto-generate Debug trait
struct User {
username: String,
email: String,
sign_in_count: u64,
active: bool,
}
// Tuple structs (named fields, but accessed by index)
struct Color(u8, u8, u8);
struct Point(f64, f64);
// Unit-like structs (no fields)
struct AlwaysEqual;
// ── Struct Methods (impl blocks) ──
impl User {
// Associated function (constructor pattern — no &self)
fn new(username: String, email: String) -> Self {
User { username, email, sign_in_count: 0, active: true }
}
// Method — takes &self (borrow)
fn summary(&self) -> String {
format!("{} ({})", self.username, self.email)
}
// Mutable method — takes &mut self
fn login(&mut self) {
self.active = true;
self.sign_in_count += 1;
}
}
// ── Struct Update Syntax ──
let user2 = User {
email: String::from("bob@example.com"),
..user1 // remaining fields from user1
};
// ── Enums ──
enum Message {
Quit, // no data
Move { x: i32, y: i32 }, // named fields
Write(String), // single String
ChangeColor(u8, u8, u8), // three u8 values
}
impl Message {
fn process(&self) {
match self {
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!("Color: ({}, {}, {})", r, g, b),
}
}
}| Type | Size | Range |
|---|---|---|
| i8 / u8 | 1 byte | -128 to 127 / 0 to 255 |
| i16 / u16 | 2 bytes | -32K to 32K / 0 to 65K |
| i32 / u32 | 4 bytes | -2B to 2B / 0 to 4B |
| i64 / u64 | 8 bytes | -9.2E18 to 9.2E18 |
| i128 / u128 | 16 bytes | Very large range |
| isize / usize | arch | Pointer-sized integer |
| Feature | String | &str |
|---|---|---|
| Location | Heap | Borrowed (anywhere) |
| Size | Dynamic, growable | Fixed length |
| Ownership | Owned | Borrowed |
| Mutability | Mutable (mut s) | Only if &mut str |
| Common use | Build/modify strings | Function params, slices |
| Conversion | s.to_string() | &s or s.as_str() |
i32 for integers and f64 for floats. Use usize/isize for indexing and pointer-sized math. Always prefer &str for function parameters (borrowing) unless you need ownership.// ── Match Expressions ──
enum Coin {
Penny, Nickel, Dime, Quarter(UsState),
}
#[derive(Debug)]
struct UsState(String);
fn value_in_cents(coin: &Coin) -> u8 {
match coin {
Coin::Penny => 1,
Coin::Nickel => 5,
Coin::Dime => 10,
Coin::Quarter(state) => { // binding with @
println!("Quarter from {:?}!", state);
25
}
}
}
// ── Match with Guards ──
let num = Some(4);
match num {
Some(x) if x < 5 => println!("less than 5: {}", x),
Some(x) => println!("{}", x),
None => println!("nothing"),
}
// ── Match as Expression ──
let result = match value {
0 => "zero",
1..=10 => "between 1 and 10",
_ => "something else", // catch-all (must be last)
};
// ── Destructuring Structs ──
struct Point { x: i32, y: i32 }
let p = Point { x: 0, y: 7 };
match p {
Point { x: 0, y } => println!("on y-axis at {}", y),
Point { x, y: 0 } => println!("on x-axis at {}", x),
Point { x, y } => println!("at ({}, {})", x, y),
}
// ── Destructuring Enums ──
enum Result<T, E> { Ok(T), Err(E) }
match result {
Ok(value) => process(value),
Err(err) => handle_error(err),
}// ── if let — single pattern match ──
let some_value = Some(7);
if let Some(n) = some_value {
println!("{}", n); // only matches Some
}
// else branch also supported
if let Some(n) = some_value {
// ...
} else {
// handle None
}
// ── let else (Rust 1.65+) ──
let Some(n) = some_value else {
return; // early return if not matching
};
println!("{}", n);
// ── while let — repeated pattern match ──
let mut stack = Vec::new();
stack.push(1);
stack.push(2);
stack.push(3);
while let Some(top) = stack.pop() {
println!("{}", top); // prints 3, 2, 1
}
// ── Match on Multiple Patterns ──
let x = 1;
match x {
1 | 2 => println!("one or two"), // OR pattern
3..=5 => println!("3 through 5"),// range pattern
_ => println!("anything else"),
}
// ── Destructuring Tuples ──
let (a, b, c) = (1, 2, 3);
let (x, _, z) = (1, 2, 3); // _ ignores a value
// ── Destructuring Arrays/Slices ──
let [first, second, ..] = [1, 2, 3, 4, 5];
let [.., last] = [1, 2, 3, 4, 5];
let [first, middle @ .., last] = [1, 2, 3, 4, 5];
// middle = [2, 3, 4]
// ── Refutable vs Irrefutable Patterns ──
// Irrefutable: always matches (let, function params)
// Refutable: might not match (if let, while let, match arms)
let (x, y) = (1, 2); // irrefutable
// let Some(x) = some_value; // ERROR: refutable in let
if let Some(x) = some_value { } // OK: refutable in if let| Pattern | Example | Matches |
|---|---|---|
| Literal | 1 | 2 | 3 | Exact values (OR with |) |
| Range | 1..=100 | Inclusive range |
| Variable | x | Binds to any value |
| Wildcard | _ | Matches anything, discards |
| Destructuring | Point { x, y } | Matches struct fields |
| Enum variant | Some(x) | Matches enum + binds inner |
| Guard | Some(x) if x > 5 | Pattern + condition |
| Binding | x @ 1..=5 | Binds while matching range |
// ── Panic — unrecoverable errors ──
panic!("crash and burn"); // program aborts with message
// Unwrap and expect on Option/Result
let s: Option<&str> = Some("value");
let n = s.unwrap(); // panics if None
let n = s.expect("value should exist"); // panic with message
// ── Option<T> — value might be absent ──
let some_number: Option<i32> = Some(42);
let no_number: Option<i32> = None;
// Safe methods
some_number.is_some() // true
some_number.is_none() // false
no_number.unwrap_or(0) // 0 — default
no_number.unwrap_or_else(|| expensive_default())
some_number.map(|n| n * 2) // Some(84)
no_number.map(|n| n * 2) // None
some_number.and_then(|n| if n > 0 { Some(n) } else { None })
some_number.filter(|&n| n > 0)
some_number.ok_or("error") // Result<i32, &str>
// ── Result<T, E> — operation might fail ──
use std::fs::File;
use std::io;
fn open_file() -> Result<File, io::Error> {
let f = File::open("hello.txt")?; // ? operator propagates error
Ok(f)
}
// Result methods
let ok_result: Result<i32, &str> = Ok(42);
let err_result: Result<i32, &str> = Err("failed");
ok_result.is_ok() // true
err_result.is_err() // true
ok_result.unwrap() // 42
ok_result.unwrap_or(0) // 42
err_result.unwrap_or(0) // 0
ok_result.unwrap_err() // panics
err_result.unwrap_err() // "failed"// ── The ? Operator — propagate errors ──
fn read_username() -> Result<String, io::Error> {
let file = File::open("username.txt")?;
let mut reader = BufReader::new(file);
let mut username = String::new();
reader.read_line(&mut username)?;
Ok(username)
}
// ? works with Option too
fn last_char_of_first_line(text: &str) -> Option<char> {
text.lines().next()?.chars().last()
}
// ── Custom Error Types ──
#[derive(Debug)]
enum AppError {
Io(io::Error),
Parse(std::num::ParseIntError),
NotFound(String),
}
// Implement From for ? operator compatibility
impl From<io::Error> for AppError {
fn from(err: io::Error) -> Self { AppError::Io(err) }
}
impl From<std::num::ParseIntError> for AppError {
fn from(err: std::num::ParseIntError) -> Self { AppError::Parse(err) }
}
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(msg) => write!(f, "Not found: {}", msg),
}
}
}
// thiserror crate (common in production)
// use thiserror::Error;
// #[derive(Error, Debug)]
// enum AppError {
// #[error("IO error: {0}")]
// Io(#[from] io::Error),
// #[error("Not found: {0}")]
// NotFound(String),
// }// ── Option/Result Combination ──
fn find_user(id: u32) -> Option<User> { /* ... */ }
fn get_permission(user: &User) -> Result<Permission, AppError> { /* ... */ }
// Composing with and_then and ?
fn check_permission(id: u32) -> Result<bool, AppError> {
let user = find_user(id).ok_or(AppError::NotFound(
format!("User {} not found", id)
))?;
let perm = get_permission(&user)?;
Ok(perm.can_write)
}
// ── anyhow crate — ergonomic error handling ──
// use anyhow::{Context, Result};
//
// fn read_config() -> Result<Config> {
// let content = std::fs::read_to_string("config.toml")
// .context("Failed to read config file")?;
// let config: Config = toml::from_str(&content)
// .context("Failed to parse config")?;
// Ok(config)
// }
// ── Converting between Option and Result ──
let opt = Some(42);
let res: Result<i32, &str> = opt.ok_or("missing"); // Ok(42)
let none: Option<i32> = None;
let res2: Result<i32, &str> = none.ok_or("missing"); // Err("missing")
let res = Ok(42);
let opt2: Option<i32> = res.ok(); // Some(42)
let err = Err("fail");
let opt3: Option<i32> = err.ok(); // None| Feature | Option<T> | Result<T, E> |
|---|---|---|
| Meaning | Value absent | Operation failed |
| Variants | Some(T), None | Ok(T), Err(E) |
| Use case | Nullable values | Fallible operations |
| Propagation | ?. (not available) | ? operator |
| Common ops | .unwrap_or() | .unwrap_or_else() |
Result for recoverable errors (file not found, invalid input) and panic! for unrecoverable bugs (index out of bounds in test, invariant violation). In libraries, return Result; never panic on expected failures.// ── Defining and Implementing Traits ──
trait Summary {
fn summarize(&self) -> String;
// Default implementation
fn summarize_author(&self) -> String {
String::from("(Read more from author...)")
}
}
struct Article { title: String, author: String, content: String }
struct Tweet { username: String, content: String }
impl Summary for Article {
fn summarize(&self) -> String {
format!("{}: {}", self.author, self.title)
}
}
impl Summary for Tweet {
fn summarize(&self) -> String {
format!("@{}: {}", self.username, self.content)
}
// summarize_author() uses default implementation
}
// ── Traits as Parameters ──
fn notify(item: &impl Summary) { // impl Trait syntax (sugar)
println!("Breaking news! {}", item.summarize());
}
// Trait bound syntax (equivalent, more flexible)
fn notify<T: Summary>(item: &T) {
println!("{}", item.summarize());
}
// Multiple trait bounds
fn notify<T: Summary + Display>(item: &T) { }
fn notify<T>(item: &T) where T: Summary + Display { }
// ── Return Types with impl Trait ──
fn create_summarizable() -> impl Summary {
Tweet {
username: String::from("horse_ebooks"),
content: String::from("of course, as you probably know"),
}
}// ── Generic Functions ──
fn largest<T: PartialOrd>(list: &[T]) -> &T {
let mut largest = &list[0];
for item in &list[1..] {
if item > largest { largest = item; }
}
largest
}
// ── Generic Structs ──
struct Point<T> { x: T, y: T }
impl<T> Point<T> {
fn x(&self) -> &T { &self.x }
// Only for f64 Points
fn distance_from_origin(&self) -> f64
where T: std::ops::Sub<Output = T> + Into<f64> + Copy {
let a: f64 = self.x.into();
let b: f64 = self.y.into();
(a * a + b * b).sqrt()
}
}
// ── Trait Objects (dynamic dispatch) ──
let article = Article { /* ... */ };
let tweet = Tweet { /* ... */ };
// Box<dyn Trait> — trait object on the heap
fn print_summarizable(summarizable: &Box<dyn Summary>) {
println!("{}", summarizable.summarize());
}
// ── Common Traits ──
// Debug — {:?} formatting
#[derive(Debug)]
struct Pair(i32, i32);
// Clone — explicit deep copy
#[derive(Clone)]
struct Config { name: String }
// Copy — implicit stack copy (requires Clone)
#[derive(Clone, Copy)]
struct Point2D { x: f64, y: f64 }
// PartialEq / Eq — equality comparisons
#[derive(PartialEq, Eq)]
struct UserId(u64);
// Hash — usable in HashMap/HashSet
#[derive(Hash, PartialEq, Eq)]
struct Email(String);
// Default — default value
#[derive(Default)]
struct Settings { debug: bool, verbose: bool }// ── Trait Objects vs Generics ──
// Generics: monomorphization (static dispatch) — one function per type
// Trait objects: dynamic dispatch — vtable lookup at runtime
// Generic: fast, but code bloat
fn process_generic<T: Summary>(item: &T) {
println!("{}", item.summarize());
}
// Trait object: flexible, single function
fn process_dyn(item: &dyn Summary) {
println!("{}", item.summarize());
}
// ── Supertraits ──
trait OutlinePrint: Display {
fn outline_print(&self) {
let output = self.to_string();
let len = output.len();
println!("{}", "*".repeat(len + 4));
println!("* {} *", output);
println!("{}", "*".repeat(len + 4));
}
}
// ── Associated Types ──
trait Iterator {
type Item; // associated type
fn next(&mut self) -> Option<Self::Item>;
}
// ── Blanket Implementations ──
// Implement trait for ALL types that implement another trait
impl<T: Display> ToString for T {
fn to_string(&self) -> String {
format!("{}", self)
}
}| Macro | Provides | Requires |
|---|---|---|
| Debug | fmt::Debug | None |
| Clone | .clone() | All fields must impl Clone |
| Copy | Implicit copy | Clone + all fields Copy |
| PartialEq | == and != | Fields impl PartialEq |
| Eq | Total equality | PartialEq |
| Hash | Hash value | Eq |
| Default | Default value | Fields impl Default |
| PartialOrd | <, >, <=, >= | PartialEq |
| Feature | Generic<T> | &dyn Trait |
|---|---|---|
| Dispatch | Static (monomorphization) | Dynamic (vtable) |
| Speed | Faster | Slight overhead |
| Code size | Larger (one copy per type) | Smaller (single copy) |
| Flexibility | Known types at compile time | Heterogeneous collection |
// ── Vec<T> — growable array ──
let mut v: Vec<i32> = Vec::new();
let v2 = vec![1, 2, 3]; // macro shorthand
let v3 = vec![0; 10]; // [0, 0, 0, ..., 0] (10 zeros)
v.push(4);
v.pop(); // Some(4)
v.remove(1); // removes index 1
v.insert(0, 10); // insert at index
v.append(&mut v2); // move all elements
// Access
let third = &v[2]; // panic if out of bounds
let third = v.get(2); // Option<&i32> — safe
// Iteration
for i in &v { println!("{}", i); } // immutable
for i in &mut v { *i += 1; } // mutable
for (idx, val) in v.iter().enumerate() {} // with index
// Sorting
v.sort(); // ascending
v.sort_by(|a, b| b.cmp(a)); // descending
v.sort_by_key(|k| k.abs()); // by key
v.dedup(); // remove consecutive duplicates
// Common methods
v.len(); v.is_empty(); v.contains(&3);
v.clear(); v.reverse();
v.iter().filter(|x| *x > 2).collect::<Vec<_>>();
v.iter().map(|x| x * 2).collect::<Vec<_>>();
v.iter().sum::<i32>();
v.iter().product::<i32>();
v.iter().min(); v.iter().max();
v.iter().find(|&&x| x == 3);
v.windows(2); // sliding windows
v.chunks(3); // non-overlapping chunks// ── HashMap<K, V> ──
use std::collections::HashMap;
let mut scores = HashMap::new();
scores.insert(String::from("Blue"), 10);
scores.insert(String::from("Red"), 50);
// Only insert if key doesn't exist
scores.entry(String::from("Green")).or_insert(30);
scores.entry(String::from("Blue")).or_insert(100); // won't overwrite
// Entry API — modify based on existing value
let text = "hello world wonderful world";
let mut word_count = HashMap::new();
for word in text.split_whitespace() {
let count = word_count.entry(word).or_insert(0);
*count += 1;
}
// Access
let team = scores.get("Blue"); // Option<&i32>
for (key, value) in &scores { } // iterate
scores.remove("Red"); // returns Option<V>
scores.contains_key("Blue"); // bool
scores.len(); // number of entries
// ── HashSet<T> ──
use std::collections::HashSet;
let mut set: HashSet<i32> = HashSet::new();
set.insert(1);
set.insert(2);
set.insert(1); // no duplicate
set.contains(&1); // true
// Set operations
let a: HashSet<_> = [1, 2, 3].iter().cloned().collect();
let b: HashSet<_> = [2, 3, 4].iter().cloned().collect();
let union: HashSet<_> = a.union(&b).cloned().collect();
let diff: HashSet<_> = a.difference(&b).cloned().collect();
let inter: HashSet<_> = a.intersection(&b).cloned().collect();
let sym_diff: HashSet<_> = a.symmetric_difference(&b).cloned().collect();
// ── String manipulation ──
let s = String::from("Hello, World!");
s.len(); // 13 bytes
s.is_empty(); // false
s.push_str(" Rust"); // append
s.push('!'); // append char
s.insert(5, ' '); // insert at byte index
s.replace("World", "Rust"); // returns new String
s.split_whitespace().collect::<Vec<_>>(); // ["Hello,", "World!"]
s.split(", ").collect::<Vec<_>>(); // ["Hello", "World!"]
s.trim(); s.trim_start(); s.trim_end();
s.to_lowercase(); s.to_uppercase();
s.contains("World"); s.starts_with("Hello");
s.split_at(5); // ("Hello", ", World!")| Operation | Vec | HashMap | HashSet |
|---|---|---|---|
| Push/Insert | O(1)* | O(1)* | O(1)* |
| Pop/Remove | O(1) | O(1) | O(1) |
| Index Access | O(1) | O(1) avg | N/A |
| Search | O(n) | O(1) avg | O(1) avg |
| Sort | O(n log n) | N/A | N/A |
| Iterate | O(n) | O(n) | O(n) |
*Amortized for Vec; worst case O(n) on reallocation. HashMap average O(1), worst O(n).
// ── Async Functions ──
// async fn returns a Future, not the value directly
async fn fetch_data(url: &str) -> Result<String, reqwest::Error> {
let response = reqwest::get(url).await?;
Ok(response.text().await?)
}
#[tokio::main] // tokio async runtime
async fn main() {
match fetch_data("https://example.com").await {
Ok(data) => println!("Got {} bytes", data.len()),
Err(e) => eprintln!("Error: {}", e),
}
}
// ── Spawning Tasks ──
use tokio::task;
#[tokio::main]
async fn main() {
let handle = task::spawn(async {
// runs on a separate task
fetch_data("https://api.example.com").await
});
// Do other work while the task runs...
println!("Doing other work");
let result = handle.await.unwrap();
println!("Task result: {:?}", result);
}
// ── Joining Multiple Futures ──
use tokio::join;
#[tokio::main]
async fn main() {
let (a, b, c) = join!(
fetch_data("url1"),
fetch_data("url2"),
fetch_data("url3"),
);
// All three run concurrently!
}
// ── select! — wait for first to complete ──
use tokio::sync::mpsc;
use tokio::time::{sleep, Duration};
#[tokio::main]
async fn main() {
let (tx, mut rx) = mpsc::channel::<&str>(10);
tokio::spawn(async move {
sleep(Duration::from_millis(100)).await;
tx.send("from task").await.unwrap();
});
tokio::select! {
msg = rx.recv() => println!("Received: {}", msg.unwrap()),
_ = sleep(Duration::from_secs(1)) => println!("Timeout!"),
}
}// ── Async Streams ──
use tokio_stream::{self as stream, StreamExt}; // StreamExt for .next()
#[tokio::main]
async fn main() {
// Create a stream from an iterator
let mut stream = stream::iter(vec![1, 2, 3, 4, 5]);
while let Some(value) = stream.next().await {
println!("Value: {}", value);
}
// Chaining and transforming streams
let values = vec![1, 2, 3, 4, 5];
let stream = stream::iter(values)
.filter(|x| futures::future::ready(*x % 2 == 0))
.map(|x| x * 2);
tokio::pin!(stream);
while let Some(value) = stream.next().await {
println!("Mapped: {}", value);
}
}
// ── Channels ──
use tokio::sync::{mpsc, oneshot};
// Multi-producer, single-consumer channel
async fn channel_example() {
let (tx, mut rx) = mpsc::channel::<i32>(32);
tokio::spawn(async move {
for i in 0..10 {
tx.send(i).await.unwrap();
}
});
while let Some(value) = rx.recv().await {
println!("Received: {}", value);
}
}
// ── Mutex and RwLock (async-safe) ──
use tokio::sync::{Mutex, RwLock};
use std::sync::Arc;
async fn shared_state() {
let data = Arc::new(Mutex::new(0));
let mut handles = vec![];
for _ in 0..10 {
let data = Arc::clone(&data);
handles.push(tokio::spawn(async move {
let mut d = data.lock().await;
*d += 1;
}));
}
for handle in handles { handle.await.unwrap(); }
println!("Result: {}", *data.lock().await);
}| Crate | Use Case | Notes |
|---|---|---|
| tokio | Full-featured runtime | Most popular, multi-threaded |
| async-std | Std-like async API | Simpler API, good for learning |
| smol | Lightweight runtime | Small footprint, composability |
tokio::spawn for fire-and-forget concurrent tasks. Use join! when you need all results. Use select! when you want to cancel on timeout or first completion.# ── Cargo.toml ──
[package]
name = "my_project"
version = "0.1.0"
edition = "2021"
authors = ["Your Name <you@example.com>"]
description = "A Rust project"
license = "MIT"
repository = "https://github.com/user/my_project"
[dependencies]
serde = { version = "1.0", features = ["derive"] }
tokio = { version = "1", features = ["full"] }
reqwest = { version = "0.12", features = ["json"] }
anyhow = "1.0"
thiserror = "1.0"
clap = { version = "4", features = ["derive"] }
[dev-dependencies]
assert_cmd = "2"
predicates = "3"
[profile.release]
opt-level = 3
lto = true # Link-time optimization
strip = true # Strip debug symbols
[[bin]]
name = "my_cli"
path = "src/bin/cli.rs"# ── Essential Cargo Commands ──
cargo new my_project # create new binary project
cargo new --lib my_lib # create library project
cargo build # compile (debug mode)
cargo build --release # compile (optimized)
cargo run # build + run
cargo run --bin my_cli # run specific binary
cargo test # run all tests
cargo test -- --test-threads=1 # single-threaded tests
cargo test my_module # test specific module
cargo test -- --ignored # run ignored tests
cargo test -- --nocapture # show println! output
cargo bench # run benchmarks
cargo doc --open # generate + open docs
cargo clippy # linting
cargo fmt # format code
cargo fmt -- --check # check formatting
cargo tree # show dependency tree
cargo outdated # check for updates
cargo audit # security audit
cargo expand # show macro expansion
cargo udeps # find unused dependencies// ── Module System ──
// src/main.rs or src/lib.rs is the crate root
// Inline module
mod utils {
pub fn add(a: i32, b: i32) -> i32 { a + b }
pub fn multiply(a: i32, b: i32) -> i32 { a * b }
// Private by default
fn helper() { }
}
// File-based module: src/network/mod.rs or src/network.rs
// mod network; // declares the module
// src/network/mod.rs
pub mod client {
pub fn connect() { }
}
pub mod server {
pub fn start() { }
}
// ── Re-exports ──
pub use network::client::connect;
// Now users can do: my_lib::connect()
// ── use statements ──
use std::collections::HashMap;
use std::fs::{self, File}; // multiple items
use std::io::prelude::*; // bring all traits into scope
// ── Crates (external dependencies) ──
use serde::{Deserialize, Serialize};
use anyhow::{Context, Result};
// ── Testing ──
#[cfg(test)]
mod tests {
use super::*; // import parent module
#[test]
fn test_addition() {
assert_eq!(add(2, 3), 5);
}
#[test]
#[should_panic(expected = "division by zero")]
fn test_panic() {
let _ = 1 / 0;
}
#[test]
fn test_error() -> Result<()> {
let file = File::open("test.txt")
.context("Failed to open test file")?;
Ok(())
}
}| Crate | Category | Purpose |
|---|---|---|
| serde | Serialization | JSON, YAML, TOML, etc. |
| tokio | Async | Async runtime, I/O, channels |
| reqwest | HTTP | HTTP client |
| axum | Web | HTTP server framework |
| clap | CLI | Command-line argument parsing |
| anyhow | Errors | Ergonomic error handling |
| thiserror | Errors | Custom error derive macro |
| tracing | Logging | Structured logging/instrumentation |
| rayon | Parallel | Data parallelism |
| itertools | Iterators | Extra iterator methods |
Ownership is Rust's memory management system. Every value has a single owner. When the owner goes out of scope, the value is dropped (memory freed). Unlike garbage collection (Java, Go) which uses runtime tracing, Rust enforces memory safety at compile time through the borrow checker. This gives deterministic memory management with zero runtime overhead.
String is a heap-allocated, growable, owned string (similar to Vec<u8>). &str is a borrowed slice of a string — it can point to a String, a string literal, or any contiguous UTF-8 bytes. Use &str for function parameters (borrowing), and String when you need to own or modify the data.
The ? operator propagates errors. It works on both Result and Option. For Result: if Ok(v), unwraps to v; if Err(e), returns early with the error. For Option: if Some(v), unwraps; if None, returns early with None. It automatically converts error types via From.
Traits define shared behavior (like interfaces). Key differences: (1) You can implement a trait for any type, even types you don't own (orphan rule permitting). (2) Traits can have default implementations. (3) Rust has blanket implementations (impl Trait for all types impl AnotherTrait). (4) Associated types allow type-level generics within traits. Traits can be used as bounds on generics or as trait objects (&dyn Trait).
Clone creates an explicit deep copy via .clone(). Copy creates an implicit bitwise copy on assignment — it's a marker trait that tells the compiler "this type can be copied cheaply." Types with Copy must also implement Clone. Copy types cannot contain heap data (no String, Vec, Box).
Lifetimes are Rust's way of preventing dangling references at compile time. A lifetime is the scope during which a reference is valid. The borrow checker uses lifetimes to verify that references always point to valid data. Most lifetimes are inferred (lifetime elision rules). Explicit lifetimes ('a) are needed when the compiler can't determine the relationship between input and output reference lifetimes. Static lifetime ('static) means the reference lives for the entire program.
Interior mutability allows mutation through a shared reference (&T). Types include: RefCell<T> (runtime borrow checking, single-threaded), Cell<T> (for Copy types), Mutex<T> (multi-threaded mutual exclusion), RwLock<T> (multiple readers or one writer), and Atomic types (lock-free atomic operations). Use these when you need interior mutation in otherwise immutable contexts.
The orphan rule prevents you from implementing a foreign trait on a foreign type. At least one of the trait or the type must be local to your crate. This prevents coherence issues — two crates could implement the same trait for the same type, causing ambiguity. You can work around it with the newtype pattern: wrap the foreign type in your own struct.
String/&str, Clone/Copy, Box/Rc/Arc. Be able to explain WHY Rust makes design choices (memory safety without GC, fearless concurrency).