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!
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 ...
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)
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.
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.
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.)
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];
}
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With