NexusCS

Rust

Programming
Quick reference for Rust - a systems programming language focused on safety, speed, and concurrency with zero-cost abstractions and memory safety without garbage collection.
featured

Getting started

Hello world

fn main() {
    println!("Hello, world!");
    println!("x = {}", 42);        // Formatted
    println!("{:?}", vec![1, 2]);  // Debug format
}

Variables

let x = 5;                    // Immutable
let mut y = 10;               // Mutable
y = 15;                       // Ok

const MAX: u32 = 100;         // Constant
static GLOBAL: &str = "hi";   // Static

Data types

// Integers
let a: i32 = -42;             // Signed 32-bit
let b: u64 = 100;             // Unsigned 64-bit

// Floats
let c: f64 = 3.14;

// Boolean
let d: bool = true;

// Character
let e: char = 'z';

// String
let s: &str = "hello";        // String slice
let s: String = String::from("hello");

Functions

fn add(x: i32, y: i32) -> i32 {
    x + y                     // No semicolon = return
}

fn nothing() {
    println!("No return");
}

// Multiple returns with tuple
fn swap(x: i32, y: i32) -> (i32, i32) {
    (y, x)
}

Control flow

If expressions

let number = 6;

if number % 2 == 0 {
    println!("even");
} else {
    println!("odd");
}

// If as expression
let result = if number > 5 { "big" } else { "small" };

Loops

// Infinite loop
loop {
    println!("forever");
    break;                    // Exit loop
}

// While loop
while number < 10 {
    number += 1;
}

// For loop
for i in 0..5 {              // Range 0 to 4
    println!("{}", i);
}

for item in &vec![1, 2, 3] {
    println!("{}", item);
}

Match expressions

let number = 3;

match number {
    1 => println!("one"),
    2 | 3 => println!("two or three"),
    4..=6 => println!("four through six"),
    _ => println!("something else"),  // Catch-all
}

// Match as expression
let description = match number {
    1 => "one",
    _ => "other",
};

if let / while let

// if let - pattern matching shorthand
let some_value = Some(3);

if let Some(x) = some_value {
    println!("Got: {}", x);
} else {
    println!("No value");
}

// More concise than:
// match some_value {
//     Some(x) => println!("Got: {}", x),
//     None => println!("No value"),
// }

// while let - loop with pattern matching
let mut stack = vec![1, 2, 3];

while let Some(top) = stack.pop() {
    println!("{}", top);
}

// Process values until None
let mut optional = Some(0);
while let Some(i) = optional {
    if i > 5 {
        optional = None;
    } else {
        optional = Some(i + 1);
    }
}

Ownership & Borrowing

Ownership rules

let s1 = String::from("hello");
let s2 = s1;                  // s1 moved to s2
// println!("{}", s1);        // ⚠️ Error: s1 no longer valid

let s3 = s2.clone();          // Deep copy
println!("{} {}", s2, s3);    // Both valid

Ownership rules:

  • Each value has one owner
  • Owner goes out of scope → value dropped
  • Assignment moves ownership (except Copy types)

References & borrowing

let s = String::from("hello");

// Immutable borrow
let r1 = &s;
let r2 = &s;
println!("{} {}", r1, r2);    // Ok: multiple immutable refs

// Mutable borrow
let mut s = String::from("hello");
let r = &mut s;
r.push_str(" world");
// ⚠️ Can't have other refs while mutable ref exists

Slices

let s = String::from("hello world");

let hello: &str = &s[0..5];   // Slice
let world: &str = &s[6..11];

// Array slices
let a = [1, 2, 3, 4, 5];
let slice: &[i32] = &a[1..3]; // [2, 3]

Structs

Defining structs

struct User {
    username: String,
    email: String,
    active: bool,
    sign_in_count: u64,
}

// Tuple struct
struct Color(i32, i32, i32);

// Unit struct
struct AlwaysEqual;

Using structs

let user = User {
    email: String::from("test@example.com"),
    username: String::from("user123"),
    active: true,
    sign_in_count: 1,
};

// Access fields
println!("{}", user.email);

// Mutable struct
let mut user2 = User { ..user };  // Struct update syntax
user2.email = String::from("new@example.com");

Methods

struct Rectangle {
    width: u32,
    height: u32,
}

impl Rectangle {
    // Method (takes &self)
    fn area(&self) -> u32 {
        self.width * self.height
    }

    // Associated function (no self)
    fn square(size: u32) -> Rectangle {
        Rectangle { width: size, height: size }
    }
}

// Usage
let rect = Rectangle { width: 30, height: 50 };
println!("{}", rect.area());
let sq = Rectangle::square(20);

Enums

Defining enums

enum IpAddr {
    V4(u8, u8, u8, u8),
    V6(String),
}

enum Message {
    Quit,
    Move { x: i32, y: i32 },
    Write(String),
    ChangeColor(i32, i32, i32),
}

Option enum

// Built-in enum for nullable values
enum Option<T> {
    Some(T),
    None,
}

let some_number = Some(5);
let absent_number: Option<i32> = None;

// Using Option
match some_number {
    Some(i) => println!("Got: {}", i),
    None => println!("No value"),
}

// Unwrap shortcuts
let x = some_number.unwrap_or(0);     // 5
let y = absent_number.unwrap_or(0);   // 0

Result enum

// Built-in enum for error handling
enum Result<T, E> {
    Ok(T),
    Err(E),
}

fn divide(a: i32, b: i32) -> Result<i32, String> {
    if b == 0 {
        Err(String::from("division by zero"))
    } else {
        Ok(a / b)
    }
}

// Usage
match divide(10, 2) {
    Ok(result) => println!("Result: {}", result),
    Err(e) => println!("Error: {}", e),
}

Collections

Vectors

// Create vector
let v: Vec<i32> = Vec::new();
let v = vec![1, 2, 3];        // Macro

// Add elements
let mut v = Vec::new();
v.push(5);
v.push(6);

// Access elements
let third = &v[2];            // Panics if out of bounds
let third = v.get(2);         // Returns Option<&T>

// Iterate
for i in &v {
    println!("{}", i);
}

// Mutable iteration
for i in &mut v {
    *i += 50;
}

Strings

// Create string
let mut s = String::new();
let s = String::from("hello");
let s = "hello".to_string();

// Append
s.push_str(" world");
s.push('!');

// Concatenate
let s1 = String::from("Hello, ");
let s2 = String::from("world!");
let s3 = s1 + &s2;            // s1 moved
let s4 = format!("{}{}", s2, s3);

// Slicing ⚠️ Use carefully with UTF-8
let hello = &s[0..5];

// Iteration
for c in "नमस्ते".chars() {
    println!("{}", c);
}

Hash maps

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 team = String::from("Blue");
let score = scores.get(&team);    // Returns Option<&V>

// Iterate
for (key, value) in &scores {
    println!("{}: {}", key, value);
}

// Update
scores.insert(String::from("Blue"), 25);  // Overwrites

// Insert if absent
scores.entry(String::from("Yellow")).or_insert(50);

// Update based on old value
let count = scores.entry(String::from("Blue")).or_insert(0);
*count += 1;

Error handling

Panic

// Unrecoverable errors
panic!("crash and burn");

// Access out of bounds causes panic
let v = vec![1, 2, 3];
v[99];                        // Panic!

Result type

use std::fs::File;
use std::io::ErrorKind;

// Explicit match
let f = File::open("hello.txt");
let f = match f {
    Ok(file) => file,
    Err(error) => match error.kind() {
        ErrorKind::NotFound => match File::create("hello.txt") {
            Ok(fc) => fc,
            Err(e) => panic!("Error creating: {:?}", e),
        },
        other_error => panic!("Error opening: {:?}", other_error),
    },
};

// Shortcuts
let f = File::open("hello.txt").unwrap();  // Panic on error
let f = File::open("hello.txt").expect("Failed to open");

Propagating errors

use std::io;
use std::fs::File;
use std::io::Read;

fn read_username() -> Result<String, io::Error> {
    let mut f = File::open("hello.txt")?;  // ? operator
    let mut s = String::new();
    f.read_to_string(&mut s)?;
    Ok(s)
}

// ? operator shortcuts
fn read_username_short() -> Result<String, io::Error> {
    let mut s = String::new();
    File::open("hello.txt")?.read_to_string(&mut s)?;
    Ok(s)
}

Traits

Defining traits

trait Summary {
    fn summarize(&self) -> String;

    // Default implementation
    fn summarize_default(&self) -> String {
        String::from("(Read more...)")
    }
}

// Implement trait
struct Article {
    title: String,
    content: String,
}

impl Summary for Article {
    fn summarize(&self) -> String {
        format!("{}: {}", self.title, self.content)
    }
}

Trait bounds

// Trait as parameter
fn notify(item: &impl Summary) {
    println!("{}", item.summarize());
}

// Trait bound syntax
fn notify<T: Summary>(item: &T) {
    println!("{}", item.summarize());
}

// Multiple traits
fn notify<T: Summary + Display>(item: &T) {}

// Where clause for readability
fn some_function<T, U>(t: &T, u: &U) -> i32
where
    T: Display + Clone,
    U: Clone + Debug,
{
    // ...
}

Returning traits

// Return impl Trait
fn returns_summarizable() -> impl Summary {
    Article {
        title: String::from("Title"),
        content: String::from("Content"),
    }
}

// ⚠️ Can only return single type
// This won't work if returning different types:
// fn returns_summarizable(switch: bool) -> impl Summary {
//     if switch { Article {...} } else { Tweet {...} }
// }

Trait objects

// Dynamic dispatch with dyn Trait
trait Draw {
    fn draw(&self);
}

struct Circle {
    radius: f64,
}

struct Square {
    side: f64,
}

impl Draw for Circle {
    fn draw(&self) {
        println!("Drawing circle");
    }
}

impl Draw for Square {
    fn draw(&self) {
        println!("Drawing square");
    }
}

// Box<dyn Trait> for heterogeneous collections
let shapes: Vec<Box<dyn Draw>> = vec![
    Box::new(Circle { radius: 5.0 }),
    Box::new(Square { side: 10.0 }),
];

for shape in shapes {
    shape.draw();  // Dynamic dispatch
}

// Static vs dynamic dispatch
fn static_dispatch(item: &impl Draw) {    // Monomorphization
    item.draw();
}

fn dynamic_dispatch(item: &dyn Draw) {    // Runtime vtable lookup
    item.draw();
}

Static dispatch (impl Trait):

  • Faster (no vtable)
  • Generates code for each type
  • Larger binary size

Dynamic dispatch (dyn Trait):

  • Runtime flexibility
  • Single function body
  • Smaller binary, slight overhead

From/Into traits

// Implementing From<T>
struct Number {
    value: i32,
}

impl From<i32> for Number {
    fn from(item: i32) -> Self {
        Number { value: item }
    }
}

// Into is automatically implemented
let num: Number = 5.into();
let num = Number::from(5);

// Error conversion pattern
use std::fs::File;
use std::io;

#[derive(Debug)]
enum MyError {
    Io(io::Error),
    Parse(std::num::ParseIntError),
}

impl From<io::Error> for MyError {
    fn from(error: io::Error) -> Self {
        MyError::Io(error)
    }
}

impl From<std::num::ParseIntError> for MyError {
    fn from(error: std::num::ParseIntError) -> Self {
        MyError::Parse(error)
    }
}

// Use with ? operator
fn read_and_parse() -> Result<i32, MyError> {
    let mut file = File::open("number.txt")?;  // Auto-converts io::Error
    let mut contents = String::new();
    file.read_to_string(&mut contents)?;
    let num: i32 = contents.trim().parse()?;   // Auto-converts ParseIntError
    Ok(num)
}

Display and Debug traits

use std::fmt;

struct Point {
    x: i32,
    y: i32,
}

// Debug trait - for developers
#[derive(Debug)]
struct DebugPoint {
    x: i32,
    y: i32,
}

// Display trait - for users
impl fmt::Display for Point {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        write!(f, "({}, {})", self.x, self.y)
    }
}

// Custom Debug implementation
impl fmt::Debug for Point {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        f.debug_struct("Point")
            .field("x", &self.x)
            .field("y", &self.y)
            .finish()
    }
}

// Usage
let p = Point { x: 10, y: 20 };
println!("{}", p);        // Display: (10, 20)
println!("{:?}", p);      // Debug: Point { x: 10, y: 20 }
println!("{:#?}", p);     // Pretty Debug (multiline)

let dp = DebugPoint { x: 5, y: 15 };
println!("{:?}", dp);     // Derived Debug

Generics

Generic functions

fn largest<T: PartialOrd>(list: &[T]) -> &T {
    let mut largest = &list[0];

    for item in list {
        if item > largest {
            largest = item;
        }
    }

    largest
}

// Multiple type parameters
fn mix<T, U>(x: T, y: U) -> (T, U) {
    (x, y)
}

Generic structs

struct Point<T> {
    x: T,
    y: T,
}

// Multiple types
struct Point<T, U> {
    x: T,
    y: U,
}

// Methods on generic types
impl<T> Point<T> {
    fn x(&self) -> &T {
        &self.x
    }
}

// Methods for specific types
impl Point<f32> {
    fn distance_from_origin(&self) -> f32 {
        (self.x.powi(2) + self.y.powi(2)).sqrt()
    }
}

Generic enums

enum Option<T> {
    Some(T),
    None,
}

enum Result<T, E> {
    Ok(T),
    Err(E),
}

Smart pointers

Box<T>

// Heap allocation
let b = Box::new(5);
println!("b = {}", b);

// Recursive types (unknown size at compile time)
enum List {
    Cons(i32, Box<List>),
    Nil,
}

use List::{Cons, Nil};

let list = Cons(1,
    Box::new(Cons(2,
        Box::new(Cons(3,
            Box::new(Nil))))));

// Large data on heap
struct BigStruct {
    data: [u8; 10000],
}

let big = Box::new(BigStruct { data: [0; 10000] });

Use when:

  • Type has unknown size at compile time
  • Large data to avoid stack overflow
  • Transfer ownership without copying

Rc<T>

use std::rc::Rc;

// Reference counting for shared ownership
let a = Rc::new(5);
let b = Rc::clone(&a);      // Increment count
let c = Rc::clone(&a);

println!("count = {}", Rc::strong_count(&a));  // 3

// Shared ownership of graph/tree
enum List {
    Cons(i32, Rc<List>),
    Nil,
}

use List::{Cons, Nil};

let a = Rc::new(Cons(5, Rc::new(Cons(10, Rc::new(Nil)))));
let b = Cons(3, Rc::clone(&a));
let c = Cons(4, Rc::clone(&a));

// Drop decrements count
drop(a);
println!("count after drop = {}", Rc::strong_count(&b));  // 2

Use when:

  • Multiple owners needed
  • Single-threaded only (use Arc for threads)
  • Immutable shared data

RefCell<T>

use std::cell::RefCell;

// Interior mutability - mutate through immutable reference
let data = RefCell::new(5);

*data.borrow_mut() += 1;    // Mutable borrow
println!("{}", data.borrow());  // Immutable borrow

// Borrow checking at runtime
let mut_borrow = data.borrow_mut();
// let another = data.borrow_mut();  // ⚠️ Panic at runtime!

// Multiple owners with mutable data
use std::rc::Rc;

let value = Rc::new(RefCell::new(5));
let a = Rc::clone(&value);
let b = Rc::clone(&value);

*a.borrow_mut() += 10;
*b.borrow_mut() += 20;
println!("{:?}", value);    // 35

Use when:

  • Need mutability through shared reference
  • Borrow rules checked at runtime
  • Single-threaded (use Mutex for threads)

Cow<T>

use std::borrow::Cow;

// Clone-on-write - avoid cloning until mutation
fn process(input: &str) -> Cow<str> {
    if input.contains("REPLACE") {
        Cow::Owned(input.replace("REPLACE", "FIXED"))
    } else {
        Cow::Borrowed(input)  // No clone!
    }
}

let s1 = "Hello world";
let result1 = process(s1);      // Borrowed

let s2 = "REPLACE this";
let result2 = process(s2);      // Owned

// Works with any cloneable + borrowable type
fn abs_all(input: &mut Cow<[i32]>) {
    for i in 0..input.len() {
        let v = input[i];
        if v < 0 {
            input.to_mut()[i] = -v;  // Clone on first mutation
        }
    }
}

Use when:

  • Might not need to modify data
  • Avoid unnecessary clones
  • Working with borrowed or owned data

Decision table

Pointer Ownership Mutability Thread-safe Use case
Box<T> Single Mutable No Heap allocation, recursion
Rc<T> Shared Immutable No Shared ownership (single-threaded)
Arc<T> Shared Immutable Yes Shared ownership (multi-threaded)
RefCell<T> Single Interior No Runtime borrow checking
Mutex<T> Shared Interior Yes Thread-safe interior mutability
Cow<T> Either Clone-on-write No Avoid unnecessary clones

Lifetimes

Lifetime annotations

// Generic lifetime 'a
fn longest<'a>(x: &'a str, y: &'a str) -> &'a str {
    if x.len() > y.len() {
        x
    } else {
        y
    }
}

// Usage
let string1 = String::from("long string");
let result;
{
    let string2 = String::from("xyz");
    result = longest(string1.as_str(), string2.as_str());
    println!("{}", result);
}
// ⚠️ result can't outlive string2

Lifetime in structs

struct ImportantExcerpt<'a> {
    part: &'a str,
}

impl<'a> ImportantExcerpt<'a> {
    fn level(&self) -> i32 {
        3
    }

    fn announce_and_return_part(&self, announcement: &str) -> &str {
        println!("Attention: {}", announcement);
        self.part
    }
}

Lifetime elision

// These are equivalent:
fn first_word(s: &str) -> &str {}
fn first_word<'a>(s: &'a str) -> &'a str {}

// Compiler infers lifetimes based on rules

Cargo

Project commands

cargo new myproject        # Create new project
cargo init                 # Initialize in current dir
cargo build                # Build debug
cargo build --release      # Build optimized
cargo run                  # Build and run
cargo check                # Check without building
cargo test                 # Run tests
cargo doc --open           # Generate and open docs

Dependencies

# Cargo.toml
[package]
name = "myproject"
version = "0.1.0"
edition = "2021"

[dependencies]
serde = "1.0"
serde_json = "1.0"
rand = "0.8.5"

[dev-dependencies]
criterion = "0.5"
cargo add serde            # Add dependency
cargo update               # Update dependencies
cargo clean                # Remove build artifacts

Workspaces

# Cargo.toml (workspace root)
[workspace]
members = [
    "crate1",
    "crate2",
]
cargo build --workspace    # Build all crates
cargo test --workspace     # Test all crates

Modules and crates

Module basics

// Define module
mod front_of_house {
    pub mod hosting {
        pub fn add_to_waitlist() {}
        fn seat_at_table() {}       // Private
    }

    mod serving {
        fn take_order() {}
        fn serve_order() {}
    }
}

// Use module
pub fn eat_at_restaurant() {
    // Absolute path
    crate::front_of_house::hosting::add_to_waitlist();

    // Relative path
    front_of_house::hosting::add_to_waitlist();
}

File structure

// src/lib.rs or src/main.rs
mod garden;                // Looks for src/garden.rs or src/garden/mod.rs

// src/garden.rs
pub mod vegetables;        // Looks for src/garden/vegetables.rs

// src/garden/vegetables.rs
#[derive(Debug)]
pub struct Asparagus {}
src/
├── lib.rs
├── garden.rs
└── garden/
    └── vegetables.rs

Old style (still works):

src/
├── lib.rs
├── garden/
│   ├── mod.rs
│   └── vegetables.rs

Visibility modifiers

mod outer {
    pub fn public_fn() {}
    fn private_fn() {}

    pub mod inner {
        pub fn inner_public() {}

        pub(crate) fn crate_visible() {}      // Visible in crate
        pub(super) fn parent_visible() {}     // Visible to parent
        pub(in crate::outer) fn outer_visible() {}  // Visible in outer
    }
}

// Usage
outer::public_fn();           // Ok
outer::inner::inner_public(); // Ok
outer::inner::crate_visible(); // Ok (same crate)
// outer::private_fn();       // Error

use and re-exporting

// Import
use std::collections::HashMap;
use std::io::{self, Write};       // Import module and trait
use std::fmt::Result;
use std::io::Result as IoResult;  // Rename

// Glob import (use sparingly)
use std::collections::*;

// Re-export with pub use
pub use crate::front_of_house::hosting;

// External crates
use rand::Rng;
use serde::{Serialize, Deserialize};

Idiomatic patterns:

// For functions: import module
use std::collections::HashMap;
let map = HashMap::new();

// For types/traits: import directly
use std::fmt::Display;
fn print(item: impl Display) {}

Testing

Unit tests

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn it_works() {
        assert_eq!(2 + 2, 4);
    }

    #[test]
    fn exploration() {
        assert!(true);
    }

    #[test]
    #[should_panic]
    fn panic_test() {
        panic!("This should panic");
    }

    #[test]
    fn result_test() -> Result<(), String> {
        if 2 + 2 == 4 {
            Ok(())
        } else {
            Err(String::from("two plus two != four"))
        }
    }
}

Assertions

assert!(condition);                // Panic if false
assert_eq!(left, right);           // Panic if not equal
assert_ne!(left, right);           // Panic if equal

// With custom message
assert!(condition, "Error: {}", msg);
assert_eq!(a, b, "Values differ: {} vs {}", a, b);

Running tests

cargo test                 # Run all tests
cargo test test_name       # Run specific test
cargo test -- --nocapture  # Show println! output
cargo test -- --ignored    # Run ignored tests

Concurrency

Threads

use std::thread;
use std::time::Duration;

// Spawn thread
let handle = thread::spawn(|| {
    for i in 1..10 {
        println!("spawned thread: {}", i);
        thread::sleep(Duration::from_millis(1));
    }
});

handle.join().unwrap();    // Wait for thread to finish

// Move closure
let v = vec![1, 2, 3];
let handle = thread::spawn(move || {
    println!("vector: {:?}", v);
});

Channels

use std::sync::mpsc;
use std::thread;

// Create channel
let (tx, rx) = mpsc::channel();

thread::spawn(move || {
    let val = String::from("hi");
    tx.send(val).unwrap();
});

let received = rx.recv().unwrap();
println!("Got: {}", received);

// Multiple producers
let (tx, rx) = mpsc::channel();
let tx1 = tx.clone();

thread::spawn(move || {
    tx.send(String::from("hi from tx")).unwrap();
});

thread::spawn(move || {
    tx1.send(String::from("hi from tx1")).unwrap();
});

Shared state

use std::sync::{Arc, Mutex};
use std::thread;

// Arc = Atomic Reference Counter
// Mutex = Mutual Exclusion
let counter = Arc::new(Mutex::new(0));
let mut handles = vec![];

for _ in 0..10 {
    let counter = Arc::clone(&counter);
    let handle = thread::spawn(move || {
        let mut num = counter.lock().unwrap();
        *num += 1;
    });
    handles.push(handle);
}

for handle in handles {
    handle.join().unwrap();
}

println!("Result: {}", *counter.lock().unwrap());

Async/Await

Async functions

// Async function returns a Future
async fn fetch_data() -> String {
    String::from("data")
}

// .await to execute Future
async fn process() {
    let data = fetch_data().await;
    println!("{}", data);
}

// Async block
let future = async {
    let result = fetch_data().await;
    result
};

Tokio setup

// Cargo.toml
// [dependencies]
// tokio = { version = "1", features = ["full"] }

// Main function with tokio runtime
#[tokio::main]
async fn main() {
    println!("Hello from async!");

    let result = fetch_data().await;
    println!("{}", result);
}

// Alternative: manual runtime
fn main() {
    let rt = tokio::runtime::Runtime::new().unwrap();
    rt.block_on(async {
        fetch_data().await;
    });
}

Spawning tasks

use tokio::task;

#[tokio::main]
async fn main() {
    // Spawn concurrent task
    let handle = task::spawn(async {
        // Runs on tokio thread pool
        expensive_computation().await
    });

    // Do other work
    do_something_else().await;

    // Wait for task
    let result = handle.await.unwrap();
    println!("Result: {}", result);
}

// Multiple tasks
async fn fetch_multiple() {
    let task1 = task::spawn(fetch_user(1));
    let task2 = task::spawn(fetch_user(2));

    let (user1, user2) = tokio::join!(task1, task2);
}

Async examples

use tokio::net::TcpListener;
use tokio::io::{AsyncReadExt, AsyncWriteExt};

// Async TCP server
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    let listener = TcpListener::bind("127.0.0.1:8080").await?;

    loop {
        let (mut socket, _) = listener.accept().await?;

        // Spawn task per connection
        tokio::spawn(async move {
            let mut buf = [0; 1024];

            loop {
                match socket.read(&mut buf).await {
                    Ok(0) => return,  // Connection closed
                    Ok(n) => {
                        if socket.write_all(&buf[0..n]).await.is_err() {
                            return;
                        }
                    }
                    Err(_) => return,
                }
            }
        });
    }
}

// Async HTTP request
use reqwest;

async fn fetch_url(url: &str) -> Result<String, reqwest::Error> {
    let body = reqwest::get(url)
        .await?
        .text()
        .await?;
    Ok(body)
}

// Timeout
use tokio::time::{timeout, Duration};

async fn with_timeout() {
    let result = timeout(
        Duration::from_secs(5),
        fetch_data()
    ).await;

    match result {
        Ok(data) => println!("Got: {:?}", data),
        Err(_) => println!("Timeout!"),
    }
}

Macros

Common macros

println!("Hello");         // Print with newline
print!("Hello");           // Print without newline
eprintln!("Error");        // Print to stderr
format!("x = {}", x);      // Format to String

vec![1, 2, 3];             // Create vector
panic!("Error!");          // Panic with message

assert!(condition);        // Assert condition
assert_eq!(a, b);          // Assert equality
debug_assert!(condition);  // Assert in debug only

Declarative macros

// Define macro
macro_rules! say_hello {
    () => {
        println!("Hello!");
    };
}

// With arguments
macro_rules! create_function {
    ($func_name:ident) => {
        fn $func_name() {
            println!("Function {:?} called", stringify!($func_name));
        }
    };
}

// Multiple patterns
macro_rules! my_vec {
    () => {
        Vec::new()
    };
    ($elem:expr; $n:expr) => {
        vec![$elem; $n]
    };
}

Derive macros

#[derive(Debug)]           // Auto-implement Debug
#[derive(Clone)]           // Auto-implement Clone
#[derive(PartialEq)]       // Auto-implement PartialEq
#[derive(Eq)]              // Auto-implement Eq

// Multiple derives
#[derive(Debug, Clone, PartialEq)]
struct Point {
    x: i32,
    y: i32,
}

Attributes

Common attributes

// Derive traits
#[derive(Debug, Clone, PartialEq)]
struct MyStruct {}

// Allow/warn/deny lints
#[allow(dead_code)]
#[warn(missing_docs)]
#[deny(unsafe_code)]

// Testing
#[test]
fn my_test() {}

#[cfg(test)]
mod tests {}

// Conditional compilation
#[cfg(target_os = "linux")]
fn linux_only() {}

#[cfg(not(target_os = "windows"))]
fn not_windows() {}

#[cfg(any(unix, target_os = "windows"))]
fn multi_platform() {}

#[cfg(all(unix, target_arch = "x86_64"))]
fn specific_platform() {}

Feature flags

// Cargo.toml
// [features]
// default = ["std"]
// std = []
// experimental = []

#[cfg(feature = "std")]
use std::collections::HashMap;

#[cfg(not(feature = "std"))]
use alloc::collections::BTreeMap;

#[cfg(feature = "experimental")]
pub mod experimental {
    pub fn new_feature() {}
}

Documentation attributes

/// Document the following item
///
/// # Examples
///
/// ```
/// let x = example();
/// ```
pub fn example() {}

//! Document the enclosing item (module/crate)

#[doc = "Alternative doc syntax"]
pub fn other() {}

// Doc tests
/// ```
/// assert_eq!(add(2, 2), 4);
/// ```
pub fn add(a: i32, b: i32) -> i32 {
    a + b
}

// Hide from docs
#[doc(hidden)]
pub fn internal_api() {}

Other attributes

// Inline suggestions
#[inline]
fn small_function() {}

#[inline(always)]
fn always_inline() {}

// Must use return value
#[must_use]
fn important() -> i32 { 42 }

// Deprecated
#[deprecated]
fn old_api() {}

#[deprecated(since = "1.5.0", note = "Use new_api instead")]
fn old_api_detailed() {}

// Non-exhaustive (for library evolution)
#[non_exhaustive]
pub enum ApiError {
    NotFound,
    Timeout,
}

#[non_exhaustive]
pub struct Config {
    pub timeout: u64,
}

Common patterns

Iterator patterns

let v = vec![1, 2, 3, 4, 5];

// Map
let v2: Vec<_> = v.iter().map(|x| x + 1).collect();

// Filter
let v3: Vec<_> = v.iter().filter(|x| *x % 2 == 0).collect();

// Fold (reduce)
let sum: i32 = v.iter().fold(0, |acc, x| acc + x);

// Chain operations
let result: Vec<_> = v.iter()
    .filter(|x| *x % 2 == 0)
    .map(|x| x * 2)
    .collect();

// Find
let first_even = v.iter().find(|x| *x % 2 == 0);

// Any/All
let has_even = v.iter().any(|x| x % 2 == 0);
let all_positive = v.iter().all(|x| *x > 0);

Closure patterns

// Closure types
let add_one = |x| x + 1;               // Type inferred
let add_one = |x: i32| -> i32 { x + 1 };

// Capture environment
let x = 4;
let equal_to_x = |z| z == x;           // Captures x

// Move ownership
let x = vec![1, 2, 3];
let equal_to_x = move |z| z == x;      // Takes ownership

// Function parameters
fn apply<F>(f: F, x: i32) -> i32
where
    F: Fn(i32) -> i32,
{
    f(x)
}

Builder pattern

struct Circle {
    x: f64,
    y: f64,
    radius: f64,
}

struct CircleBuilder {
    x: f64,
    y: f64,
    radius: f64,
}

impl CircleBuilder {
    fn new() -> CircleBuilder {
        CircleBuilder {
            x: 0.0,
            y: 0.0,
            radius: 1.0,
        }
    }

    fn x(&mut self, x: f64) -> &mut Self {
        self.x = x;
        self
    }

    fn y(&mut self, y: f64) -> &mut Self {
        self.y = y;
        self
    }

    fn radius(&mut self, radius: f64) -> &mut Self {
        self.radius = radius;
        self
    }

    fn build(&self) -> Circle {
        Circle {
            x: self.x,
            y: self.y,
            radius: self.radius,
        }
    }
}

// Usage
let c = CircleBuilder::new()
    .x(10.0)
    .y(20.0)
    .radius(5.0)
    .build();

File I/O

Basic file operations

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

// Read entire file to string
let contents = fs::read_to_string("file.txt")?;

// Read entire file to bytes
let bytes = fs::read("file.bin")?;

// Write string to file (overwrites)
fs::write("output.txt", "Hello, world!")?;

// Write bytes to file
fs::write("output.bin", &[0, 1, 2, 3])?;

// Check if file exists
if fs::metadata("file.txt").is_ok() {
    println!("File exists");
}

// Create directory
fs::create_dir("my_dir")?;
fs::create_dir_all("path/to/nested/dir")?;

// Remove file
fs::remove_file("file.txt")?;

File handle

use std::fs::File;
use std::io::{Read, Write};

// Open for reading
let mut file = File::open("input.txt")?;
let mut contents = String::new();
file.read_to_string(&mut contents)?;

// Create or truncate
let mut file = File::create("output.txt")?;
file.write_all(b"Hello, world!")?;

// Append
use std::fs::OpenOptions;

let mut file = OpenOptions::new()
    .append(true)
    .open("log.txt")?;
file.write_all(b"New log entry\n")?;

// Read and write
let mut file = OpenOptions::new()
    .read(true)
    .write(true)
    .create(true)
    .open("data.txt")?;

Buffered I/O

use std::fs::File;
use std::io::{BufReader, BufRead, BufWriter, Write};

// Buffered reading (efficient for large files)
let file = File::open("large.txt")?;
let reader = BufReader::new(file);

// Read line by line
for line in reader.lines() {
    let line = line?;
    println!("{}", line);
}

// Buffered writing
let file = File::create("output.txt")?;
let mut writer = BufWriter::new(file);

writer.write_all(b"Line 1\n")?;
writer.write_all(b"Line 2\n")?;
writer.flush()?;  // Ensure data is written

// Read specific lines
let file = File::open("file.txt")?;
let reader = BufReader::new(file);
let lines: Vec<_> = reader
    .lines()
    .take(5)           // First 5 lines
    .collect();

Path operations

use std::path::Path;
use std::fs;

let path = Path::new("dir/file.txt");

// Path info
println!("Exists: {}", path.exists());
println!("Is file: {}", path.is_file());
println!("Is dir: {}", path.is_dir());
println!("File name: {:?}", path.file_name());
println!("Extension: {:?}", path.extension());
println!("Parent: {:?}", path.parent());

// Build paths
use std::path::PathBuf;

let mut path = PathBuf::from("/tmp");
path.push("dir");
path.push("file.txt");
// Result: /tmp/dir/file.txt

// Canonicalize (resolve symlinks, relative paths)
let canonical = fs::canonicalize("./file.txt")?;

Serde

JSON serialization

// Cargo.toml
// [dependencies]
// serde = { version = "1.0", features = ["derive"] }
// serde_json = "1.0"

use serde::{Serialize, Deserialize};

#[derive(Serialize, Deserialize, Debug)]
struct Person {
    name: String,
    age: u32,
    email: String,
}

// Serialize to JSON
let person = Person {
    name: "Alice".to_string(),
    age: 30,
    email: "alice@example.com".to_string(),
};

let json = serde_json::to_string(&person)?;
println!("{}", json);
// {"name":"Alice","age":30,"email":"alice@example.com"}

// Pretty print
let json = serde_json::to_string_pretty(&person)?;

// Deserialize from JSON
let json_str = r#"{"name":"Bob","age":25,"email":"bob@example.com"}"#;
let person: Person = serde_json::from_str(json_str)?;
println!("{:?}", person);

// From file
let file = File::open("data.json")?;
let person: Person = serde_json::from_reader(file)?;

Field attributes

use serde::{Serialize, Deserialize};

#[derive(Serialize, Deserialize)]
struct User {
    // Rename field in JSON
    #[serde(rename = "userName")]
    username: String,

    // Skip field
    #[serde(skip)]
    password_hash: String,

    // Skip when serializing
    #[serde(skip_serializing)]
    internal_id: u64,

    // Skip when deserializing
    #[serde(skip_deserializing)]
    computed: String,

    // Default value if missing
    #[serde(default)]
    active: bool,

    // Custom default function
    #[serde(default = "default_role")]
    role: String,

    // Flatten nested struct
    #[serde(flatten)]
    metadata: Metadata,
}

fn default_role() -> String {
    "user".to_string()
}

#[derive(Serialize, Deserialize)]
struct Metadata {
    created_at: String,
    updated_at: String,
}

// Option fields
#[derive(Serialize, Deserialize)]
struct OptionalFields {
    // Skip if None
    #[serde(skip_serializing_if = "Option::is_none")]
    middle_name: Option<String>,
}

Container attributes

// Rename all fields
#[derive(Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
struct Product {
    product_name: String,      // -> "productName"
    unit_price: f64,           // -> "unitPrice"
}

// Tag for enums
#[derive(Serialize, Deserialize)]
#[serde(tag = "type")]
enum Message {
    Text { content: String },
    Image { url: String },
}
// {"type":"Text","content":"hello"}

// Untagged enum (by structure)
#[derive(Serialize, Deserialize)]
#[serde(untagged)]
enum Value {
    Number(i32),
    Text(String),
}

// Deny unknown fields
#[derive(Deserialize)]
#[serde(deny_unknown_fields)]
struct Strict {
    field1: String,
}

FFI (Foreign Function Interface)

Calling C from Rust

use libc::size_t;

#[link(name = "snappy")]
unsafe extern "C" {
    fn snappy_max_compressed_length(source_length: size_t) -> size_t;
}

fn main() {
    let x = unsafe { snappy_max_compressed_length(100) };
    println!("max compressed length: {}", x);
}

Add to Cargo.toml: libc = "0.2"

Exporting Rust functions

#[no_mangle]
pub extern "C" fn hello_from_rust() {
    println!("Hello from Rust!");
}

// With safety wrapper
#[no_mangle]
pub unsafe extern "C" fn process_data(ptr: *const u8, len: usize) -> i32 {
    if ptr.is_null() {
        return -1;
    }
    let slice = std::slice::from_raw_parts(ptr, len);
    // Process data
    slice.len() as i32
}

Bindgen basics

// build.rs
fn main() {
    let bindings = bindgen::Builder::default()
        .header("wrapper.h")
        .generate()
        .expect("Unable to generate bindings");

    let out_path = std::path::PathBuf::from(std::env::var("OUT_DIR").unwrap());
    bindings
        .write_to_file(out_path.join("bindings.rs"))
        .expect("Couldn't write bindings");
}

// In lib.rs
include!(concat!(env!("OUT_DIR"), "/bindings.rs"));

Add to Cargo.toml: bindgen = "0.69" (build-dependencies)

C types mapping

C Type Rust Type
int c_int
unsigned int c_uint
char c_char
void* *mut c_void
const char* *const c_char
size_t usize
use std::ffi::{c_int, c_char, c_void, CStr, CString};

// String conversion
let c_str = CString::new("hello").unwrap();
let ptr: *const c_char = c_str.as_ptr();

// From C string
unsafe {
    let s = CStr::from_ptr(ptr).to_str().unwrap();
}

Unsafe Rust

Unsafe superpowers

unsafe {
    // 1. Dereference raw pointers
    let x = 5;
    let r = &raw const x;
    let val = *r;

    // 2. Call unsafe functions
    dangerous();

    // 3. Access/modify mutable statics
    COUNTER += 1;

    // 4. Implement unsafe traits
    // (requires unsafe impl)

    // 5. Access union fields
    // my_union.field
}

unsafe fn dangerous() {}

static mut COUNTER: u32 = 0;

Raw pointers

let mut num = 5;
let r1 = &raw const num;  // *const i32
let r2 = &raw mut num;    // *mut i32

unsafe {
    println!("r1: {}", *r1);
    *r2 = 10;
}

// From references
let r: *const i32 = &num;
let r: *mut i32 = &mut num;

// Null pointer
let null: *const i32 = std::ptr::null();
let null_mut: *mut i32 = std::ptr::null_mut();

// Check null
if !null.is_null() {
    unsafe { println!("{}", *null); }
}

Safety invariants

  • No data races
  • No dereferencing null/dangling pointers
  • References always point to valid data
  • No uninitialized memory reads
  • Uphold lifetime requirements

Always wrap unsafe code in safe abstractions.

Unsafe traits

// Marker traits for thread safety
unsafe trait MyUnsafeTrait {
    fn do_something(&self);
}

unsafe impl MyUnsafeTrait for i32 {
    fn do_something(&self) {
        println!("{}", self);
    }
}

// Example: Send and Sync
// - Send: safe to transfer between threads
// - Sync: safe to share references between threads
// Most types implement these automatically

Unions

#[repr(C)]
union MyUnion {
    i: i32,
    f: f32,
}

fn main() {
    let u = MyUnion { i: 42 };

    // Accessing union fields is unsafe
    unsafe {
        println!("As int: {}", u.i);
        println!("As float: {}", u.f);  // Reinterprets bits
    }
}

Common Crates

Rayon (parallelism)

use rayon::prelude::*;

// Parallel iterator
let sum: i32 = vec![1, 2, 3, 4, 5]
    .par_iter()
    .map(|&x| x * 2)
    .sum();

// Parallel sorting
let mut v = vec![5, 2, 1, 4, 3];
v.par_sort_unstable();

// Custom parallel tasks
rayon::join(
    || heavy_task_1(),
    || heavy_task_2()
);

// Parallel chunks
data.par_chunks(100)
    .for_each(|chunk| process(chunk));

Add: rayon = "1.10"

Clap (CLI parsing)

use clap::Parser;

#[derive(Parser)]
#[command(version, about)]
struct Args {
    /// Name of the person
    #[arg(short, long)]
    name: String,

    /// Number of times to greet
    #[arg(short, long, default_value_t = 1)]
    count: u8,

    /// Enable verbose output
    #[arg(long)]
    verbose: bool,
}

fn main() {
    let args = Args::parse();
    for _ in 0..args.count {
        println!("Hello {}!", args.name);
    }
}

Add: clap = { version = "4", features = ["derive"] }

thiserror (library errors)

use thiserror::Error;

#[derive(Error, Debug)]
pub enum MyError {
    #[error("IO error: {0}")]
    Io(#[from] std::io::Error),

    #[error("Parse error at line {line}: {msg}")]
    Parse { line: usize, msg: String },

    #[error("Invalid value: {0}")]
    Invalid(String),
}

fn read_config() -> Result<Config, MyError> {
    let content = std::fs::read_to_string("config.txt")?;
    // ...
}

Add: thiserror = "1.0"

anyhow (application errors)

use anyhow::{Context, Result, bail, anyhow};

fn read_config(path: &str) -> Result<Config> {
    let content = std::fs::read_to_string(path)
        .context("Failed to read config file")?;

    let config: Config = serde_json::from_str(&content)
        .context("Failed to parse config")?;

    if config.name.is_empty() {
        bail!("Config name cannot be empty");
    }

    Ok(config)
}

fn main() -> Result<()> {
    let config = read_config("config.json")?;
    Ok(())
}

Add: anyhow = "1.0"

Reqwest (HTTP client)

use reqwest;
use std::collections::HashMap;

// Async GET
let resp = reqwest::get("https://api.example.com/data")
    .await?
    .json::<HashMap<String, String>>()
    .await?;

// Async POST with JSON
let client = reqwest::Client::new();
let resp = client
    .post("https://api.example.com/users")
    .json(&user)
    .header("Authorization", "Bearer token")
    .send()
    .await?;

// Blocking (sync) client
let body = reqwest::blocking::get("https://example.com")?
    .text()?;

Add: reqwest = { version = "0.12", features = ["json"] }

Tracing (structured logging)

use tracing::{info, warn, error, debug, instrument, span, Level};

#[instrument]
fn process_request(id: u64) {
    info!(id, "Processing request");

    if id == 0 {
        warn!("Invalid ID");
        return;
    }

    debug!(result = 42, "Computation complete");
}

// Setup subscriber
use tracing_subscriber;

fn main() {
    tracing_subscriber::fmt::init();

    let span = span!(Level::INFO, "my_span");
    let _guard = span.enter();

    process_request(1);
}

Add: tracing = "0.1", tracing-subscriber = "0.3"

Memory Layout

repr attributes

// C-compatible layout
#[repr(C)]
struct CStruct {
    a: u8,   // offset 0
    b: u32,  // offset 4 (padded)
    c: u16,  // offset 8
}            // size: 12 bytes

// Transparent wrapper (same ABI as inner type)
#[repr(transparent)]
struct Wrapper(u32);

// Remove padding (use carefully!)
#[repr(packed)]
struct Packed {
    a: u8,
    b: u32,  // No alignment padding
}

// Can't take references to packed fields!
let p = Packed { a: 1, b: 2 };
let val = p.b;  // OK: copy the value
// let r = &p.b; // ERROR: unaligned reference

// Specific alignment
#[repr(align(16))]
struct Aligned {
    data: [u8; 4],
}

Size and alignment

use std::mem::{size_of, align_of, size_of_val};

// Primitives
assert_eq!(size_of::<u8>(), 1);
assert_eq!(size_of::<u64>(), 8);
assert_eq!(align_of::<u32>(), 4);

// Zero-sized types (ZST)
struct Empty;
assert_eq!(size_of::<Empty>(), 0);

struct Unit;
assert_eq!(size_of::<Unit>(), 0);

// PhantomData
use std::marker::PhantomData;
struct Marker<T>(PhantomData<T>);
assert_eq!(size_of::<Marker<String>>(), 0);

// Runtime size
let v = vec![1, 2, 3];
println!("Vec header: {}", size_of_val(&v));  // 24
println!("Vec data: {}", v.len() * size_of::<i32>());

Enum layout

use std::mem::size_of;

// Simple enum
enum Simple { A, B, C }
assert_eq!(size_of::<Simple>(), 1);  // Just discriminant

// Enum with data
enum Message {
    Quit,
    Move { x: i32, y: i32 },
    Text(String),
}
// Size = discriminant + largest variant + padding

// Niche optimization
assert_eq!(size_of::<Option<&i32>>(), 8);  // Same as &i32!
// None uses null pointer niche

// NonZero for niche
use std::num::NonZeroU32;
assert_eq!(size_of::<Option<NonZeroU32>>(), 4);

DST and fat pointers

use std::mem::size_of;

// Thin pointers (sized types)
assert_eq!(size_of::<&i32>(), 8);        // Just address
assert_eq!(size_of::<Box<i32>>(), 8);

// Fat pointers (unsized types)
assert_eq!(size_of::<&str>(), 16);       // ptr + len
assert_eq!(size_of::<&[i32]>(), 16);     // ptr + len
assert_eq!(size_of::<&dyn Clone>(), 16); // ptr + vtable

// Box to unsized
let boxed: Box<dyn std::fmt::Debug> = Box::new(42);
assert_eq!(size_of_val(&boxed), 16);  // Fat pointer

Performance Patterns

Zero-cost abstractions

// Iterator chains compile to same code as manual loops
let sum: i32 = (1..100)
    .filter(|x| x % 2 == 0)
    .map(|x| x * x)
    .sum();

// Equivalent to optimized:
// let mut sum = 0;
// for x in 1..100 {
//     if x % 2 == 0 {
//         sum += x * x;
//     }
// }

// Generics are monomorphized
fn process<T: Clone>(item: T) -> T {
    item.clone()
}
// Generates specialized code for each T

Optimization attributes

#[inline]
fn small_func() { }

#[inline(always)]
fn must_inline() { }

#[inline(never)]
fn no_inline() { }

#[cold]
fn error_path() {
    // Hint: rarely called
}

#[cfg(not(debug_assertions))]
fn release_only() { }

// Target-specific optimization
#[cfg(target_arch = "x86_64")]
fn use_simd() { }

Common pitfalls

// BAD: Unnecessary clones
for item in collection.clone() { }

// GOOD: Iterate by reference
for item in &collection { }

// BAD: Allocations in hot loop
for _ in 0..1000 {
    let v = vec![1, 2, 3];
}

// GOOD: Reuse allocation
let mut v = Vec::with_capacity(3);
for _ in 0..1000 {
    v.clear();
    v.extend_from_slice(&[1, 2, 3]);
}

// BAD: String concatenation in loop
let mut s = String::new();
for item in items {
    s = s + &item.to_string();  // Reallocates each time
}

// GOOD: Use push_str or format!
let mut s = String::with_capacity(100);
for item in items {
    s.push_str(&item.to_string());
}

Profiling tools

# Flamegraph
cargo install flamegraph
cargo flamegraph --bin myapp

# Linux perf
perf record -g ./target/release/myapp
perf report

# macOS Instruments
cargo install cargo-instruments
cargo instruments -t time

# Memory profiling
cargo install cargo-bloat
cargo bloat --release --crates

# Benchmarking
cargo bench  # Requires criterion or test harness

Benchmarking with Criterion

// benches/my_bench.rs
use criterion::{criterion_group, criterion_main, Criterion};

fn fibonacci(n: u64) -> u64 {
    match n {
        0 | 1 => n,
        _ => fibonacci(n - 1) + fibonacci(n - 2),
    }
}

fn criterion_benchmark(c: &mut Criterion) {
    c.bench_function("fib 20", |b| b.iter(|| fibonacci(20)));
}

criterion_group!(benches, criterion_benchmark);
criterion_main!(benches);

Add: criterion = { version = "0.5", features = ["html_reports"] }

Procedural Macros

Proc macro setup

# Cargo.toml for proc-macro crate
[lib]
proc-macro = true

[dependencies]
syn = { version = "2", features = ["full"] }
quote = "1"
proc-macro2 = "1"

Proc macros must be in a separate crate.

Derive macro

// my_macro/src/lib.rs
use proc_macro::TokenStream;
use quote::quote;
use syn::{parse_macro_input, DeriveInput};

#[proc_macro_derive(Builder)]
pub fn derive_builder(input: TokenStream) -> TokenStream {
    let input = parse_macro_input!(input as DeriveInput);
    let name = &input.ident;
    let builder_name = quote::format_ident!("{}Builder", name);

    let expanded = quote! {
        impl #name {
            pub fn builder() -> #builder_name {
                #builder_name::default()
            }
        }

        #[derive(Default)]
        pub struct #builder_name {
            // Builder fields...
        }
    };

    TokenStream::from(expanded)
}

Attribute macro

use proc_macro::TokenStream;
use quote::quote;
use syn::{parse_macro_input, ItemFn};

#[proc_macro_attribute]
pub fn log_calls(
    _attr: TokenStream,
    item: TokenStream
) -> TokenStream {
    let input = parse_macro_input!(item as ItemFn);
    let name = &input.sig.ident;
    let block = &input.block;
    let sig = &input.sig;

    let expanded = quote! {
        #sig {
            println!("Entering: {}", stringify!(#name));
            let result = { #block };
            println!("Exiting: {}", stringify!(#name));
            result
        }
    };

    TokenStream::from(expanded)
}

// Usage:
// #[log_calls]
// fn my_function() { }

Function-like macro

use proc_macro::TokenStream;
use quote::quote;
use syn::{parse_macro_input, LitStr};

#[proc_macro]
pub fn make_answer(input: TokenStream) -> TokenStream {
    let input = parse_macro_input!(input as LitStr);
    let value = input.value();

    let expanded = quote! {
        fn answer() -> &'static str {
            #value
        }
    };

    TokenStream::from(expanded)
}

// Usage: make_answer!("42");
// Generates: fn answer() -> &'static str { "42" }

Helper attributes

#[proc_macro_derive(MyTrait, attributes(my_attr))]
pub fn derive_my_trait(input: TokenStream) -> TokenStream {
    let input = parse_macro_input!(input as DeriveInput);

    // Access helper attributes on fields
    // #[my_attr(skip)]
    // field: Type,

    // Parse attributes from input.attrs
    // and field.attrs

    TokenStream::new()
}

Embedded Rust

no_std basics

#![no_std]
#![no_main]

use cortex_m_rt::entry;
use panic_halt as _;

#[entry]
fn main() -> ! {
    // Your embedded code here
    loop {
        // Main loop
    }
}

#![no_std] disables the standard library, #![no_main] disables the standard entry point.

Panic handler

#![no_std]

use core::panic::PanicInfo;

#[panic_handler]
fn panic(_info: &PanicInfo) -> ! {
    // Options:
    // - Loop forever
    // - Reset device
    // - Log error and halt

    loop {}
}

// Or use a crate
// use panic_halt as _;     // Halt
// use panic_reset as _;    // Reset
// use panic_semihosting;   // Debug output

Using alloc

#![no_std]
extern crate alloc;

use alloc::vec::Vec;
use alloc::string::String;
use alloc::boxed::Box;

// Must provide allocator
use embedded_alloc::LlffHeap as Heap;

#[global_allocator]
static HEAP: Heap = Heap::empty();

fn init_heap() {
    use core::mem::MaybeUninit;
    const HEAP_SIZE: usize = 1024;
    static mut HEAP_MEM: [MaybeUninit<u8>; HEAP_SIZE] =
        [MaybeUninit::uninit(); HEAP_SIZE];
    unsafe { HEAP.init(HEAP_MEM.as_ptr() as usize, HEAP_SIZE) }
}

Hardware abstraction

#![no_std]
#![no_main]

use cortex_m_rt::entry;
use stm32f4xx_hal::{pac, prelude::*};

#[entry]
fn main() -> ! {
    let dp = pac::Peripherals::take().unwrap();

    // Configure clocks
    let rcc = dp.RCC.constrain();
    let clocks = rcc.cfgr.sysclk(48.MHz()).freeze();

    // Configure GPIO
    let gpioa = dp.GPIOA.split();
    let mut led = gpioa.pa5.into_push_pull_output();

    loop {
        led.toggle();
        cortex_m::asm::delay(8_000_000);
    }
}

Embedded targets

# Add target
rustup target add thumbv7em-none-eabihf  # Cortex-M4F/M7F
rustup target add thumbv7m-none-eabi     # Cortex-M3
rustup target add thumbv6m-none-eabi     # Cortex-M0/M0+
rustup target add riscv32imac-unknown-none-elf  # RISC-V

# Build
cargo build --target thumbv7em-none-eabihf

# .cargo/config.toml
[build]
target = "thumbv7em-none-eabihf"

[target.thumbv7em-none-eabihf]
runner = "probe-run --chip STM32F411CEUx"

WebAssembly

wasm-bindgen basics

use wasm_bindgen::prelude::*;

// Export function to JavaScript
#[wasm_bindgen]
pub fn greet(name: &str) -> String {
    format!("Hello, {}!", name)
}

// Import from JavaScript
#[wasm_bindgen]
extern "C" {
    fn alert(s: &str);

    #[wasm_bindgen(js_namespace = console)]
    fn log(s: &str);
}

#[wasm_bindgen]
pub fn show_alert() {
    alert("Hello from Rust!");
}

#[wasm_bindgen(start)]
pub fn main() {
    log("WASM module initialized");
}

Add: wasm-bindgen = "0.2"

wasm-pack workflow

# Install
cargo install wasm-pack

# Build for bundlers (webpack, etc.)
wasm-pack build --target bundler

# Build for web (ES modules)
wasm-pack build --target web

# Build for Node.js
wasm-pack build --target nodejs

# Test in browser
wasm-pack test --headless --firefox

# Publish to npm
wasm-pack publish

web-sys DOM access

use wasm_bindgen::prelude::*;
use web_sys::{Document, Element, Window};

#[wasm_bindgen]
pub fn manipulate_dom() -> Result<(), JsValue> {
    let window: Window = web_sys::window()
        .ok_or("no window")?;
    let document: Document = window.document()
        .ok_or("no document")?;

    let element: Element = document
        .create_element("p")?;
    element.set_text_content(Some("Hello from Rust!"));

    document.body()
        .ok_or("no body")?
        .append_child(&element)?;

    Ok(())
}

Add: web-sys = { version = "0.3", features = ["Window", "Document", "Element"] }

JS interop

use wasm_bindgen::prelude::*;
use js_sys::{Array, Object, Reflect};

// Accept JS values
#[wasm_bindgen]
pub fn process_array(arr: &Array) -> u32 {
    arr.length()
}

// Return JS objects
#[wasm_bindgen]
pub fn create_object() -> Object {
    let obj = Object::new();
    Reflect::set(&obj, &"name".into(), &"Rust".into()).unwrap();
    obj
}

// Async JavaScript
#[wasm_bindgen]
pub async fn fetch_data(url: &str) -> Result<JsValue, JsValue> {
    let window = web_sys::window().unwrap();
    let resp = wasm_bindgen_futures::JsFuture::from(
        window.fetch_with_str(url)
    ).await?;
    Ok(resp)
}

Add: js-sys = "0.3", wasm-bindgen-futures = "0.4"

Cargo Advanced

Workspaces

# Root Cargo.toml
[workspace]
members = ["crate1", "crate2", "crates/*"]
resolver = "2"

[workspace.package]
version = "1.0.0"
edition = "2021"
authors = ["Your Name"]
license = "MIT"

[workspace.dependencies]
serde = { version = "1.0", features = ["derive"] }
tokio = { version = "1", features = ["full"] }

# In member Cargo.toml
[package]
name = "crate1"
version.workspace = true
edition.workspace = true

[dependencies]
serde.workspace = true
cargo build --workspace
cargo test --workspace
cargo test -p crate1       # Single package

Build scripts

// build.rs
fn main() {
    // Rerun conditions
    println!("cargo::rerun-if-changed=build.rs");
    println!("cargo::rerun-if-changed=src/data.json");
    println!("cargo::rerun-if-env-changed=MY_VAR");

    // Link native library
    println!("cargo::rustc-link-lib=mylib");
    println!("cargo::rustc-link-search=/usr/local/lib");

    // Set cfg flags
    println!("cargo::rustc-cfg=feature=\"custom\"");

    // Set environment for code
    println!("cargo::rustc-env=GIT_HASH={}", get_git_hash());

    // Warning message
    println!("cargo::warning=Custom build warning");
}

fn get_git_hash() -> String {
    std::process::Command::new("git")
        .args(["rev-parse", "--short", "HEAD"])
        .output()
        .map(|o| String::from_utf8_lossy(&o.stdout).trim().to_string())
        .unwrap_or_else(|_| "unknown".to_string())
}

Target-specific dependencies

[target.'cfg(windows)'.dependencies]
winapi = { version = "0.3", features = ["winuser"] }

[target.'cfg(unix)'.dependencies]
libc = "0.2"
nix = "0.27"

[target.'cfg(target_os = "macos")'.dependencies]
core-foundation = "0.9"

[target.'cfg(target_arch = "wasm32")'.dependencies]
wasm-bindgen = "0.2"

# Cross-compilation
[target.x86_64-unknown-linux-gnu]
linker = "x86_64-linux-gnu-gcc"

[target.aarch64-apple-darwin]
rustflags = ["-C", "target-cpu=apple-m1"]

Cargo features

[features]
default = ["std"]
std = []
alloc = []
serde = ["dep:serde"]
full = ["std", "serde", "async"]
async = ["dep:tokio"]

[dependencies]
serde = { version = "1.0", optional = true }
tokio = { version = "1", optional = true }
#[cfg(feature = "std")]
use std::collections::HashMap;

#[cfg(all(feature = "alloc", not(feature = "std")))]
extern crate alloc;

#[cfg(feature = "serde")]
use serde::{Serialize, Deserialize};

Release profiles

[profile.dev]
opt-level = 0
debug = true
overflow-checks = true

[profile.release]
opt-level = 3
lto = true              # Link-time optimization
codegen-units = 1       # Better optimization
panic = "abort"         # Smaller binary
strip = true            # Remove symbols

[profile.bench]
inherits = "release"
debug = true            # For profiling

# Custom profile
[profile.release-debug]
inherits = "release"
debug = true

Common Pitfalls

Integer overflow

// In release mode, overflow wraps silently
let x: u8 = 255;
let y = x + 1;  // y = 0 in release, panics in debug

// Explicit handling
let y = x.checked_add(1);    // Returns Option
let y = x.saturating_add(1); // Clamps to max (255)
let y = x.wrapping_add(1);   // Wraps to 0
let (y, overflow) = x.overflowing_add(1); // Returns both

// Use larger types for intermediate calculations
let result: u64 = (a as u64) * (b as u64);

Floating point equality

// BAD: Direct comparison
let x = 0.1 + 0.2;
if x == 0.3 { }  // Often false!

// GOOD: Epsilon comparison
const EPSILON: f64 = 1e-10;
if (x - 0.3).abs() < EPSILON { }

// For relative comparison
fn approx_eq(a: f64, b: f64, epsilon: f64) -> bool {
    (a - b).abs() <= epsilon * a.abs().max(b.abs())
}

// Consider using fixed-point or decimal crates
// for financial calculations

Mutex poisoning

use std::sync::Mutex;

let mutex = Mutex::new(0);
let result = mutex.lock();

match result {
    Ok(guard) => {
        // Use guard
    },
    Err(poisoned) => {
        // Another thread panicked while holding lock
        // Can still recover the data
        let guard = poisoned.into_inner();
        // But consider if data is valid
    }
}

// Alternative: always recover
let guard = mutex.lock().unwrap_or_else(|e| e.into_inner());

Arc vs Rc confusion

use std::rc::Rc;
use std::sync::Arc;

// Rc: Single-threaded only
let rc = Rc::new(5);
// thread::spawn(move || println!("{}", rc)); // ERROR!

// Arc: Thread-safe
let arc = Arc::new(5);
let arc_clone = Arc::clone(&arc);
std::thread::spawn(move || {
    println!("{}", arc_clone);  // OK
});

// Common pattern: Arc<Mutex<T>>
use std::sync::Mutex;
let shared = Arc::new(Mutex::new(Vec::new()));

Deadlocks

use std::sync::{Mutex, MutexGuard};

// BAD: Lock ordering deadlock
fn deadlock_risk() {
    let a = Mutex::new(1);
    let b = Mutex::new(2);

    // Thread 1: locks a, then b
    // Thread 2: locks b, then a
    // -> Deadlock!
}

// GOOD: Consistent lock ordering
fn safe() {
    let a = Mutex::new(1);
    let b = Mutex::new(2);

    // Always lock in same order (a before b)
    let guard_a = a.lock().unwrap();
    let guard_b = b.lock().unwrap();
}

// GOOD: Lock, copy, release
fn also_safe() {
    let a = Mutex::new(1);
    let val = *a.lock().unwrap();  // Lock released immediately
}

String vs str confusion

// &str: Borrowed string slice (view into data)
// String: Owned, growable string

fn takes_str(s: &str) { }
fn takes_string(s: String) { }

let owned = String::from("hello");
let borrowed: &str = "hello";

takes_str(&owned);      // &String coerces to &str
takes_str(borrowed);    // Already &str

takes_string(owned);           // Moves ownership
takes_string(borrowed.to_string()); // Creates new String

// Common pattern: Accept &str for flexibility
fn flexible(s: impl AsRef<str>) {
    let s: &str = s.as_ref();
}

Gotchas

Common mistakes

// ⚠️ Can't borrow as mutable while immutable borrow exists
let mut s = String::from("hello");
let r1 = &s;
let r2 = &mut s;          // Error!

// ✅ Fix: End immutable borrows first
let mut s = String::from("hello");
let r1 = &s;
println!("{}", r1);       // r1 last use
let r2 = &mut s;          // Ok: r1 no longer used

// ⚠️ Dangling reference
fn dangle() -> &String {  // Error: missing lifetime
    let s = String::from("hello");
    &s                    // s dropped here
}

// ✅ Fix: Return owned value
fn no_dangle() -> String {
    String::from("hello")
}

// ⚠️ Moving out of borrowed content
let v = vec![1, 2, 3];
let first = &v[0];
v.push(4);                // Error: v is borrowed

// ✅ Fix: Use indexing after modification
let mut v = vec![1, 2, 3];
v.push(4);
let first = &v[0];

Performance tips

// Use &str instead of String when possible
fn process(s: &str) {}    // Accepts both &str and &String

// Avoid unnecessary clones
let s1 = String::from("hello");
let s2 = s1.clone();      // Expensive
let s3 = &s1;             // Cheap

// Use iterators instead of for loops
// Slower
let mut sum = 0;
for i in 0..n {
    sum += i;
}

// Faster (zero-cost abstraction)
let sum: i32 = (0..n).sum();

// Preallocate capacity
let mut v = Vec::with_capacity(1000);  // Avoid reallocations

Type annotations

// Sometimes needed when compiler can't infer
let v: Vec<i32> = Vec::new();
let v = Vec::<i32>::new();

// Turbofish syntax
let numbers = "1,2,3";
let nums: Vec<i32> = numbers
    .split(',')
    .map(|s| s.parse().unwrap())      // Error: type unclear
    .collect();

// Fix with turbofish
let nums: Vec<i32> = numbers
    .split(',')
    .map(|s| s.parse::<i32>().unwrap())
    .collect();

Also see