Named Return Lifetimes
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
'out and 'input clarify which input a reference comes fromprefer_first<'a, 'b>(x: &'a str, _y: &'b str) -> &'a str documents the output sourceParser<'input> uses a named lifetime to tie parsed output to the input buffer'input, 'source, 'out) helps readabilityCode 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
'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<'input> enforces that parsed results cannot outlive the input buffer; OCaml parsers hold GC-managed strings with no lifetime constraint.-> &'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");
}
}#[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
Exercises
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.fn choose<'long: 'short, 'short>(cond: bool, a: &'long str, b: &'short str) -> &'short str — explain why 'long: 'short is needed here.'input, 'key, 'value) instead of 'a/'b — assess whether readability improves.