Rust Advanced

Complete guide to advanced Rust programming and systems development

Table of Contents

Ownership & Borrowing

Ownership Rules
Ownership Rules:
1. Each value has an owner
2. There can only be one owner at a time
3. When owner goes out of scope, value is dropped

┌─────────────────────────────────────────────────────────┐
│                  Ownership Flow                          │
└─────────────────────────────────────────────────────────┘

Stack (Fixed Size):           Heap (Dynamic Size):
┌──────────────┐
│  s1: String  │──────────────┐
│  ptr: 0x1234 │              │
│  len: 5      │              │
│  cap: 5      │              │
└──────────────┘              │
                              ▼
                         ┌──────────────┐
                         │  "Hello"     │
                         │  (heap data) │
                         └──────────────┘

// Move semantics
let s1 = String::from("hello");
let s2 = s1;  // s1 moved to s2, s1 no longer valid

// println!("{}", s1);  // ❌ Error: value borrowed after move

// Clone (deep copy)
let s1 = String::from("hello");
let s2 = s1.clone();  // Both s1 and s2 valid

println!("{} {}", s1, s2);  // ✅ OK

// Copy trait (stack-only data)
let x = 5;
let y = x;  // Copy, not move

println!("{} {}", x, y);  // ✅ Both valid

// Types that implement Copy:
// - All integer types
// - Boolean type
// - Floating point types
// - Character type
// - Tuples (if all elements implement Copy)

// Ownership and functions
fn takes_ownership(s: String) {
    println!("{}", s);
}  // s dropped here

fn makes_copy(x: i32) {
    println!("{}", x);
}

let s = String::from("hello");
takes_ownership(s);  // s moved
// println!("{}", s);  // ❌ Error

let x = 5;
makes_copy(x);  // x copied
println!("{}", x);  // ✅ OK

// Return values and ownership
fn gives_ownership() -> String {
    let s = String::from("hello");
    s  // Ownership moved to caller
}

fn takes_and_gives_back(s: String) -> String {
    s  // Ownership moved back
}

let s1 = gives_ownership();
let s2 = String::from("world");
let s3 = takes_and_gives_back(s2);  // s2 moved

// Multiple return values
fn calculate_length(s: String) -> (String, usize) {
    let length = s.len();
    (s, length)  // Return ownership with result
}

let s1 = String::from("hello");
let (s2, len) = calculate_length(s1);
println!("Length of '{}' is {}", s2, len);
Borrowing & References
// Immutable references
fn calculate_length(s: &String) -> usize {
    s.len()
}  // s goes out of scope, but doesn't drop (doesn't own)

let s1 = String::from("hello");
let len = calculate_length(&s1);  // Borrow s1
println!("Length of '{}' is {}", s1, len);  // s1 still valid

// Multiple immutable references (OK)
let s = String::from("hello");
let r1 = &s;
let r2 = &s;
let r3 = &s;
println!("{}, {}, {}", r1, r2, r3);  // ✅ OK

// Mutable references
fn change(s: &mut String) {
    s.push_str(", world");
}

let mut s = String::from("hello");
change(&mut s);
println!("{}", s);  // "hello, world"

// Mutable reference rules:
// 1. Only ONE mutable reference at a time
// 2. Can't have mutable and immutable references together

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

let r1 = &mut s;
// let r2 = &mut s;  // ❌ Error: second mutable borrow

// But this works (non-overlapping scopes):
{
    let r1 = &mut s;
}  // r1 goes out of scope

let r2 = &mut s;  // ✅ OK

// Can't mix mutable and immutable
let mut s = String::from("hello");

let r1 = &s;     // ✅ OK
let r2 = &s;     // ✅ OK
// let r3 = &mut s;  // ❌ Error: can't borrow as mutable

println!("{} {}", r1, r2);

// After last use of immutable references:
let r3 = &mut s;  // ✅ OK (non-overlapping)
println!("{}", r3);

// Dangling references (prevented by compiler)
// fn dangle() -> &String {
//     let s = String::from("hello");
//     &s  // ❌ Error: returns reference to local variable
// }  // s dropped here

// Fix: return String itself (move ownership)
fn no_dangle() -> String {
    let s = String::from("hello");
    s  // ✅ OK: ownership moved
}

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

let hello = &s[0..5];    // "hello"
let world = &s[6..11];   // "world"
let whole = &s[..];      // entire string

// String slices
fn first_word(s: &str) -> &str {
    let bytes = s.as_bytes();

    for (i, &item) in bytes.iter().enumerate() {
        if item == b' ' {
            return &s[0..i];
        }
    }

    &s[..]
}

let s = String::from("hello world");
let word = first_word(&s);
println!("{}", word);  // "hello"

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

// Deref coercion
fn hello(name: &str) {
    println!("Hello, {}!", name);
}

let s = String::from("Rust");
hello(&s);  // &String → &str automatically

// Interior mutability pattern
use std::cell::RefCell;

let data = RefCell::new(5);

{
    let mut value = data.borrow_mut();
    *value += 1;
}  // Mutable borrow released

println!("{}", data.borrow());  // 6

Lifetimes

Lifetime Annotations
// Lifetime syntax
&i32        // Reference
&'a i32     // Reference with explicit lifetime
&'a mut i32 // Mutable reference with explicit lifetime

// Function with lifetime
// Without lifetime annotation (error):
// fn longest(x: &str, y: &str) -> &str {
//     if x.len() > y.len() { x } else { y }
// }  // ❌ Error: missing lifetime specifier

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

let string1 = String::from("long string");
let string2 = String::from("xyz");

let result = longest(&string1, &string2);
println!("Longest: {}", result);

// Lifetime explanation:
// 'a means: the returned reference will be valid
// as long as both x and y are valid

// Example with different scopes
let string1 = String::from("long string");
let result;

{
    let string2 = String::from("xyz");
    result = longest(&string1, &string2);
    println!("{}", result);  // ✅ OK here
}
// println!("{}", result);  // ❌ Error: string2 dropped

// Struct with lifetime
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  // Returns reference with lifetime 'a
    }
}

let novel = String::from("Call me Ishmael. Some years ago...");
let first_sentence = novel.split('.').next().expect("No '.'");
let excerpt = ImportantExcerpt {
    part: first_sentence,
};

// Multiple lifetimes
fn longest_with_announcement<'a, 'b>(
    x: &'a str,
    y: &'a str,
    ann: &'b str,
) -> &'a str {
    println!("Announcement: {}", ann);
    if x.len() > y.len() { x } else { y }
}

// Lifetime elision rules
// These rules let compiler infer lifetimes:

// Rule 1: Each parameter gets own lifetime
fn first_word(s: &str) -> &str {
    // Compiler infers: fn first_word<'a>(s: &'a str) -> &'a str
}

// Rule 2: If one input lifetime, output gets same lifetime
fn get_first(s: &str) -> &str {
    // Compiler infers: fn get_first<'a>(s: &'a str) -> &'a str
}

// Rule 3: If &self or &mut self, output gets self's lifetime
impl<'a> ImportantExcerpt<'a> {
    fn get_part(&self) -> &str {
        // Compiler infers return lifetime from &self
        self.part
    }
}

// Static lifetime
let s: &'static str = "I have a static lifetime.";
// String literals have 'static lifetime (stored in binary)

// Function returning static reference
fn get_default() -> &'static str {
    "default"
}

// Lifetime bounds
use std::fmt::Display;

fn longest_with_display<'a, T>(
    x: &'a str,
    y: &'a str,
    ann: T,
) -> &'a str
where
    T: Display,
{
    println!("Announcement: {}", ann);
    if x.len() > y.len() { x } else { y }
}

// Lifetime subtyping
struct Context<'a>(&'a str);

struct Parser<'a, 's> {
    context: &'a Context<'s>,
}

impl<'a, 's: 'a> Parser<'a, 's> {
    // 's: 'a means 's outlives 'a
    fn parse(&self) -> Result<(), &'s str> {
        Ok(())
    }
}

// Higher-ranked trait bounds (HRTB)
fn call_with_ref(f: F)
where
    F: for<'a> Fn(&'a str),
{
    f("hello");
}

call_with_ref(|s| println!("{}", s));

Traits & Generics

Traits
// Define trait
pub trait Summary {
    fn summarize(&self) -> String;

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

// Implement trait
pub struct Article {
    pub headline: String,
    pub content: String,
}

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

pub struct Tweet {
    pub username: String,
    pub content: String,
}

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

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

// Trait bound syntax (equivalent)
fn notify(item: &T) {
    println!("Breaking news! {}", item.summarize());
}

// Multiple trait bounds
fn notify(item: &(impl Summary + Display)) {
    // item implements both Summary and Display
}

// Or:
fn notify(item: &T) {
    // ...
}

// Where clauses
fn some_function(t: &T, u: &U) -> i32
where
    T: Display + Clone,
    U: Clone + Debug,
{
    // Function body
}

// Return trait
fn returns_summarizable() -> impl Summary {
    Tweet {
        username: String::from("user"),
        content: String::from("content"),
    }
}

// Note: can only return ONE concrete type

// Associated types
pub trait Iterator {
    type Item;  // Associated type

    fn next(&mut self) -> Option;
}

struct Counter {
    count: u32,
}

impl Iterator for Counter {
    type Item = u32;  // Specify associated type

    fn next(&mut self) -> Option {
        self.count += 1;
        if self.count < 6 {
            Some(self.count)
        } else {
            None
        }
    }
}

// Trait objects (dynamic dispatch)
pub trait Draw {
    fn draw(&self);
}

pub struct Screen {
    pub components: Vec>,
}

impl Screen {
    pub fn run(&self) {
        for component in self.components.iter() {
            component.draw();
        }
    }
}

// Add different types implementing Draw
let screen = Screen {
    components: vec![
        Box::new(Button { /* ... */ }),
        Box::new(TextField { /* ... */ }),
    ],
};

// Supertraits
trait OutlinePrint: Display {
    fn outline_print(&self) {
        let output = self.to_string();
        println!("* {} *", output);
    }
}

// Must implement Display to implement OutlinePrint

// Operator overloading
use std::ops::Add;

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

impl Add for Point {
    type Output = Point;

    fn add(self, other: Point) -> Point {
        Point {
            x: self.x + other.x,
            y: self.y + other.y,
        }
    }
}

let p1 = Point { x: 1, y: 2 };
let p2 = Point { x: 3, y: 4 };
let p3 = p1 + p2;

// Common traits
// Clone: explicit copy
#[derive(Clone)]
struct MyStruct { /* ... */ }

// Copy: implicit copy (must be Clone)
#[derive(Copy, Clone)]
struct Point { x: i32, y: i32 }

// Debug: {:?} formatting
#[derive(Debug)]
struct User { name: String }

// PartialEq, Eq: equality comparison
#[derive(PartialEq, Eq)]
struct Id(u32);

// PartialOrd, Ord: ordering
#[derive(PartialOrd, Ord, PartialEq, Eq)]
struct Priority(u32);

// Hash: for use in HashMap/HashSet
#[derive(Hash, PartialEq, Eq)]
struct Key(String);

// Default: default value
#[derive(Default)]
struct Config {
    timeout: u32,
    retries: u32,
}

let config = Config::default();
Generics
// Generic function
fn largest(list: &[T]) -> &T {
    let mut largest = &list[0];

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

    largest
}

let numbers = vec![34, 50, 25, 100, 65];
let result = largest(&numbers);

// Generic struct
struct Point {
    x: T,
    y: T,
}

impl Point {
    fn x(&self) -> &T {
        &self.x
    }
}

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

let p = Point { x: 5, y: 10 };
let f = Point { x: 1.0, y: 4.0 };

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

let both_integer = Point { x: 5, y: 10 };
let both_float = Point { x: 1.0, y: 4.0 };
let mixed = Point { x: 5, y: 4.0 };

// Generic enum
enum Option {
    Some(T),
    None,
}

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

// Generic with trait bounds
fn print_all(items: &[T]) {
    for item in items {
        println!("{}", item);
    }
}

// Const generics
struct ArrayPair {
    left: [T; N],
    right: [T; N],
}

fn print_array(arr: [T; N]) {
    for item in arr {
        println!("{}", item);
    }
}

print_array([1, 2, 3]);
print_array(["a", "b", "c", "d"]);

// Phantom types
use std::marker::PhantomData;

struct Locked;
struct Unlocked;

struct Door {
    _state: PhantomData,
}

impl Door {
    fn unlock(self) -> Door {
        Door {
            _state: PhantomData,
        }
    }
}

impl Door {
    fn open(&self) {
        println!("Door opened");
    }
}

let locked_door: Door = Door {
    _state: PhantomData,
};
// locked_door.open();  // ❌ Error: can't open locked door

let unlocked_door = locked_door.unlock();
unlocked_door.open();  // ✅ OK

Resources & Learning Path

Learning Progression
Phase 1: Rust Fundamentals (3-4 weeks)
□ Ownership and borrowing
□ Basic types and control flow
□ Structs and enums
□ Pattern matching
□ Error handling with Result

Phase 2: Intermediate Rust (4-6 weeks)
□ Lifetimes
□ Traits and generics
□ Collections and iterators
□ Testing
□ Cargo and modules

Phase 3: Advanced Rust (6-8 weeks)
□ Smart pointers
□ Concurrency patterns
□ Async programming
□ Unsafe Rust
□ Macros

Phase 4: Production Rust (Ongoing)
□ Performance optimization
□ FFI and interop
□ Production patterns
□ Systems programming
□ Embedded development
Pro Tips Summary
Ownership & Borrowing
✓ Prefer borrowing over moving
✓ Use references when possible
✓ Clone only when necessary
✓ Understand lifetime elision rules
✓ Use Cow for flexible ownership

Performance
✓ Use iterators over loops
✓ Avoid unnecessary allocations
✓ Use &str instead of String
✓ Profile before optimizing
✓ Use release builds for benchmarks
✓ Consider zero-cost abstractions

Error Handling
✓ Use Result for recoverable errors
✓ Use panic! for unrecoverable errors
✓ Implement Error trait
✓ Use ? operator for propagation
✓ Create custom error types
✓ Use anyhow/thiserror crates

Async
✓ Use async/await for I/O
✓ Don't block async runtime
✓ Choose right runtime (tokio/async-std)
✓ Understand Future trait
✓ Use select! for concurrency
✓ Handle cancellation properly

Best Practices
✓ Follow Rust naming conventions
✓ Use clippy for lints
✓ Format with rustfmt
✓ Write documentation tests
✓ Use cargo workspaces
✓ Keep dependencies updated
✓ Minimize unsafe code