Vector lifetime rust

fn understanding_vec_lifetime(path: &str) {
let mut header = vec![];
let mut sequence = vec![];
let f = File::open(&path).expect("file not present");
let read = BufReader::new(f);
for i in read.lines() {
    let line = i
               .expect("line not present");
	if line.starts_with("A") {
        header.push(&line)
    }
    if !line.starts_with("A") {
        sequence.push(&line)
    } 
    println!("{:?}", header)
}
}

Now here it gives that the line doesnt live long enough?. Any guess or articles on illustrating the lifetimes in RUST. How to access this as a lifetime across all the function.

How to access the vec declared outside the function scope and push to the vector from the inner scope.

I started RUST 2 week ago but grepping it fast.

The fundamental issue here is that line is a temporary variable that gets dropped and recreated each loop iteration. This means that you can’t let any references to it escape the loop body— They’d then be pointing to memory that’s been freed/reused.

The most straightforward solution is to move the value into the vector instead of trying to store a reference:

fn understanding_vec_lifetime(path: &str) {
    let mut header = vec![];
    let mut sequence = vec![];
    let f = File::open(&path).expect("file not present");
    let read = BufReader::new(f);
    for i in read.lines() {
        let line = i.expect("line not present");
        if line.starts_with("A") {
            header.push(line)
        } else {
            sequence.push(line)
        }
        println!("{:?}", header)
    }
}
4 Likes

This i already know and have used it. I want to know that does RUST allows the variables outside the scope of the function to be used as with in the inner scope. I know that you can do shadowing but vec doesnt allow for the shadowing.

Another point that can be done is that since Vec doesnt have the copy trait, you can give the variable outside the function and then use the clone() to access the memory bound with in the function but then also it remains the same.

Any way that we can associate a lifetime and then access the outscope with in an innerscope.

Gaurav

I'm not sure I understand what you're asking exactly. However: In Rust's ownership system values that go out of scope are dropped -- cannot be used anymore. Holding onto references to the dropped values is one such "use". That is, being borrowed by a reference is incompatible with being dropped, which is what the error in the OP was about.

The way to prevent drops is to move the value into something with a larger scope -- pushing the owned value into the Vec, for example. Pushing a borrow of the value -- &line -- into the Vec does not prevent the value from dropping. Rust references (&_, &mut _) are about borrowing, not keeping things alive.

The lifetimes of references -- those 'a things -- are about the duration of the borrow, and not how long the borrowed value lives.[1] The overlap of terminology is unfortunate.


  1. Even though it's limited by when the value drops. ↩︎

5 Likes

That is true and thank you for this discussion. by declaring the 'a or the 'b:'a subtyping the borrowed value, you can actually declare the value to let it live for the life time. I am completely aware of this as this will allow the owner lifetime and the member lifetime who is borrowing the memory.

if you use CONST then it automatically assign a static lifetime to the variable but that doesnt hold in case of the Vec as vec dont borrow the copy trait.

So i was basically trying new ways as if i can impl with the <T + other methods + send > and also implement sync and then use them as await for the asynchronous programming.

Thank you for this discussion and it was good to know that this is not implemented but this discussion is worth.

Gaurav

That can make one borrow at least as long as another, but can't extend the drop scope of the borrowed value.

(Async and the rest is far beyond this thread so far.)

How about if we give he Vec<'a> will that take the lifetime over the entire array or we give a enum with in the Vector as Vec<'a<struct add{
where T rest of the code
}>

and can we type cast this as shadowing lifetime.

Gaurav

Sorry, I don't understand.

Lets say that we want to give a explicit lifetime to a vector so we can give Vec<'a<u32>> and in this the vector will import the lifetime which we can subtype to the function as fn <'a> so the output will have the same lifetime as the function.

Can we give a struct with in the Vec as with the same lifetime as

struct add {
   val: String, 
   vec: Vec<'a<&str>,
}
Vec<'a<add>>

in this way the vector with in the struct is also getting the same lifetime as the Vector itself and we dont need to declare a new lifetime for the same.

Gaurav

Neither of these two lines is valid Rust syntax.

This tells me that you probably misunderstand ownership, since ownership of the Strings is transferred in this function. Lifetime annotations are related to borrowing, and transfer of ownership (moving) is not borrowing.

I suggest studying the Rust book, particularly the parts on ownership, lifetimes, and generics. Working through exercises, in the book or in other materials, can also help.

2 Likes

Lifetimes are descriptive, not prescriptive. That is, they describe what your code is actually doing, but they cannot change what your code is doing. "Giving your vector a lfietime" is not a thing in Rust; a lifetime annotation can only confirm that your Vec does or does not have the required lifetime.

In your code, the lifetime of line begins at let line = /* expression */ and ends immediately after printlin!, before the end of the body of the loop. Any references to line must be dropped before that happens.

But your code stores references to line in either header or sequence, both of which exist for effectively the entire body of understanding_vec_lifetime - a lifetime which is longer than line.

The compiler is, quite accurately, telling you that it is impossible to correctly store a reference to a short-lived value (line) in a long-lived container (header, sequence). Changing the annotations used to describe your program would not fix this; you need to actually change your program.

One possible change would be to transfer ownership of the String representing each line from the lines variable to the vectors. This is a fairly easy change to make:

fn understanding_vec_lifetime(path: &str) {
    let mut header = vec![];
    let mut sequence = vec![];
    let f = File::open(&path).expect("file not present");
    let read = BufReader::new(f);
    for i in read.lines() {
        let line = i
                   .expect("line not present");
    	if line.starts_with("A") {
            header.push(line); // <-- remove & here…
        }
        if !line.starts_with("A") {
            sequence.push(line); // <-- …and here.
        } 
        println!("{:?}", header)
    }
}

By transferring ownership, the program guarantees that each string will live at least as long as the vec it's inserted into will live, which is enough to satisfy the borrow checker that your program never accesses dropped values.

4 Likes

This is the solution that makes sense. It is best to avoid borrowing unless there is a reason for it.

@codecreatede Let's say you really want to create two Vecs containing references to the Strings (this is borrowing), for some reason that is not explained in your example.

Then each String cannot be dropped inside the loop as described above. Instead, we must store the owned Strings somewhere else, for at least as long as the two Vecs exist. You can't borrow something that no longer exists (has been dropped).

Here is an example of this, where the lines are all stored as owned Strings in a Vec, and then borrowed within the loop. The references in the other two Vecs will refer to the owned Strings in the first Vec where they are stored.

fn understanding_vec_lifetime(path: &str) {
    let mut header = vec![];
    let mut sequence = vec![];
    let f = File::open(path).expect("file not present");
    let read = BufReader::new(f);
    let lines: Vec<String> =
        read.lines().map(|result| result.expect("line not present")).collect();
    for line in &lines {
        if line.starts_with("A") {
            header.push(line)
        }
        if !line.starts_with("A") {
            sequence.push(line)
        }
        println!("{:?}", header)
    }
}

Note that lifetime annotations are not needed here (but may be needed in more complex situations). The key thing is to understand ownership.

EDIT: Also note that the type of line is &String because we're iterating borrowed Strings using for line in &lines. Therefore, the type of header and sequence is inferred to be Vec<&String>. If you want Vec<&str> instead, which is typical/idiomatic in Rust, you can declare the types:

    let mut header: Vec<&str> = vec![];
    let mut sequence: Vec<&str> = vec![];
2 Likes

If you want to understand the lifetime in Rust in a thorough way, I would recommend reading the Chapeter 10 of The Rust Programming Language first to have an overview of what lifetime is in Rust. Then read 3.3-3.6 of the Rustonomicon to get a deeper insight of lifetime. At this time, you might still have some misconceptions about Rust lifetime. If so, you can refer to this blog that disenchants some common confusion a beginner can have.

Moreover, Rust is like a monolithic system. All the grammar and features, like the lifetime, borrow check, drop check, are correlated. Sometimes you just cannot understand a single feature on its own and why it is designed in that way until you capture a larger map of the Rust world. Don't be afraid if you encounter some difficulties on the way. Put your question aside and learn something related to that concept. Maybe a new point of view to the original question would hit you and you'll find everything is reasonable. You can also put your question on the forum, which is always a welcoming community for everyone in Rust.

1 Like

Thank you all @jumpnbrownweasel @derspiny that is OK and actually i completed and it worked but i am working on something new that i want to buy multiple ownership and that was the reason to ask to understand the multiple ownership in detail. This discussion after some reading yesterday helped me understand the ownership in RUST in much depth. Thank you all for the worth shown and i appreciate you all.