Lifetimes in Rust, Why and How

Lifetimes in Rust, Why and How

What is lifetime

Lifetimes tells how long references would be valid, ensuring memory safety without a garbage collector.

Does not compile

fn longest (x:& str, y:& str) -> & str{
    if x.len() > y.len() {
        x
    } else {
        y
    }
}
fn main() {
    let string1 = String::from("Hi");
    {
        let string2 = String::from("Hello");
        let result = longest (string1.as_str(), string2.as_str());
        println! ("{result}");
    }
}

$ cargo build
3 | fn longest(x:& str, y:& str) -> & str{
    |              -----    -----     ^ expected named lifetime parameter
    |
    = help: this function's return type contains a borrowed value, but 
    the signature does not say whether it is borrowed from `x` or `y`
  help: consider introducing a named lifetime parameter        

Why compliation error? Compiler is unable to infer the lifetimes of parameters

Compiles (With Lifetime Annotation)

Lifetime? Prefix every parameter with ‘k. Apostrophe (‘k), denotes reference has generic lifetime.

fn longest <'k> (x:&'k str, y:&'k str) -> &'k str{
    if x.len() > y.len() {
        x
    } else {
        y
    }
}
fn main() {
    let string1 = String::from("Hi");
    {
        let string2 = String::from("Hello ");
        let result = longest (string1.as_str(), string2.as_str());
        println! ("{result}");
    }
}

$ cargo run
Hello        

How this worked? Rust can’t tell whether the reference being returned refers to x or y Borrow checker can’t determine lifetime of return value will relate to x or y. With changed function signature, We tell compiler, returned reference will be valid as long as both the parameters are valid.

Lifetime in struct

Why we need lifetime in struct?

if we want to store reference in struct, ie struct does not hold Owned types.

Will not compile

struct A {
    x:&str
}
fn main() {
    let m = String::from("test");
    let o = A {
        x : &m,
    };
    println!("{}", o.x);
}
$ cargo run
missing lifetime specifier
--> src/main.rs:6:7
6 |     x:&str
  |       ^ expected named lifetime parameter        

Compiles (With Lifetime)

struct A <'a> {
    x : &'a str,
}
fn main() {
    let m = String::from("test");
    let o = A {
        x : &m,
    };
    println!("{}", o.x);
}
$ cargo run
test        

Lifetime Ellision

The patterns programmed into Rust complier which applies lifetime rules in desired situtation automatically. These are not rules for programmers to follow; they’re a set of particular cases that the compiler will consider, and if your code fits these cases, you don’t need to write the lifetimes explicitly. Input Lifetime: Lifetimes on function or method parameters Output lifetimes: lifetimes on return values

3 rules to figure out the lifetimes

Rule-1(applies to input lifetimes)

compiler assigns a lifetime parameter to each parameter that’s a reference. 1 parameter gets one lifetime parameter, 2 parameters gets 3 separate lifetime parameters & so on

                fn foo (x: &i32); becomes
                fn foo <'a>(x: &'a i32);

                fn foo (x:&i32, y:&i32) becomes
                fn foo <'a, 'b>(x: &'a i32, y: &'b i32)        

Rule-2(applies to input lifetimes)

if there is exactly one input lifetime parameter, that lifetime is assigned to all output lifetime parameters.

fn foo <'a>(x: &'a i32) -> &'a i32        

Rule-3

if there are multiple input lifetime parameters, but one of them is &self or &mut self, lifetime of self is assigned to all output lifetime parameters.

要查看或添加评论,请登录

社区洞察

其他会员也浏览了