Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why do &str arrays in Rust passed as parameters have different lifetimes?

I am in the process of learning Rust and was testing some array copying through a function. I am sure there are built-in Rust functions to copy/clone array information, but a personal implementation I thought would be a good idea to help my understanding of passing references through functions.

fn copy_str_arr_original (a1: [&str; 60], a2: &mut [&str; 60]) {
    // copy 1 into 2
    for i in 0..60 {
        a2[i] = a1[i];
    } // change is reflected in a2 as it is passed as &mut
}

However, this threw the error these two types are declared with different lifetimes... for the &str types themselves. After some further studying, I tried declaring my own lifetime and assigning them to it, and that fixed it!

fn copy_str_arr_fix<'a> (a1: [&'a str; 60], a2: &mut [&'a str; 60]) {
    // copy 1 into 2
    for i in 0..60 {
        a2[i] = a1[i];
    } // change is reflected in a2 as it is passed as &mut
}

Why is this the case, though? Why does the type of values within the array need to have a lifetime assigned instead of the parameters themselves? In other words, why does this not work at all?

fn copy_str_arr_bad<'a> (a1: &'a [&str; 60], a2: &'a mut [&str; 60]) {
    // does not work...           ^-----------------------^-------- different lifetimes
    for i in 0..60 {
        a2[i] = a1[i]; 
    } 
}

I am still struggling to get the hang of how lifetimes work in the context of more complex objects such as arrays and structs, so any explanation would be greatly appreciated!

like image 917
jts Avatar asked Nov 03 '21 17:11

jts


People also ask

What are the 10 reasons for doing what we do?

10 Reasons: A Guide for Why We Do, What We Do 1. Obligation (Need). Abraham Maslow’s Hierarchy of Needs. ... Starting at the base of the above pyramid, in Abraham... 2. Pride (Sense of duty). This is a wide-ranging category, which could encompass a sense of duty like patriotism,... 3. Vice (Selfish ...

Why do we go to work?

Survival is a sure necessity! So, we go to work and make sure we provide so we have a roof over our heads. Our cognizance of these things may not always be conscious, yet our senses are attuned and aware of it. We do what we have to do to survive, out of obligation to serve others and our needs. 2. Pride (Sense of duty)

Why do we do what we do in life?

We do what we have to do to survive, out of obligation to serve others and our needs. 2. Pride (Sense of duty) This is a wide-ranging category, which could encompass a sense of duty like patriotism, nationalism, volunteerism — the pledge of ourselves for a cause that we deem to be greater than ourselves.

Why is understanding the “why” and “what” so important?

The greater understanding we have for the “Why” and the “What”, the more likely we are to find peace and balance in our lives. This by no means suggests that life is one big game of figuring out all the answers and mysteries that elude us. That’s impossible. It’s a fool’s errand.


Video Answer


2 Answers

The error message is a bit confusing because it refers to lifetimes generated as per rules of lifetime elision. In your case, lifetime elision means that:

fn copy_str_arr_original(a1: [&str; 60], a2: &mut [&str; 60])

is syntactic sugar for:

fn copy_str_arr_original<'a1, 'a2_mut, 'a2>(a1: [&'a1 str; 60], a2: &'a2_mut mut [&'a2 str; 60])

In other words, we have three completely unrelated lifetimes. "Unrelated" means that the caller gets to choose how long the objects they're associated with live. For example, the strings in a2 might be static and live until the end of the program, while the strings in a1 might get dropped immediately after copy_str_arr_original() returns. Or the other way around. If that amount of freedom seems like it could cause problems, you're on the right track because the borrow checker agrees with you.

Note that, somewhat counter-intuitively, the length of the 'a2_mut lifetime is completely irrelevant, it can be as long or as short as the caller likes. Our function has received the reference and can therefore use it during the function's scope. 'a2_mut lifetime tells us how long it will live outside the scope of the function, and we just don't care about that.

'a1 and 'a2 are another matter. Since we're copying references from a1 to a2, we are effectively casting the references inside a1 (of type &'a1 str) to the type of references stored in a2 (which is &'a2 str):

a2[i] = a1[i];  // implicitly casts &'a1 str to &'a2 str

For that to be valid, &'a1 str must be a subtype of &'a2 str. While Rust doesn't have classes and subclassing in the C++ sense, it does have subtypes where lifetimes are concerned. In that sense, A is a subtype of B if values of A values are guaranteed to live at least as long as values of B. In other words, 'a1 must leave at least as long as 'a2, which is expressed as 'a1: 'a2. So this compiles:

fn copy_str_arr<'a1: 'a2, 'a2, 'a2_mut>(a1: [&'a1 str; 60], a2: &'a2_mut mut [&'a2 str; 60]) {
    for i in 0..60 {
        a2[i] = a1[i];
    }
}

Another way for the cast to succeed is to just require the lifetime to be the same, which you did in your copy_str_arr_fix(). (You also omitted the 'a2_mut lifetime, which compiler correctly interpreted as a request for an unrelated anonymous lifetime.)

like image 125
user4815162342 Avatar answered Oct 19 '22 12:10

user4815162342


Let's assume that you can define copy_str_arr with two different, unrelated lifetimes, like this:

fn copy_str_arr<'a, 'b>(a1: [&'a str; 60], a2: &mut [&'b str; 60]) {
    // ...
}

Then consider this example:

let mut outer: [&str; 60] = [""; 60];

{
    let temp_string = String::from("temporary string");
    
    let inner: [&str; 60] = [&temp_string; 60];

    // this compiles because our bad `copy_str_arr` function allows
    // `inner` and `outer` to have unrelated lifetimes
    copy_str_array(&inner, &mut outer); 

}   // <-- `temp_string` destroyed here

// now `outer` contains references to `temp_string` here, which is invalid
// because it has already been destroyed!

println!("{:?}", outer); // undefined behavior! may print garbage, crash your
                         // program, make your computer catch fire or anything else

As you can see, if a1 and a2 are allowed to have completely unrelated lifetimes, then we can end up in a situation where one of the arrays holds references to invalid data, which is very bad.

However, the lifetimes do not have to be the same. You can instead require that the lifetime you are copying from outlives the lifetime you are copying to (thus ensuring that you're not illegally extending the lifetime of a reference):

fn copy_str_arr<'a, 'b>(a1: &[&'a str; 60], a2: &mut [&'b str; 60])
where
    'a: 'b, // 'a (source) outlives 'b (destination)
{
    for i in 0..60 {
        a2[i] = a1[i];
    }
}
like image 37
Frxstrem Avatar answered Oct 19 '22 11:10

Frxstrem