๐Ÿฆ€ 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

535: Lifetimes in Enums

Difficulty: 3 Level: Intermediate Enum variants that hold references require the same lifetime treatment as structs โ€” the enum cannot outlive the data it borrows.

The Problem This Solves

Enums are frequently used in parsers and type-safe protocols. When a variant holds a reference to an input string, without lifetime annotations you get:
enum Token {
 Word(&str),     // error: missing lifetime specifier
 Number(i64),
 End,
}
The compiler needs to know how long `Word`'s `&str` is valid. Without that, nothing stops you from extracting a `Token::Word(s)` and using `s` after the source string is dropped โ€” a classic use-after-free. Lifetime annotations on enums also enable zero-copy parsers: instead of allocating a `String` for each token, you return slices pointing into the original input. The compiler enforces that those slices don't outlive the input.

The Intuition

An enum with a lifetime `<'a>` is just a sum type that happens to contain a view. The `'a` parameter applies to any variant that holds a `&'a` reference. Variants without references (like `Number(i64)`) are unaffected โ€” they're owned data and don't participate in the lifetime constraint. Pattern matching on a lifetime-annotated enum works exactly the same as without lifetimes. The annotation only adds a compile-time check that the enum doesn't outlive its borrowed data.

How It Works in Rust

The annotation:
// 'a declares: any variant holding a &str borrows from a source that lives for 'a
#[derive(Debug, PartialEq)]
enum Token<'a> {
 Word(&'a str),      // zero-copy slice from input
 Number(i64),        // owned โ€” not constrained by 'a
 Punctuation(char),  // owned โ€” not constrained by 'a
 End,                // no data at all
}
Using it in a parser:
fn parse_token(input: &str) -> Token<'_> {
 //                                ^^^ 'lifetime of input flows to Token
 let input = input.trim_start();
 if input.is_empty() { return Token::End; }
 
 let first = input.chars().next().unwrap();
 if first.is_alphabetic() {
     let end = input.find(|c: char| !c.is_alphanumeric())
                    .unwrap_or(input.len());
     Token::Word(&input[..end])  // zero-copy slice โ€” valid as long as input is
 } else {
     Token::Number(42) // placeholder
 }
}
Generic result enum with lifetime:
// Both the success value and the remaining input borrow from the source
enum ParseResult<'a, T> {
 Ok(T, &'a str),        // parsed value + remaining input
 Err(&'a str, String),  // failing position (borrowed) + error message (owned)
}
Pattern matching works normally:
let input = String::from("hello world");
match parse_token(&input) {
 Token::Word(w)       => println!("word: {}", w),  // w is &'a str
 Token::Number(n)     => println!("num: {}", n),
 Token::Punctuation(c)=> println!("punct: {}", c),
 Token::End           => println!("end"),
}

What This Unlocks

Key Differences

ConceptOCamlRust
Variant with referenceADT constructors hold any value; GC manages validityVariants with `&'a str` require `<'a>` on the enum; compiler tracks validity
Zero-copy parsing`Bigarray.t` or manual unsafe needed for zero-copy`&'a str` variants are naturally zero-copy โ€” slice the input
Mixed owned/borrowed variantsGC handles all โ€” no distinction neededSome variants owned (no 'a constraint), others borrowed (participate in 'a)
Result types in parsers`('a, 'b) result` or custom type`ParseResult<'a, T>` โ€” remaining input borrows from source, error message owned
Enum lifetime scopeN/AEnum<'a> cannot outlive its 'a โ€” enforced at all use sites
//! # 535. Lifetimes in Enums
//! Enum variants containing references require lifetime parameters.

/// Token borrowing from the input string
#[derive(Debug, PartialEq)]
enum Token<'a> {
    Word(&'a str),
    Number(i64),
    Punctuation(char),
    End,
}

/// Parse result that borrows from input
#[derive(Debug)]
enum ParseResult<'a, T> {
    Ok(T, &'a str),   // value + remaining input
    Err(&'a str, String), // failing input + error message
}

/// JSON-like value that may borrow from source
#[derive(Debug)]
enum JsonValue<'a> {
    Null,
    Bool(bool),
    Int(i64),
    Str(&'a str), // zero-copy string slice
    Array(Vec<JsonValue<'a>>),
}

fn parse_token(input: &str) -> ParseResult<'_, Token<'_>> {
    let input = input.trim_start();
    if input.is_empty() {
        return ParseResult::Ok(Token::End, "");
    }

    let mut chars = input.char_indices();
    let (_, first) = chars.next().unwrap();

    if first.is_alphabetic() {
        let end = chars
            .find(|(_, c)| !c.is_alphanumeric() && *c != '_')
            .map(|(i, _)| i)
            .unwrap_or(input.len());
        return ParseResult::Ok(Token::Word(&input[..end]), &input[end..]);
    }

    if first.is_ascii_digit() || (first == '-' && chars.next().map(|(_, c)| c.is_ascii_digit()).unwrap_or(false)) {
        let end = input.find(|c: char| !c.is_ascii_digit() && c != '-').unwrap_or(input.len());
        if let Ok(n) = input[..end].parse::<i64>() {
            return ParseResult::Ok(Token::Number(n), &input[end..]);
        }
    }

    if first.is_ascii_punctuation() || first == ' ' {
        return ParseResult::Ok(Token::Punctuation(first), &input[1..]);
    }

    ParseResult::Err(input, format!("unexpected character: {:?}", first))
}

fn main() {
    // Tokenize a string
    let input = "hello world 42 foo!";
    let mut remaining = input;
    println!("Tokenizing: {:?}", input);

    loop {
        match parse_token(remaining) {
            ParseResult::Ok(Token::End, _) => break,
            ParseResult::Ok(token, rest) => {
                println!("  {:?}", token);
                remaining = rest;
            }
            ParseResult::Err(at, msg) => {
                println!("  Error: {} at {:?}", msg, at);
                break;
            }
        }
    }

    // JsonValue with borrowed strings
    let source = String::from(r#"hello world rust"#);
    let words: Vec<&str> = source.split_whitespace().collect();
    let json_arr: Vec<JsonValue<'_>> = words.iter().map(|w| JsonValue::Str(w)).collect();
    let json = JsonValue::Array(json_arr);
    println!("\nJsonValue: {:?}", json);

    // Pattern match on enum with lifetime
    let text = String::from("  identifier 123");
    match parse_token(&text) {
        ParseResult::Ok(Token::Word(w), rest) => println!("Word: {:?}, rest: {:?}", w, rest),
        ParseResult::Ok(Token::Number(n), _)  => println!("Number: {}", n),
        other => println!("other: {:?}", other),
    }
}

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

    #[test]
    fn test_parse_word() {
        let input = "hello world";
        match parse_token(input) {
            ParseResult::Ok(Token::Word(w), rest) => {
                assert_eq!(w, "hello");
                assert_eq!(rest, " world");
            }
            _ => panic!("expected Word"),
        }
    }

    #[test]
    fn test_parse_number() {
        let input = "42 rest";
        match parse_token(input) {
            ParseResult::Ok(Token::Number(n), _) => assert_eq!(n, 42),
            _ => panic!("expected Number"),
        }
    }

    #[test]
    fn test_parse_end() {
        let input = "";
        match parse_token(input) {
            ParseResult::Ok(Token::End, _) => {}
            _ => panic!("expected End"),
        }
    }
}
(* Enums with string references in OCaml โ€” GC handles lifetimes *)
type token =
  | Word of string
  | Number of int
  | Punctuation of char
  | End

type parse_result =
  | Ok of token * string   (* token and remaining input *)
  | Error of string * int  (* message and position *)

let parse_token input pos =
  if pos >= String.length input then Ok (End, "")
  else
    let c = input.[pos] in
    if c >= '0' && c <= '9' then
      Ok (Number (Char.code c - Char.code '0'), String.sub input (pos+1) (String.length input - pos - 1))
    else if c = ' ' then
      Ok (Punctuation ' ', String.sub input (pos+1) (String.length input - pos - 1))
    else
      Ok (Word (String.make 1 c), String.sub input (pos+1) (String.length input - pos - 1))

let () =
  let input = "hello 42" in
  let rec go pos =
    if pos >= String.length input then ()
    else match parse_token input pos with
    | Ok (End, _) -> ()
    | Ok (Word w, _rest) -> Printf.printf "Word: %s\n" w; go (pos + 1)
    | Ok (Number n, _rest) -> Printf.printf "Number: %d\n" n; go (pos + 1)
    | Ok (Punctuation c, _rest) -> Printf.printf "Punc: %c\n" c; go (pos + 1)
    | Error (msg, p) -> Printf.printf "Error at %d: %s\n" p msg
  in
  go 0