๐Ÿฆ€ Functional Rust
๐ŸŽฌ Rust Ownership in 30 seconds Visual walkthrough of ownership, moves, and automatic memory management.
๐Ÿ“ Text version (for readers / accessibility)

โ€ข Each value in Rust has exactly one owner โ€” when the owner goes out of scope, the value is dropped

โ€ข Assignment moves ownership by default; the original binding becomes invalid

โ€ข Borrowing (&T / &mut T) lets you reference data without taking ownership

โ€ข The compiler enforces: many shared references OR one mutable reference, never both

โ€ข No garbage collector needed โ€” memory is freed deterministically at scope exit

107: Lifetimes in Structs

Difficulty: 3 Level: Advanced When a struct holds a reference, it must carry a lifetime parameter โ€” proving the struct can't outlive the data it points into.

The Problem This Solves

Storing a reference in a struct is a trap in C. A struct that holds a `char*` pointing into a local variable will become a dangling pointer the moment that local variable goes out of scope โ€” but nothing stops you from storing the struct globally or passing it to another function. The struct looks valid; reading it returns garbage or crashes. In Java, this can't happen โ€” every reference keeps the pointed-to object alive. But that means objects that "should" be freed stay in memory as long as any reference exists, causing memory leaks in long-lived caches. Rust handles this exactly right: a struct holding a reference is only valid as long as the referenced data is alive. The lifetime parameter on the struct makes this relationship explicit. The compiler then tracks every instance of the struct and ensures it never outlives the data it points into. No dangling pointers, no memory leaks, no surprises.

The Intuition

A struct that holds a reference must declare its lifetime (`'a`) to let the compiler enforce: this struct instance cannot outlive the data it's borrowing โ€” the struct is valid for exactly as long as the reference is valid.

How It Works in Rust

// ERROR: struct holds reference but has no lifetime parameter
struct Excerpt {
 text: &str, // ERROR: missing lifetime specifier
}

// FIX: declare the lifetime on the struct
struct Excerpt<'a> {
 text: &'a str, // "this struct lives no longer than the &str it holds"
}

fn demo() {
 let novel = String::from("Call me Ishmael. Some years ago...");
 
 let first_sentence;
 {
     let i = novel.find('.').unwrap_or(novel.len());
     first_sentence = &novel[..i];
 }
 
 let excerpt = Excerpt { text: first_sentence };
 println!("{}", excerpt.text); // fine โ€” novel is still alive
}

// Lifetime on impl blocks mirrors the struct
impl<'a> Excerpt<'a> {
 fn text(&self) -> &str {
     // Return elides to &'a str โ€” the excerpt's lifetime
     self.text
 }
 
 fn announce(&self, announcement: &str) -> &str {
     // Rule 3: output gets self's lifetime
     println!("Attention: {}", announcement);
     self.text
 }
}

// ERROR: struct outlives the data it references
fn dangling_excerpt() -> Excerpt<'_> {
 let text = String::from("temporary");
 Excerpt { text: &text } // ERROR: text dropped when function returns
}

// Multiple references โ€” each with its own lifetime constraint
struct TwoStrings<'a, 'b> {
 first: &'a str,
 second: &'b str,
}

What This Unlocks

Key Differences

ConceptOCamlRust
Struct holding referenceAutomatic (GC keeps target alive)Must declare lifetime parameter
Struct outliving dataImpossible (GC prevents)Compile error (lifetime check)
Zero-copy string viewsLess common (strings are values)Idiomatic with `&'a str` fields
Lifetime annotation on structNot neededRequired when struct holds references
Dangling struct referenceCan't happen (GC)Can't happen (borrow checker)
// Example 107: Lifetimes in Structs
//
// When a struct holds a reference, it needs a lifetime parameter
// to ensure the referenced data outlives the struct.

// Approach 1: Struct borrowing a string slice
#[derive(Debug)]
struct Excerpt<'a> {
    text: &'a str,
    page: u32,
}

fn approach1() {
    let book = String::from("Call me Ishmael. Some years ago...");
    let exc = Excerpt { text: &book[..16], page: 1 };
    assert_eq!(exc.text, "Call me Ishmael.");
    println!("Excerpt p.{}: {}", exc.page, exc.text);
    // exc cannot outlive book
}

// Approach 2: Struct with multiple borrowed fields
#[derive(Debug)]
struct Article<'a> {
    title: &'a str,
    author: &'a str,
    body: &'a str,
}

impl<'a> Article<'a> {
    fn summarize(&self) -> String {
        format!("{} by {} ({} chars)", self.title, self.author, self.body.len())
    }
}

fn approach2() {
    let title = String::from("Rust Ownership");
    let author = String::from("Alice");
    let body = String::from("Ownership is...");
    let a = Article { title: &title, author: &author, body: &body };
    println!("{}", a.summarize());
}

// Approach 3: Nested structs with lifetimes
#[derive(Debug)]
struct Highlight<'a> {
    excerpt: Excerpt<'a>,
    note: &'a str,
}

fn approach3() {
    let text = String::from("important passage");
    let note = String::from("Remember this!");
    let exc = Excerpt { text: &text, page: 42 };
    let h = Highlight { excerpt: exc, note: &note };
    assert_eq!(h.excerpt.page, 42);
    println!("Highlight p.{}: {} โ€” {}", h.excerpt.page, h.excerpt.text, h.note);
}

fn main() {
    println!("=== Approach 1: Basic Struct with Lifetime ===");
    approach1();
    println!("\n=== Approach 2: Multiple Borrowed Fields ===");
    approach2();
    println!("\n=== Approach 3: Nested Lifetimes ===");
    approach3();
}

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

    #[test]
    fn test_excerpt() {
        let text = "hello world";
        let e = Excerpt { text, page: 1 };
        assert_eq!(e.text, "hello world");
    }

    #[test]
    fn test_article_summarize() {
        let a = Article { title: "T", author: "A", body: "12345" };
        assert!(a.summarize().contains("5 chars"));
    }

    #[test]
    fn test_highlight() {
        let e = Excerpt { text: "test", page: 1 };
        let h = Highlight { excerpt: e, note: "note" };
        assert_eq!(h.note, "note");
    }

    #[test]
    fn test_lifetime_scope() {
        let outer = String::from("outer");
        let e = Excerpt { text: &outer, page: 1 };
        assert_eq!(e.text, "outer");
        // e cannot outlive outer โ€” compiler enforces this
    }
}
(* Example 107: Lifetimes in Structs โ€” Storing References Safely *)

(* Approach 1: Struct holding a reference to external data *)
type excerpt = { text : string; page : int }

let make_excerpt text page = { text; page }

let approach1 () =
  let book = "Call me Ishmael. Some years ago..." in
  let exc = make_excerpt (String.sub book 0 16) 1 in
  assert (exc.text = "Call me Ishmael.");
  Printf.printf "Excerpt p.%d: %s\n" exc.page exc.text

(* Approach 2: Struct with multiple string fields *)
type article = { title : string; author : string; body : string }

let summarize a =
  Printf.sprintf "%s by %s (%d chars)" a.title a.author (String.length a.body)

let approach2 () =
  let a = { title = "Rust Ownership"; author = "Alice"; body = "Ownership is..." } in
  let s = summarize a in
  Printf.printf "%s\n" s

(* Approach 3: Nested structs with shared data *)
type highlight = { excerpt : excerpt; note : string }

let approach3 () =
  let exc = { text = "important passage"; page = 42 } in
  let h = { excerpt = exc; note = "Remember this!" } in
  assert (h.excerpt.page = 42);
  Printf.printf "Highlight p.%d: %s โ€” %s\n" h.excerpt.page h.excerpt.text h.note

let () =
  approach1 ();
  approach2 ();
  approach3 ();
  Printf.printf "โœ“ All tests passed\n"

๐Ÿ“Š Detailed Comparison

Comparison: Lifetimes in Structs

Struct Holding Text

OCaml:

๐Ÿช Show OCaml equivalent
type excerpt = { text : string; page : int }
let e = { text = "hello"; page = 1 }

Rust:

struct Excerpt<'a> {
 text: &'a str,  // borrows โ€” needs lifetime
 page: u32,
}
let e = Excerpt { text: "hello", page: 1 };

Methods on Borrowed Structs

OCaml:

๐Ÿช Show OCaml equivalent
let summarize a =
Printf.sprintf "%s by %s" a.title a.author

Rust:

impl<'a> Article<'a> {
 fn summarize(&self) -> String {
     format!("{} by {}", self.title, self.author)
 }
}

Owned vs Borrowed Fields

OCaml โ€” no distinction:

๐Ÿช Show OCaml equivalent
type label = { name : string }  (* always GC-managed *)

Rust โ€” choose based on ownership needs:

// Borrowed โ€” lightweight but has lifetime constraint
struct Label<'a> { name: &'a str }

// Owned โ€” no lifetime, but allocates
struct OwnedLabel { name: String }