| |

Rust 43 ๐Ÿฆ€ Lifetime Annotations in Functions and Structs

The previous chapter introduced lifetimes as the compiler’s mechanism for preventing dangling references. This chapter is about the syntax and the patterns. The 'a annotation is not a runtime value and it is not a memory address. It is a name for a region of code, and the compiler uses it to reason about how long a reference is valid. The annotation appears in three places: on function signatures, on struct definitions, and on impl blocks. Each place has its own rules and its own common patterns, and the elision rules cover the majority of cases.

The function signature is where lifetimes are most visible. A function that takes one reference and returns one reference does not need an annotation: the elision rules assign the input’s lifetime to the output. A function that takes multiple references and returns a reference needs an annotation, because the compiler cannot know which input the output borrows from. The annotation is a contract: fn longest<'a>(x: &'a str, y: &'a str) -> &'a str says that the returned reference is valid for the lifetime that is the intersection of the two inputs. The caller relies on this, and the borrow checker enforces it.

The struct definition is where lifetimes become part of the type. A struct that owns its data has no lifetime parameter. A struct that borrows its data has a lifetime parameter for each distinct borrow. struct Excerpt<'a> { text: &'a str } says that the struct borrows a string, and the struct cannot outlive the string. The lifetime parameter is part of the struct’s type, so Excerpt<'a> and Excerpt<'b> are different types when 'a and 'b are different. The compiler tracks the relationship, and it rejects a program where the struct outlives the borrowed data.

The impl block is where the struct’s lifetime is used in methods. The impl<'a> Excerpt<'a> block declares the lifetime parameter, and the methods inside it take &self, which has the lifetime of the struct. The elision rules assign &self‘s lifetime to the returned reference, so a method that returns a reference from the struct’s field does not need an annotation. A method that takes an additional reference and returns a reference needs an annotation to say which lifetime the output borrows from.

This chapter covers three areas. First, the annotation syntax and the elision rules โ€” the 'a syntax, the three elision rules, and the cases where the annotation is required. Second, lifetime annotations in functions โ€” the single-input case, the multiple-input case, the 'static lifetime, and the patterns for returning references. Third, lifetime annotations in structs and impl blocks โ€” the struct definition, the impl block, the methods, and the interaction with generics. The chapter ends with a complete example session, a quick reference, best practices, common pitfalls, real-world examples, and diagrams showing the annotation’s role.

Key point: A lifetime annotation is a name for a region of code, not a runtime value. The elision rules infer the lifetimes for most functions: one lifetime per input, one input lifetime assigned to the output, and &self assigned to the output. The explicit annotation is required when a function takes multiple references and returns a reference, when a struct holds a reference, and when a method’s output borrows from a parameter other than &self.


Why lifetime annotations exist

The ambiguity problem. A function that takes one reference and returns one reference has an unambiguous lifetime: the output borrows from the input. A function that takes two references and returns one reference is ambiguous: the output could borrow from either input. The compiler cannot guess, so the developer must write the annotation. The annotation resolves the ambiguity by naming the relationship between the inputs and the output.

The contract problem. A function’s signature is a contract with the caller. The caller needs to know how long the returned reference is valid. The annotation is the way to express this. fn first<'a>(s: &'a str) -> &'a str says that the returned reference is valid for as long as the input reference is valid. The caller can rely on this, and the compiler enforces it. Without the annotation, the contract would be implicit, and the caller would have to read the function body to understand it.

The struct-type problem. A struct that holds a reference must declare the lifetime as a type parameter. The lifetime is part of the struct’s type, and it determines how long the struct can live. struct Excerpt<'a> { text: &'a str } says that the struct borrows a string with lifetime 'a, and the struct cannot outlive 'a. The compiler uses this information to verify that the struct is not used after the borrowed data is dropped.

The method-lifetime problem. A method on a struct with a lifetime parameter must declare the lifetime in the impl block. The &self parameter has the struct’s lifetime, and the elision rules assign it to the output. A method that takes an additional reference and returns a reference needs an annotation to say whether the output borrows from &self or from the parameter. The annotation is the way to express this.

The generic-interaction problem. A generic function can have both type parameters and lifetime parameters. The order matters: lifetimes come first, then types. fn f<'a, T>(x: &'a T) -> &'a T declares the lifetime 'a and the type T. The lifetime bound T: 'a says that the type T outlives the lifetime 'a, which is required when the function returns a reference to a T.

The trade-off. Lifetime annotations add noise to function signatures. A function with multiple references and a returned reference has an annotation that is not always obvious. The borrow checker rejects some programs that are actually safe, and the fix may require restructuring the code or adding a bound. The trade-off is between the safety guarantee and the expressiveness of the type system. For most programs, the elision rules keep the annotations minimal, and the annotations that are required are a small price for the absence of dangling references.


a. The annotation syntax and the elision rules

A lifetime annotation is written with an apostrophe and a name, conventionally a single lowercase letter starting with 'a. The annotation is attached to a reference with &'a T. The annotation can also appear as a bound on a generic parameter: T: 'a means that T outlives 'a.

fn first<'a>(s: &'a str) -> &'a str {
    &s[..1]
}

The function declares a lifetime parameter 'a in the angle brackets, uses it on the input reference, and uses it on the output reference. The contract is that the returned reference is valid for the same lifetime as the input.

The elision rules allow the annotation to be omitted in common cases. There are three rules:

  1. Each input reference gets its own lifetime parameter. A function with two input references has two lifetime parameters, even if they are not written.
  2. If there is exactly one input lifetime, it is assigned to all output lifetimes. A function with one input reference and one output reference has the same lifetime for both.
  3. If there is a &self or &mut self parameter, its lifetime is assigned to all output lifetimes. A method that returns a reference from &self does not need an annotation.

The first rule means that fn f(x: &str, y: &str) expands to fn f<'a, 'b>(x: &'a str, y: &'b str). The second rule means that fn f(x: &str) -> &str expands to fn f<'a>(x: &'a str) -> &'a str. The third rule means that a method fn get(&self) -> &str expands to fn get<'a>(&'a self) -> &'a str.

The annotation is required when none of the rules apply. A function with two input references and a returned reference has no rule that assigns the output’s lifetime, so the annotation is required.

fn longest<'a>(x: &'a str, y: &'a str) -> &'a str {
    if x.len() > y.len() { x } else { y }
}

The annotation says that both inputs and the output share the lifetime 'a. The returned reference is valid for the intersection of the two input lifetimes. The caller must ensure that both inputs are valid for as long as the returned reference is used.

A function can have multiple lifetime parameters with different bounds:

fn first<'a, 'b>(x: &'a str, y: &'b str) -> &'a str {
    x
}

The function returns a reference with the lifetime 'a, which is the lifetime of x. The y parameter has the lifetime 'b, which is unrelated to the output. The caller can pass a y with a shorter lifetime, and the returned reference is still valid for the lifetime of x.


b. Lifetime annotations in functions

The function signature is the most common place for lifetime annotations. The patterns fall into a few categories.

The single-input case. A function with one input reference and one output reference does not need an annotation, because the elision rules assign the input’s lifetime to the output.

fn first_word(s: &str) -> &str {
    s.split_whitespace().next().unwrap_or("")
}

The elided form is fn first_word<'a>(s: &'a str) -> &'a str. The function returns a reference that borrows from s, and the caller must ensure that s outlives the returned reference.

The multiple-input case. A function with multiple input references and a returned reference needs an annotation. The annotation says which input the output borrows from.

fn longest<'a>(x: &'a str, y: &'a str) -> &'a str {
    if x.len() > y.len() { x } else { y }
}

The annotation 'a is shared by both inputs and the output. The returned reference is valid for the shorter of the two input lifetimes. The caller must ensure that both inputs are valid for as long as the returned reference is used.

fn first<'a, 'b>(x: &'a str, y: &'b str) -> &'a str {
    x
}

The annotation 'a is on x and the output, and 'b is on y. The returned reference is valid for the lifetime of x only. The y parameter has an unrelated lifetime, and the caller can pass a y that does not outlive the returned reference.

The 'static case. A function that returns a 'static reference returns a reference that is valid for the entire program. String literals have the 'static lifetime.

fn greeting() -> &'static str {
    "hello"
}

A 'static reference can be used anywhere, because it is valid for the entire execution. The 'static lifetime is also used as a bound: T: 'static means that the type T contains no references with a shorter lifetime.

The method case. A method that takes &self and returns a reference does not need an annotation, because the elision rules assign &self‘s lifetime to the output.

impl Excerpt<'_> {
    fn text(&self) -> &str {
        self.text
    }
}

The elided form is fn text<'a>(&'a self) -> &'a str. The returned reference borrows from self, and the caller must ensure that the struct outlives the returned reference.

A method that takes an additional reference and returns a reference needs an annotation to say which lifetime the output borrows from:

impl<'a> Excerpt<'a> {
    fn longer<'b>(&'a self, other: &'b str) -> &'a str {
        if self.text.len() > other.len() {
            self.text
        } else {
            self.text
        }
    }
}

The method returns a reference with the lifetime 'a, which is the lifetime of self. The other parameter has the lifetime 'b, which is unrelated to the output. The caller can pass an other with a shorter lifetime.


c. Lifetime annotations in structs and impl blocks

A struct that holds a reference must declare a lifetime parameter. The parameter is used in the field’s type, and the struct’s lifetime is bounded by the referenced data.

struct Excerpt<'a> {
    text: &'a str,
}

The Excerpt struct holds a reference to a string with lifetime 'a. The struct cannot outlive the string. The compiler enforces this: the struct’s lifetime is bounded by 'a.

The struct is used with a concrete lifetime inferred from the context:

fn main() {
    let novel = String::from("Call me Ishmael. Some years ago...");
    let first_sentence = novel.split('.').next().unwrap();
    let excerpt = Excerpt { text: first_sentence };
    println!("{}", excerpt.text);
}

The excerpt variable borrows from novel. The compiler infers the lifetime 'a as the region of code where excerpt is used. The novel variable must be valid for that region, and the compiler verifies this.

An impl block on a struct with a lifetime must declare the lifetime parameter:

impl<'a> Excerpt<'a> {
    fn text(&self) -> &str {
        self.text
    }

    fn first_word(&self) -> &str {
        self.text.split_whitespace().next().unwrap_or("")
    }
}

The impl<'a> declares the lifetime parameter, and the methods inside the block use it. The &self parameter has the lifetime of the struct, and the elision rules assign it to the returned reference. The methods do not need an explicit annotation because the elision rules apply.

A struct can have multiple lifetime parameters if it holds references with different lifetimes:

struct Pair<'a, 'b> {
    first: &'a str,
    second: &'b str,
}

The Pair struct holds two references with potentially different lifetimes. The compiler tracks both and ensures that the struct does not outlive either.

A struct can have both lifetime parameters and type parameters:

struct Holder<'a, T> {
    value: &'a T,
}

The Holder struct holds a reference to a value of type T. The lifetime 'a is the lifetime of the reference, and T is the type of the value. The compiler tracks both. The bound T: 'a is implied when the struct holds a reference to T, because the reference cannot outlive the value.

The 'static bound on a struct means that the struct contains no references with a shorter lifetime:

struct Config {
    name: &'static str,
    version: &'static str,
}

The Config struct holds two string literals. The 'static lifetime says that the strings are valid for the entire program. The struct can be used anywhere.


Complete Example Session

// ============================================
// PART 1: A SINGLE-INPUT FUNCTION
// ============================================

fn first_word(s: &str) -> &str {
    s.split_whitespace().next().unwrap_or("")
}

fn main() {
    let text = String::from("hello world");
    let word = first_word(&text);
    println!("{}", word); // hello
}

// The elision rules assign the input's lifetime to the output.
// No annotation is needed.


// ============================================
// PART 2: A MULTIPLE-INPUT FUNCTION
// ============================================

fn longest<'a>(x: &'a str, y: &'a str) -> &'a str {
    if x.len() > y.len() { x } else { y }
}

fn main() {
    let s1 = String::from("long string");
    let s2 = String::from("short");
    let result = longest(&s1, &s2);
    println!("{}", result); // long string
}

// The annotation 'a is shared by both inputs and the output.
// The returned reference is valid for the shorter lifetime.


// ============================================
// PART 3: MULTIPLE LIFETIME PARAMETERS
// ============================================

fn first<'a, 'b>(x: &'a str, y: &'b str) -> &'a str {
    x
}

fn main() {
    let s1 = String::from("first");
    let s2 = String::from("second");
    let result = first(&s1, &s2);
    println!("{}", result); // first
}

// The output borrows from x only.
// y has an unrelated lifetime 'b.


// ============================================
// PART 4: THE 'static LIFETIME
// ============================================

fn greeting() -> &'static str {
    "hello"
}

fn main() {
    let s = greeting();
    println!("{}", s); // hello
}

// String literals have the 'static lifetime.
// The returned reference is valid for the entire program.


// ============================================
// PART 5: A STRUCT WITH A LIFETIME
// ============================================

struct Excerpt<'a> {
    text: &'a str,
}

fn main() {
    let novel = String::from("Call me Ishmael. Some years ago...");
    let first_sentence = novel.split('.').next().unwrap();
    let excerpt = Excerpt { text: first_sentence };
    println!("{}", excerpt.text);
}

// The Excerpt struct borrows from novel.
// The struct cannot outlive novel.


// ============================================
// PART 6: AN IMPL BLOCK WITH A LIFETIME
// ============================================

impl<'a> Excerpt<'a> {
    fn text(&self) -> &str {
        self.text
    }

    fn first_word(&self) -> &str {
        self.text.split_whitespace().next().unwrap_or("")
    }
}

// The impl block declares the lifetime parameter.
// The methods take &self and return &str.
// The elision rules assign &self's lifetime to the output.


// ============================================
// PART 7: A METHOD WITH AN ADDITIONAL LIFETIME
// ============================================

impl<'a> Excerpt<'a> {
    fn longer<'b>(&'a self, other: &'b str) -> &'a str {
        if self.text.len() > other.len() {
            self.text
        } else {
            self.text
        }
    }
}

// The method returns a reference with the lifetime 'a,
// which is the lifetime of self.
// The other parameter has the lifetime 'b, unrelated to the output.


// ============================================
// PART 8: MULTIPLE LIFETIMES IN A STRUCT
// ============================================

struct Pair<'a, 'b> {
    first: &'a str,
    second: &'b str,
}

fn main() {
    let s1 = String::from("hello");
    let s2 = String::from("world");
    let pair = Pair { first: &s1, second: &s2 };
    println!("{} {}", pair.first, pair.second);
}

// The Pair struct holds two references with different lifetimes.
// The compiler tracks both.


// ============================================
// PART 9: A STRUCT WITH A LIFETIME AND A TYPE
// ============================================

struct Holder<'a, T> {
    value: &'a T,
}

fn main() {
    let x = 42;
    let holder = Holder { value: &x };
    println!("{}", holder.value);
}

// The Holder struct holds a reference to a value of type T.
// The lifetime 'a is the lifetime of the reference.
// The type T is the type of the value.
// The bound T: 'a is implied.


// ============================================
// PART 10: THE COMPLETE EXAMPLE
// ============================================

struct Parser<'a> {
    input: &'a str,
    position: usize,
}

impl<'a> Parser<'a> {
    fn new(input: &'a str) -> Self {
        Parser { input, position: 0 }
    }

    fn rest(&self) -> &'a str {
        &self.input[self.position..]
    }

    fn peek(&self) -> Option<char> {
        self.rest().chars().next()
    }

    fn advance(&mut self) -> Option<char> {
        let c = self.peek()?;
        self.position += c.len_utf8();
        Some(c)
    }
}

fn main() {
    let input = String::from("hello world");
    let mut parser = Parser::new(&input);

    while let Some(c) = parser.advance() {
        print!("{}", c);
    }
    println!();
}

// The Parser struct holds a reference to the input.
// The rest method returns a reference with the input's lifetime,
// not &self's lifetime, because the slice borrows from the input.
// The advance method takes &mut self and does not return a reference.

The ten parts show a single-input function, a multiple-input function, multiple lifetime parameters, the 'static lifetime, a struct with a lifetime, an impl block with a lifetime, a method with an additional lifetime, multiple lifetimes in a struct, a struct with a lifetime and a type, and the complete example.


Quick Reference

Annotation Syntax

FormMeaning
<'a>Declare a lifetime parameter
&'a TA reference with lifetime 'a
T: 'aThe type T outlives 'a
&'a mut TA mutable reference with lifetime 'a
'staticThe lifetime of the entire program

The Elision Rules

RuleEffect
1Each input reference gets its own lifetime
2One input lifetime โ†’ assigned to the output
3&self lifetime โ†’ assigned to the output

Function Signatures

SignatureAnnotation needed
fn f(x: &str) -> &strNo (rule 2)
fn f(x: &str, y: &str) -> &strYes (ambiguous)
fn f<'a>(x: &'a str, y: &'a str) -> &'a strExplicit
fn f<'a, 'b>(x: &'a str, y: &'b str) -> &'a strExplicit, different lifetimes
fn f() -> &'static str'static

Struct and Impl

FormMeaning
struct S<'a> { f: &'a T }A struct with a lifetime
impl<'a> S<'a> { ... }An impl block with a lifetime
impl S<'_> { ... }Anonymous lifetime in the impl
struct S<'a, T> { f: &'a T }Lifetime and type parameters
struct S { f: &'static str }A 'static field

Best Practices

โœ… Do This:

// Let the elision rules handle simple cases
fn first(s: &str) -> &str { &s[..1] }                                     // โœ…
// Add annotations for multiple inputs
fn longest<'a>(x: &'a str, y: &'a str) -> &'a str { ... }                 // โœ…
// Use separate lifetimes when the output borrows from one input
fn first<'a, 'b>(x: &'a str, y: &'b str) -> &'a str { x }                // โœ…
// Declare the lifetime in a struct that borrows
struct Excerpt<'a> { text: &'a str }                                      // โœ…
// Use the lifetime in the impl block
impl<'a> Excerpt<'a> { fn text(&self) -> &str { self.text } }             // โœ…
// Use 'static for string literals
fn greeting() -> &'static str { "hello" }                                 // โœ…

โŒ Don’t Do This:

// Don't omit the annotation with multiple inputs
fn longest(x: &str, y: &str) -> &str { ... } // missing lifetime           // โŒ
// Don't use the same lifetime when the output borrows from one input
fn first<'a>(x: &'a str, y: &'a str) -> &'a str { x } // too restrictive   // โœ… (works but over-constrains)
// Don't forget the lifetime in the impl block
impl Excerpt<'_> { ... } // anonymous lifetime is fine in modern Rust       // โœ…
// Don't return a reference to a local
fn dangling() -> &str { let s = String::new(); &s }                        // โŒ
// Don't use 'static when a shorter lifetime suffices
fn f(s: &str) -> &'static str { s } // does not compile                    // โŒ

Common Pitfalls

PitfallWhy It HappensFix
Missing lifetime specifierMultiple inputs, returned referenceAdd the annotation
Borrowed value does not live long enoughReference outlives the dataExtend the scope or own the data
Over-constraining lifetimesSame lifetime for all inputsUse separate lifetimes
Struct outlives the dataThe struct’s lifetime is longerDeclare the data in an outer scope
Method returns the wrong lifetimeOutput borrows from a parameterAnnotate the method
'static bound not satisfiedBorrowed data escapesMove the data into the closure
Impl block missing the lifetimeThe struct has a lifetime parameterAdd impl<'a> S<'a>

Real-World Examples

1. Single-Input Function

fn first_word(s: &str) -> &str { s.split_whitespace().next().unwrap_or("") }

2. Multiple-Input Function

fn longest<'a>(x: &'a str, y: &'a str) -> &'a str { ... }

3. Separate Lifetimes

fn first<'a, 'b>(x: &'a str, y: &'b str) -> &'a str { x }

4. ‘static

fn greeting() -> &'static str { "hello" }

5. Struct with a Lifetime

struct Excerpt<'a> { text: &'a str }

6. Impl Block

impl<'a> Excerpt<'a> { fn text(&self) -> &str { self.text } }

7. Method with an Additional Lifetime

impl<'a> Excerpt<'a> { fn longer<'b>(&'a self, other: &'b str) -> &'a str { self.text } }

8. Multiple Lifetimes in a Struct

struct Pair<'a, 'b> { first: &'a str, second: &'b str }

9. Lifetime and Type Parameters

struct Holder<'a, T> { value: &'a T }

10. Complete Parser

struct Parser<'a> { input: &'a str, position: usize }
impl<'a> Parser<'a> { fn rest(&self) -> &'a str { ... } }

Visual

The Elision Rules

โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚  THE ELISION RULES                                           โ”‚
โ”‚                                                              โ”‚
โ”‚  RULE 1: Each input reference gets its own lifetime.         โ”‚
โ”‚  โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”    โ”‚
โ”‚  โ”‚  fn f(x: &str, y: &str)                              โ”‚    โ”‚
โ”‚  โ”‚  โ†’ fn f<'a, 'b>(x: &'a str, y: &'b str)              โ”‚    โ”‚
โ”‚  โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜    โ”‚
โ”‚                                                              โ”‚
โ”‚  RULE 2: One input lifetime โ†’ assigned to the output.        โ”‚
โ”‚  โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”    โ”‚
โ”‚  โ”‚  fn f(x: &str) -> &str                               โ”‚    โ”‚
โ”‚  โ”‚  โ†’ fn f<'a>(x: &'a str) -> &'a str                   โ”‚    โ”‚
โ”‚  โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜    โ”‚
โ”‚                                                              โ”‚
โ”‚  RULE 3: &self lifetime โ†’ assigned to the output.            โ”‚
โ”‚  โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”    โ”‚
โ”‚  โ”‚  impl S { fn m(&self) -> &str }                      โ”‚    โ”‚
โ”‚  โ”‚  โ†’ impl S { fn m<'a>(&'a self) -> &'a str }          โ”‚    โ”‚
โ”‚  โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜    โ”‚
โ”‚                                                              โ”‚
โ”‚  โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”    โ”‚
โ”‚  โ”‚  AMBIGUOUS: multiple inputs, returned reference      โ”‚    โ”‚
โ”‚  โ”‚  fn f(x: &str, y: &str) -> &str                      โ”‚    โ”‚
โ”‚  โ”‚    โ†’ annotation REQUIRED                             โ”‚    โ”‚
โ”‚  โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜    โ”‚
โ”‚                                                              โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜

A Function with Multiple Inputs

โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚  A FUNCTION WITH MULTIPLE INPUTS                             โ”‚
โ”‚                                                              โ”‚
โ”‚  fn longest<'a>(x: &'a str, y: &'a str) -> &'a str {         โ”‚
โ”‚    if x.len() > y.len() { x } else { y }                     โ”‚
โ”‚  }                                                           โ”‚
โ”‚                                                              โ”‚
โ”‚  โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”    โ”‚
โ”‚  โ”‚  'a: the shared lifetime of both inputs and the outputโ”‚   โ”‚
โ”‚  โ”‚                                                       โ”‚    โ”‚
โ”‚  โ”‚  โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”                                    โ”‚    โ”‚
โ”‚  โ”‚  โ”‚  x: &'a str  โ”‚ โ”€โ”€โ”                                โ”‚    โ”‚
โ”‚  โ”‚  โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜   โ”‚                                โ”‚    โ”‚
โ”‚  โ”‚                     โ”œโ”€โ”€โ–บ return: &'a str             โ”‚    โ”‚
โ”‚  โ”‚  โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”   โ”‚                                โ”‚    โ”‚
โ”‚  โ”‚  โ”‚  y: &'a str  โ”‚ โ”€โ”€โ”˜                                โ”‚    โ”‚
โ”‚  โ”‚  โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜                                    โ”‚    โ”‚
โ”‚  โ”‚                                                       โ”‚    โ”‚
โ”‚  โ”‚  The returned reference is valid for the SHORTER     โ”‚    โ”‚
โ”‚  โ”‚  of the two input lifetimes.                         โ”‚    โ”‚
โ”‚  โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜    โ”‚
โ”‚                                                              โ”‚
โ”‚  Caller:                                                     โ”‚
โ”‚  โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”    โ”‚
โ”‚  โ”‚  let s1 = String::from("long string");               โ”‚    โ”‚
โ”‚  โ”‚  let result;                                         โ”‚    โ”‚
โ”‚  โ”‚  {                                                   โ”‚    โ”‚
โ”‚  โ”‚    let s2 = String::from("short");                   โ”‚    โ”‚
โ”‚  โ”‚    result = longest(&s1, &s2);                       โ”‚    โ”‚
โ”‚  โ”‚  }                                                   โ”‚    โ”‚
โ”‚  โ”‚  println!("{}", result);  โ† ERROR: s2 is dropped     โ”‚    โ”‚
โ”‚  โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜    โ”‚
โ”‚                                                              โ”‚
โ”‚  The borrow checker rejects the program because the          โ”‚
โ”‚  returned reference would outlive s2.                        โ”‚
โ”‚                                                              โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜

A Struct with a Lifetime

โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚  A STRUCT WITH A LIFETIME                                    โ”‚
โ”‚                                                              โ”‚
โ”‚  struct Excerpt<'a> {                                        โ”‚
โ”‚    text: &'a str,                                            โ”‚
โ”‚  }                                                           โ”‚
โ”‚                                                              โ”‚
โ”‚  โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”    โ”‚
โ”‚  โ”‚  'a: the lifetime of the borrowed string             โ”‚    โ”‚
โ”‚  โ”‚                                                       โ”‚    โ”‚
โ”‚  โ”‚  The struct cannot outlive the string.               โ”‚    โ”‚
โ”‚  โ”‚  The borrow checker enforces this.                   โ”‚    โ”‚
โ”‚  โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜    โ”‚
โ”‚                                                              โ”‚
โ”‚  impl<'a> Excerpt<'a> {                                      โ”‚
โ”‚    fn text(&self) -> &str {                                  โ”‚
โ”‚      self.text                                               โ”‚
โ”‚    }                                                         โ”‚
โ”‚  }                                                           โ”‚
โ”‚                                                              โ”‚
โ”‚  โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”    โ”‚
โ”‚  โ”‚  The impl block declares the lifetime parameter.     โ”‚    โ”‚
โ”‚  โ”‚  The &self parameter has the lifetime of the struct. โ”‚    โ”‚
โ”‚  โ”‚  The elision rules assign it to the output.          โ”‚    โ”‚
โ”‚  โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜    โ”‚
โ”‚                                                              โ”‚
โ”‚  โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”    โ”‚
โ”‚  โ”‚  let novel = String::from("Call me Ishmael...");     โ”‚    โ”‚
โ”‚  โ”‚  let first = novel.split('.').next().unwrap();       โ”‚    โ”‚
โ”‚  โ”‚  let excerpt = Excerpt { text: first };              โ”‚    โ”‚
โ”‚  โ”‚                                                       โ”‚    โ”‚
โ”‚  โ”‚  novel:   โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ–บ    โ”‚    โ”‚
โ”‚  โ”‚  excerpt: โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ–บ                          โ”‚    โ”‚
โ”‚  โ”‚                                                       โ”‚    โ”‚
โ”‚  โ”‚  excerpt is dropped before novel.                    โ”‚    โ”‚
โ”‚  โ”‚  The program compiles.                               โ”‚    โ”‚
โ”‚  โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜    โ”‚
โ”‚                                                              โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜

A Method with an Additional Lifetime

โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚  A METHOD WITH AN ADDITIONAL LIFETIME                        โ”‚
โ”‚                                                              โ”‚
โ”‚  impl<'a> Excerpt<'a> {                                      โ”‚
โ”‚    fn longer<'b>(&'a self, other: &'b str) -> &'a str {      โ”‚
โ”‚      if self.text.len() > other.len() {                      โ”‚
โ”‚        self.text                                             โ”‚
โ”‚      } else {                                                โ”‚
โ”‚        self.text                                             โ”‚
โ”‚      }                                                       โ”‚
โ”‚    }                                                         โ”‚
โ”‚  }                                                           โ”‚
โ”‚                                                              โ”‚
โ”‚  โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”    โ”‚
โ”‚  โ”‚  'a: the lifetime of self and the output             โ”‚    โ”‚
โ”‚  โ”‚  'b: the lifetime of the other parameter             โ”‚    โ”‚
โ”‚  โ”‚                                                       โ”‚    โ”‚
โ”‚  โ”‚  The output borrows from self, not from other.       โ”‚    โ”‚
โ”‚  โ”‚  The caller can pass an other with a shorter         โ”‚    โ”‚
โ”‚  โ”‚  lifetime, and the returned reference is still       โ”‚    โ”‚
โ”‚  โ”‚  valid for the lifetime of self.                     โ”‚    โ”‚
โ”‚  โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜    โ”‚
โ”‚                                                              โ”‚
โ”‚  โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”    โ”‚
โ”‚  โ”‚  let excerpt = Excerpt { text: "hello" };            โ”‚    โ”‚
โ”‚  โ”‚  let result = excerpt.longer("hi");                  โ”‚    โ”‚
โ”‚  โ”‚  println!("{}", result);  โ† OK                       โ”‚    โ”‚
โ”‚  โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜    โ”‚
โ”‚                                                              โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜

Summary

ItemValue
Lifetime annotation'a, a name for a region of code
Elision rule 1Each input reference gets its own lifetime
Elision rule 2One input lifetime โ†’ assigned to the output
Elision rule 3&self lifetime โ†’ assigned to the output
Annotation requiredMultiple inputs, returned reference
Struct lifetimestruct S<'a> { f: &'a T }
Impl lifetimeimpl<'a> S<'a> { ... }
'staticThe lifetime of the entire program
Lifetime boundT: 'a
Multiple lifetimesSeparate parameters for unrelated borrows

Key takeaways:

  • A lifetime annotation is a name for a region of code, not a runtime value. The 'a syntax names the region, and the compiler uses it to reason about how long a reference is valid.
  • The elision rules infer the lifetimes for most functions. The rules are: one lifetime per input, one input lifetime assigned to the output, and &self assigned to the output. The explicit annotation is only needed when the elision is ambiguous.
  • The annotation is required when a function takes multiple references and returns a reference. The compiler cannot know which input the output borrows from, so the developer must write the annotation to resolve the ambiguity.
  • A struct that holds a reference must declare a lifetime parameter. The lifetime is part of the struct’s type, and it bounds the struct’s lifetime to the referenced data.
  • An impl block on a struct with a lifetime must declare the lifetime parameter. The &self parameter has the struct’s lifetime, and the elision rules assign it to the output.
  • A method that returns a reference from a parameter other than &self needs an annotation. The annotation says which lifetime the output borrows from, and it allows the caller to pass arguments with different lifetimes.
  • The 'static lifetime is the lifetime of the entire program. String literals have the 'static lifetime. A T: 'static bound means that the type contains no references with a shorter lifetime.
  • Separate lifetimes are less restrictive than a shared lifetime. A function that returns a reference from one input should use a separate lifetime for the other input, so the caller is not required to make both inputs live equally long.

Remember: Lifetime annotations are the syntax for naming the regions of code over which references are valid. The elision rules infer the lifetimes for most functions, and the explicit annotation is only needed when the elision is ambiguous. A struct that borrows its data declares a lifetime parameter, and an impl block on the struct declares the same parameter. The 'static lifetime is the lifetime of the entire program, and a T: 'static bound means that the type contains no shorter-lived references. The result is a system where the compiler verifies that every reference is valid, and the developer only writes the annotations that the elision rules cannot infer.



Stop using slow, ad-bloated tool sites! ๐Ÿคฎ

๐Ÿ”Ž Search “KandZ Tools” on Google to use many professional utilities for free.

KandZ.me is the ultimate minimalist hub for:
โœ… Finance (Mortgage, Interest, Inflation)
โœ… Tech (Base64, JSON, Dev Suite, IP)
โœ… Health (BMI, BMR, TDEE)
โœ… Productivity (Timer, Workspace, QR)

โšก๏ธ Fast & Private
๐Ÿ”’ No data leaves your device
๐Ÿ’Ž 100% Free

๐Ÿ”— Use it now: https://tools.kandz.me
๐Ÿ”– Bookmark itโ€”youโ€™ll need it later!