DEV Community

Rust lifetimes, a high wall for Rust newbies

TakaakiFuruse on January 03, 2019

Learning Rust is hard I recently start learning Rust. The language is famous but at the same time is infamous. Everybody says it has ste...
Collapse
 
barronli profile image
Barronli

The two lifetimes are necessary because you want the struct field to outlive the containing struct. This cannot be achieved with single lifetime for both the struct and its field.

In this function:

fn parse_context<'a>(context: Context<'a>) -> Result<(), &'a str> {
    Parser { context: &context }.parse()
}

It returns a reference &str that is a field (of a field) of Parser, while the Parser only exists within the function body. In order to make it work, the field of Parser has to borrow a value that has the same lifetime as the return value. That is, as long as the returned value &str has the same lifetime as the input argument context, it does not matter how long the Parser struct lives. In other words, the lifetimes of Parser and context are independent - that is why they have two lifetimes in the struct definition.

struct Parser<'c, 's> {
    context: &'c Context<'s>,
}

To understand it in another way, you can consider the struct constructor of Parser as a new() function, whose input argument has different lifetime than it self's, which is common in function definitions.

impl<'a, 's> Parser<'a, 's> {
    //the argument context has lifetime 's and is borrowed for lifetime 'a
    fn new(context: &'a Context<'s>)->Parser<'a, 's>{
        Parser{context}
    }
    fn parse(&'a self) -> Result<(), &'s str> {
        Err(&self.context.0[1..])
    }
}

fn parse_context<'a>(context: Context<'a>) -> Result<(), &'a str> {
    Parser::new(&context).parse()   //Parser only lives within this scope
}

In this way, it is like that you have an input argument passed to Parser "function", and returns the same argument data. The argument's lifetime has nothing to do with the Parser "function".

See the working version in Playground.

Collapse
 
bretthancox profile image
bretthancox

Love this article and this additional information is great. Thank you for adding it.

I can deal with most of Rust's idiosyncrasies, but with lifetimes I feel like the compiler leads me by the nose. I do as I'm told without knowing and I hate doing that.

Collapse
 
saveriomiroddi profile image
Saverio Miroddi

I'm confused by this article, because the parse_context() function is improper - the version presented consumes the context object, but it borrows it inside. This doesn't make sense :-)

The proper version of parse_context() borrows the context, and doesn't require two lifetimes:

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

struct Parser<'a> {
    context: &'a Context<'a>,
}

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

fn parse_context<'a>(context: &'a Context) -> &'a str {
    Parser { context }.parse()
}
Enter fullscreen mode Exit fullscreen mode

Now, without knowing the... context (no pun intended!), I can't be sure if this was intended, or if it's been an accident.

It's possible that this was a contrived example for the sake of understanding (which is fair, as a matter of fact, I do find this interesting), however, based on the mutability of the fields and the operations (all immutable), it seems to me that this was an accident.

Collapse
 
serak profile image
Serak Shiferaw

2 question here, if the compiler knows i should add lifetime at specified points why not just do it itself. instead of telling me to add 'a everywhere the other is why didnt rust used the old c style &str

e.g

[allow(unused_variables)]

fn main() {
let string1 = String::from("abcd");
let string2 = "xyz";

let result = longest(&string1.as_str(), &string2); //notice & to pass by reference and enforce the compiler to pass lifetime to the called function 

}

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

Collapse
 
eribol profile image
eribol

How about loops? How can we use lifetimes with loops?

Collapse
 
bretthancox profile image
bretthancox

Bravo on the helpful article.

Also, unicorn emoji for this line: "If they go out boundries, compiler kills them, just like a prison."