ExamplesBy LevelBy TopicLearning Paths
548 Intermediate

Named Return Lifetimes

Functional Programming

Tutorial Video

Text description (accessibility)

This video demonstrates the "Named Return Lifetimes" functional Rust example. Difficulty level: Intermediate. Key concepts covered: Functional Programming. When a function returns a reference, the lifetime of that reference must be tied to one of its inputs. Key difference from OCaml: 1. **Documentation vs enforcement**: Rust named return lifetimes both document and enforce which input is borrowed; OCaml documentation comments describe the relationship with no enforcement.

Tutorial

The Problem

When a function returns a reference, the lifetime of that reference must be tied to one of its inputs. In simple cases, elision handles this automatically. But when functions have multiple reference parameters and it is important to document or enforce which input the output borrows from, explicit named lifetimes in the return type make the relationship unambiguous. Named return lifetimes are especially valuable in parser structs, view adapters, and any API where the relationship between input source and output view matters for correctness.

🎯 Learning Outcomes

  • • How named output lifetimes like 'out and 'input clarify which input a reference comes from
  • • How prefer_first<'a, 'b>(x: &'a str, _y: &'b str) -> &'a str documents the output source
  • • How Parser<'input> uses a named lifetime to tie parsed output to the input buffer
  • • When naming lifetimes with semantically meaningful names ('input, 'source, 'out) helps readability
  • • The difference between elided lifetimes and explicitly named ones in terms of semantics
  • Code Example

    #![allow(clippy::all)]
    //! Named Return Lifetimes
    //!
    //! Explicit lifetime names for return references.
    
    /// Explicitly named output lifetime.
    pub fn first<'out>(items: &'out [i32]) -> Option<&'out i32> {
        items.first()
    }
    
    /// Two inputs, output tied to first.
    pub fn prefer_first<'a, 'b>(x: &'a str, _y: &'b str) -> &'a str {
        x
    }
    
    /// Struct with named output lifetime.
    pub struct Parser<'input> {
        data: &'input str,
    }
    
    impl<'input> Parser<'input> {
        pub fn new(data: &'input str) -> Self {
            Parser { data }
        }
    
        pub fn parse(&self) -> &'input str {
            self.data.trim()
        }
    }
    
    #[cfg(test)]
    mod tests {
        use super::*;
    
        #[test]
        fn test_first() {
            let items = [1, 2, 3];
            assert_eq!(first(&items), Some(&1));
        }
    
        #[test]
        fn test_prefer_first() {
            assert_eq!(prefer_first("a", "b"), "a");
        }
    
        #[test]
        fn test_parser() {
            let input = "  hello  ";
            let parser = Parser::new(input);
            assert_eq!(parser.parse(), "hello");
        }
    }

    Key Differences

  • Documentation vs enforcement: Rust named return lifetimes both document and enforce which input is borrowed; OCaml documentation comments describe the relationship with no enforcement.
  • **'input vs 'a**: Using 'input as a lifetime name is purely cosmetic in Rust — it carries no additional meaning beyond 'a to the type checker, but significantly improves human readability.
  • Parser struct lifetimes: Rust Parser<'input> enforces that parsed results cannot outlive the input buffer; OCaml parsers hold GC-managed strings with no lifetime constraint.
  • Compiler verification: Rust verifies that -> &'out i32 actually comes from the 'out-annotated input; OCaml relies on programmer discipline and testing.
  • OCaml Approach

    OCaml function return types carry no lifetime annotation. The relationship between input and output references is a convention expressed through documentation:

    type 'input parser = { data: 'input }
    let parse p = String.trim p.data  (* always valid — GC-managed *)
    

    Full Source

    #![allow(clippy::all)]
    //! Named Return Lifetimes
    //!
    //! Explicit lifetime names for return references.
    
    /// Explicitly named output lifetime.
    pub fn first<'out>(items: &'out [i32]) -> Option<&'out i32> {
        items.first()
    }
    
    /// Two inputs, output tied to first.
    pub fn prefer_first<'a, 'b>(x: &'a str, _y: &'b str) -> &'a str {
        x
    }
    
    /// Struct with named output lifetime.
    pub struct Parser<'input> {
        data: &'input str,
    }
    
    impl<'input> Parser<'input> {
        pub fn new(data: &'input str) -> Self {
            Parser { data }
        }
    
        pub fn parse(&self) -> &'input str {
            self.data.trim()
        }
    }
    
    #[cfg(test)]
    mod tests {
        use super::*;
    
        #[test]
        fn test_first() {
            let items = [1, 2, 3];
            assert_eq!(first(&items), Some(&1));
        }
    
        #[test]
        fn test_prefer_first() {
            assert_eq!(prefer_first("a", "b"), "a");
        }
    
        #[test]
        fn test_parser() {
            let input = "  hello  ";
            let parser = Parser::new(input);
            assert_eq!(parser.parse(), "hello");
        }
    }
    ✓ Tests Rust test suite
    #[cfg(test)]
    mod tests {
        use super::*;
    
        #[test]
        fn test_first() {
            let items = [1, 2, 3];
            assert_eq!(first(&items), Some(&1));
        }
    
        #[test]
        fn test_prefer_first() {
            assert_eq!(prefer_first("a", "b"), "a");
        }
    
        #[test]
        fn test_parser() {
            let input = "  hello  ";
            let parser = Parser::new(input);
            assert_eq!(parser.parse(), "hello");
        }
    }

    Deep Comparison

    OCaml vs Rust: lifetime named return

    See example.rs and example.ml for implementations.

    Key Differences

  • OCaml uses garbage collection
  • Rust uses ownership and borrowing
  • Both support the core concept
  • Exercises

  • Named lifetime parser: Implement struct Tokenizer<'src> { source: &'src str, pos: usize } with a method fn next_token(&mut self) -> Option<&'src str> that returns a slice of source.
  • Two-input choose: Write fn choose<'long: 'short, 'short>(cond: bool, a: &'long str, b: &'short str) -> &'short str — explain why 'long: 'short is needed here.
  • Lifetime documentation: Take any three functions from earlier examples and rewrite them with semantically named lifetimes ('input, 'key, 'value) instead of 'a/'b — assess whether readability improves.
  • Open Source Repos