// Example 106: Lifetime Elision Rules
//
// Rust has three elision rules that let you skip lifetime annotations:
// 1. Each input reference gets its own lifetime
// 2. If exactly one input lifetime, output gets that lifetime
// 3. If &self or &mut self, output gets self's lifetime
// Approach 1: Single input โ elision applies (rule 2)
// fn first_word<'a>(s: &'a str) -> &'a str โ what compiler infers
fn first_word(s: &str) -> &str {
s.split_whitespace().next().unwrap_or(s)
}
fn approach1() {
let word = first_word("hello world");
assert_eq!(word, "hello");
println!("First word: {}", word);
}
// Approach 2: Method with &self โ elision applies (rule 3)
struct TextBuffer {
content: String,
}
impl TextBuffer {
// fn get_content<'a>(&'a self) -> &'a str โ inferred
fn get_content(&self) -> &str {
&self.content
}
fn get_length(&self) -> usize {
self.content.len()
}
}
fn approach2() {
let buf = TextBuffer { content: "Hello, World!".into() };
let c = buf.get_content();
let l = buf.get_length();
assert_eq!(l, 13);
println!("Content: {}, Length: {}", c, l);
}
// Approach 3: Multiple inputs โ MUST annotate (no elision)
fn pick_longer<'a>(a: &'a str, b: &'a str) -> &'a str {
if a.len() >= b.len() { a } else { b }
}
fn approach3() {
let result = pick_longer("hello", "hi");
assert_eq!(result, "hello");
println!("Longer: {}", result);
}
fn main() {
println!("=== Approach 1: Single Input (Elided) ===");
approach1();
println!("\n=== Approach 2: Method &self (Elided) ===");
approach2();
println!("\n=== Approach 3: Multiple Inputs (Must Annotate) ===");
approach3();
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_first_word() {
assert_eq!(first_word("hello world"), "hello");
assert_eq!(first_word("single"), "single");
assert_eq!(first_word(""), "");
}
#[test]
fn test_text_buffer() {
let buf = TextBuffer { content: "test".into() };
assert_eq!(buf.get_content(), "test");
assert_eq!(buf.get_length(), 4);
}
#[test]
fn test_pick_longer() {
assert_eq!(pick_longer("abc", "ab"), "abc");
assert_eq!(pick_longer("x", "yy"), "yy");
}
#[test]
fn test_elision_with_owned_param() {
// No lifetime needed: input is owned, output is owned
fn to_upper(s: &str) -> String {
s.to_uppercase()
}
assert_eq!(to_upper("hi"), "HI");
}
}
(* Example 106: Lifetime Elision โ When You Don't Need to Annotate *)
(* OCaml never needs lifetime annotations. This example shows the
patterns where Rust also skips them thanks to elision rules. *)
(* Approach 1: Single input reference โ output borrows from it *)
let first_word s =
match String.index_opt s ' ' with
| Some i -> String.sub s 0 i
| None -> s
let approach1 () =
let word = first_word "hello world" in
assert (word = "hello");
Printf.printf "First word: %s\n" word
(* Approach 2: Method-style โ self is the obvious source *)
type text_buffer = { content : string }
let get_content buf = buf.content
let get_length buf = String.length buf.content
let approach2 () =
let buf = { content = "Hello, World!" } in
let c = get_content buf in
let l = get_length buf in
assert (l = 13);
Printf.printf "Content: %s, Length: %d\n" c l
(* Approach 3: Multiple inputs โ OCaml doesn't care *)
let pick_longer a b =
if String.length a >= String.length b then a else b
let approach3 () =
let result = pick_longer "hello" "hi" in
assert (result = "hello");
Printf.printf "Longer: %s\n" result
let () =
approach1 ();
approach2 ();
approach3 ();
Printf.printf "โ All tests passed\n"