ExamplesBy LevelBy TopicLearning Paths
558 Intermediate

Input Lifetime Patterns

Functional Programming

Tutorial Video

Text description (accessibility)

This video demonstrates the "Input Lifetime Patterns" functional Rust example. Difficulty level: Intermediate. Key concepts covered: Functional Programming. Input lifetimes determine how long the references passed to a function must remain valid. Key difference from OCaml: 1. **Input validity duration**: Rust input lifetimes enforce at compile time how long the caller must keep inputs alive; OCaml's GC extends lifetime automatically as needed.

Tutorial

The Problem

Input lifetimes determine how long the references passed to a function must remain valid. A function with one &str input has a simple input lifetime; a function with multiple &str inputs has multiple potentially independent input lifetimes. Getting input lifetimes right determines how long callers must keep their data alive. Over-constraining input lifetimes (requiring longer-lived data than necessary) makes APIs rigid; under-constraining them is a compile error. This example surveys the common input lifetime patterns systematically.

🎯 Learning Outcomes

  • • How single-input functions get one input lifetime (often elided)
  • • How first<'a, 'b>(a: &'a str, _b: &'b str) -> &'a str expresses independent input lifetimes
  • • How Processor<'input> ties the struct to an input buffer's lifetime at construction
  • • Why input lifetimes on struct constructors determine how long the struct can live
  • • How the relationship between input and output lifetimes determines API flexibility
  • Code Example

    #![allow(clippy::all)]
    //! Input Lifetime Patterns
    //!
    //! How input lifetimes constrain function signatures.
    
    /// Single input lifetime propagates to output.
    pub fn echo<'a>(s: &'a str) -> &'a str {
        s
    }
    
    /// Multiple inputs with independent lifetimes.
    pub fn first<'a, 'b>(a: &'a str, _b: &'b str) -> &'a str {
        a
    }
    
    /// Input lifetime bounds struct.
    pub struct Processor<'input> {
        data: &'input str,
    }
    
    impl<'input> Processor<'input> {
        pub fn new(data: &'input str) -> Self {
            Processor { data }
        }
    
        pub fn process(&self) -> &'input str {
            self.data.trim()
        }
    }
    
    #[cfg(test)]
    mod tests {
        use super::*;
    
        #[test]
        fn test_echo() {
            assert_eq!(echo("hello"), "hello");
        }
    
        #[test]
        fn test_first() {
            assert_eq!(first("a", "b"), "a");
        }
    
        #[test]
        fn test_processor() {
            let p = Processor::new("  test  ");
            assert_eq!(p.process(), "test");
        }
    }

    Key Differences

  • Input validity duration: Rust input lifetimes enforce at compile time how long the caller must keep inputs alive; OCaml's GC extends lifetime automatically as needed.
  • Independent inputs: Rust's <'a, 'b> allows two inputs to have different scopes — one can go out of scope before the other; OCaml treats all GC-managed inputs uniformly.
  • Struct-bound lifetimes: Rust Processor<'input> cannot outlive its input data; OCaml's processor keeps its data alive as long as the processor exists.
  • API design: Rust API designers choose whether inputs share a lifetime or have independent lifetimes — a design decision affecting caller ergonomics; OCaml has no such choice.
  • OCaml Approach

    OCaml input "lifetimes" are managed by the GC — the compiler does not track how long input data must remain valid. Functions that store references to inputs simply keep them alive through GC references:

    type 'a processor = { data: 'a }
    let process p = String.trim p.data  (* data kept alive by GC *)
    

    Full Source

    #![allow(clippy::all)]
    //! Input Lifetime Patterns
    //!
    //! How input lifetimes constrain function signatures.
    
    /// Single input lifetime propagates to output.
    pub fn echo<'a>(s: &'a str) -> &'a str {
        s
    }
    
    /// Multiple inputs with independent lifetimes.
    pub fn first<'a, 'b>(a: &'a str, _b: &'b str) -> &'a str {
        a
    }
    
    /// Input lifetime bounds struct.
    pub struct Processor<'input> {
        data: &'input str,
    }
    
    impl<'input> Processor<'input> {
        pub fn new(data: &'input str) -> Self {
            Processor { data }
        }
    
        pub fn process(&self) -> &'input str {
            self.data.trim()
        }
    }
    
    #[cfg(test)]
    mod tests {
        use super::*;
    
        #[test]
        fn test_echo() {
            assert_eq!(echo("hello"), "hello");
        }
    
        #[test]
        fn test_first() {
            assert_eq!(first("a", "b"), "a");
        }
    
        #[test]
        fn test_processor() {
            let p = Processor::new("  test  ");
            assert_eq!(p.process(), "test");
        }
    }
    ✓ Tests Rust test suite
    #[cfg(test)]
    mod tests {
        use super::*;
    
        #[test]
        fn test_echo() {
            assert_eq!(echo("hello"), "hello");
        }
    
        #[test]
        fn test_first() {
            assert_eq!(first("a", "b"), "a");
        }
    
        #[test]
        fn test_processor() {
            let p = Processor::new("  test  ");
            assert_eq!(p.process(), "test");
        }
    }

    Deep Comparison

    OCaml vs Rust: lifetime input lifetime

    See example.rs and example.ml for implementations.

    Exercises

  • Three-input function: Write fn join3<'a>(sep: &str, a: &'a str, b: &'a str) -> String — note that sep does not appear in the output, so it does not need a named lifetime.
  • Input consumed by struct: Implement struct Parser<'src> { input: &'src str, pos: usize } where the 'src lifetime is set at construction and next<'parser>(&'parser mut self) -> Option<&'src str> returns slices of the original input.
  • Optional input: Write fn with_prefix<'a>(s: &'a str, prefix: Option<&str>) -> String that prepends prefix to s if present — observe that prefix needs no named lifetime since it does not appear in the output.
  • Open Source Repos