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

557: Output Lifetimes in Traits

Difficulty: 4 Level: Intermediate-Advanced Specify which input a trait method's return value borrows from โ€” so the compiler can track validity across trait boundaries.

The Problem This Solves

When a trait method returns a reference, the compiler needs to know which input it borrows from. For simple cases (one input, `&self`), lifetime elision handles it. But when a method has both `&self` and a `&'a str` input, and the output borrows from the string argument rather than `self`, you must be explicit โ€” elision gets it wrong. This pattern appears constantly in extractors, parsers, adapters, and any abstraction over "give me a view into this data." Without explicit output lifetimes in traits, you either over-restrict callers (requiring both to live equally long) or end up copying data unnecessarily. The lifetime parameter on the trait itself (`trait Extractor<'a>`) vs on the method is the key design decision, and each choice has different tradeoffs for implementors.

The Intuition

Think of a lifetime annotation on the output as a label: "this return value was carved out of that input." When you write `fn extract(&self, source: &'a str) -> Vec<&'a str>`, you're saying "the returned slices point into `source`, not into `self`." The caller can then drop `self` while still using the returned slices โ€” as long as `source` lives. Putting `'a` on the trait (`trait Extractor<'a>`) instead of on the method means the lifetime is fixed when you implement the trait, not when you call the method. This is useful for implementors that need to store `'a`-lived data.

How It Works in Rust

Lifetime on the trait โ€” fixed at impl time:
trait Extractor<'a> {
 type Output;
 fn extract(&self, source: &'a str) -> Self::Output;
}

impl<'a> Extractor<'a> for WordExtractor {
 type Output = Vec<&'a str>;
 fn extract(&self, source: &'a str) -> Vec<&'a str> {
     source.split_whitespace().collect()
 }
}
The associated type `Output` is generic over `'a`, so callers get `Vec<&'a str>` โ€” slices of the input string. Using the trait generically โ€” the bound ties input and output lifetimes:
fn extract_and_print<'a>(
 extractor: &impl Extractor<'a, Output = Vec<&'a str>>,
 text: &'a str,
) {
 let words = extractor.extract(text);
 println!("{:?}", words);
}
`&self` lifetime in output โ€” elision works here:
trait AsRef2 {
 fn as_str(&self) -> &str;  // implicitly: fn as_str<'a>(&'a self) -> &'a str
}
Elision rule 3: when `&self` is the only input, the output borrows from `self`. Proving the output borrows from the argument, not `self`:
let text = String::from("hello world rust");
let extractor = WordExtractor;
let words = extractor.extract(&text);
// extractor can be dropped here โ€” words only borrow from text
drop(extractor);
println!("{:?}", &words[..2]);  // still valid

What This Unlocks

Key Differences

ConceptOCamlRust
Trait with lifetimeN/A (no lifetimes)`trait Foo<'a>` or method-level `'a`
Return borrows from argumentGC tracks implicitlyExplicit `'a` on both input and output
Associated type with lifetimeN/A`type Output;` resolved at `impl` with `'a` in scope
Elision for `&self` outputN/ARule 3: output inherits `self`'s lifetime automatically
//! # 557. Output Lifetimes in Traits
//! Lifetime parameters in trait method return types.

/// Trait with lifetime in output
trait Extractor<'a> {
    type Output;
    fn extract(&self, source: &'a str) -> Self::Output;
}

struct WordExtractor;
impl<'a> Extractor<'a> for WordExtractor {
    type Output = Vec<&'a str>;
    fn extract(&self, source: &'a str) -> Vec<&'a str> {
        source.split_whitespace().collect()
    }
}

/// Trait with self lifetime in output
trait AsRef2 {
    fn as_str(&self) -> &str;
}

struct Wrapper(String);
impl AsRef2 for Wrapper {
    fn as_str(&self) -> &str { &self.0 }
}

/// Generic function using trait with output lifetime
fn extract_and_print<'a>(extractor: &impl Extractor<'a, Output = Vec<&'a str>>, text: &'a str) {
    let words = extractor.extract(text);
    println!("extracted {} words: {:?}", words.len(), words);
}

fn main() {
    let text = String::from("hello world rust programming");
    let extractor = WordExtractor;
    extract_and_print(&extractor, &text);

    let w = Wrapper("wrapped string".to_string());
    println!("as_str: {}", w.as_str());

    // Lifetime in return position of trait method
    let s = String::from("output lifetime demo");
    let words = extractor.extract(&s);
    println!("words still valid: {:?}", &words[..2]);
}

#[cfg(test)]
mod tests {
    use super::*;
    #[test]
    fn test_word_extractor() {
        let text = "a b c d";
        let e = WordExtractor;
        let words = e.extract(text);
        assert_eq!(words, vec!["a", "b", "c", "d"]);
    }
    #[test]
    fn test_wrapper() {
        let w = Wrapper("hello".to_string());
        assert_eq!(w.as_str(), "hello");
    }
}
(* Output lifetimes in OCaml โ€” all automatic *)
let longest s1 s2 = if String.length s1 >= String.length s2 then s1 else s2
let first_char s = if String.length s > 0 then Some (String.sub s 0 1) else None
let suffix s n = if n >= String.length s then "" else String.sub s n (String.length s - n)

let () =
  Printf.printf "%s\n" (longest "hello" "hi");
  Printf.printf "%s\n" (match first_char "rust" with Some s -> s | None -> "");
  Printf.printf "%s\n" (suffix "hello world" 6)