Rust multiple lifetimes struct can't access beyond the less longer lifetime parameter - struct

struct TwoStrRef<'a, 'b> {
str1: &'a str,
str2: &'b str,
}
fn main() {
let a_ref_struct;
let string_1 = String::from("hello");
let str_ref;
{
let string_2 = String::from("world");
a_ref_struct = TwoStrRef {
str1: &string_1,
str2: &string_2,
};
str_ref = a_ref_struct.str1;
}
// str_ref; // Ok
// a_ref_struct.str1; // Error: `string_2` does not live long enough borrowed value does not live long enough
}
there are two lifetime parameters for struct TwoStrRef, a' and 'b, and I assign the reference of string_1 and string_2 (which are in different scope and string_1 is the longer one) to field str_1 and str_2, and when I try to access a_ref_struct.str1 outside the scope of string_2 (but the same with string_1), the compiler will throw error, which indicated that string_2 does not live long enough. Isn't the str1 field holds the reference of string_1 that is not outside its scope ? And why if I assign the str1 reference to str_ref, I can access it in the same scope with string_1?

Isn't the str1 field holds the reference of string_1 that is not outside its scope ?
a_ref_struct is dropped before string_2 goes out of scope: this is required because otherwise any access of its str2 field (which could potentially occur in its drop handler, for example) would be invalid. Indeed if a_ref_struct were to exist after string2 is dropped, its str2 would be a reference into released memory which is Undefined Behaviour even if that reference is never accessed.
And why if I assign the str1 reference to str_ref, I can access it in the same scope with string_1?
There you are just taking a copy of the reference that is held in a_ref_struct.str1 (with lifetime 'a of string_1) and storing that copy into str_ref. a_ref_struct can then be (and is) dropped without affecting that (copied) reference.

Related

Why does assigning a reference to a variable make me not able to return it

In this code:
fn main() {
let a = {
&mut vec![1]
};
let b = {
let temp = &mut vec![1];
temp
};
println!("{a:?} {b:?}");
}
Why is a valid and b not valid ("temporary value dropped while borrowed [E0716]")?
It would make sense to me if they were both problematic, why isn't the vec in a getting dropped?
Is this simply that the compiler can understand the first example but the second one is to hard for it to understand?
In one sentence: a is a temporary while b is not.
temp is a variable; variables are always dropped at the end of the enclosing scope. The scope ends before we assign it to b.
In contrast, the vec![] in a is a temporary, as it is not assigned to a variable. Temporaries are generally dropped at the end of the statement, however because the statement is a let declaration, the temporary inside it is subject to temporary lifetime extension and its lifetime is extended to match the lifetime of a itself, that is, until the enclosing block of a.
Note that to be precise, temp is also assigned a temporary that is subject to temporary lifetime extension - but its extended lifetime matches the lifetime of temp, as it is part of its declaration.

Where does the value live after it's borrowed in the function?

Consider below code:
fn main(){
let mut s = "Hi".to_string();
s.push_str(&" foo".to_string());
println!("{}",s);
}
We send reference of foo (i.e, a String not str) in push_str. But, we are not storing foo in any variable and just sending the reference.
Where does the value foo live for the reference to be valid inside of push_str.
As I'm just borrowing foo, how can I refer to foo (as a reference) later in my code i.e, after println?
Rust temporaries exist until the end of the current expression, and they can even be extended if necessary. The Rust compiler effectively creates a temporary variable for that temporary that you never see. This
s.push_str(&" foo".to_string());
is semantically equivalent to
{
let tmp = " foo".to_string();
s.push_str(&tmp);
}

Who has the ownership of a temporary value when it is created and referred by a struct? [duplicate]

Coming from C++, I'm rather surprised that this code is valid in Rust:
let x = &mut String::new();
x.push_str("Hello!");
In C++, you can't take the address of a temporary, and a temporary won't outlive the expression it appears in.
How long does the temporary live in Rust? And since x is only a borrow, who is the owner of the string?
Why is it legal to borrow a temporary?
It's legal for the same reason it's illegal in C++ — because someone said that's how it should be.
How long does the temporary live in Rust? And since x is only a borrow, who is the owner of the string?
The reference says:
the temporary scope of an expression is the
smallest scope that contains the expression and is for one of the following:
The entire function body.
A statement.
The body of a if, while or loop expression.
The else block of an if expression.
The condition expression of an if or while expression, or a match
guard.
The expression for a match arm.
The second operand of a lazy boolean expression.
Essentially, you can treat your code as:
let mut a_variable_you_cant_see = String::new();
let x = &mut a_variable_you_cant_see;
x.push_str("Hello!");
See also:
Why can I return a reference to a local literal but not a variable?
What is the scope of unnamed values?
Are raw pointers to temporaries ok in Rust?
From the Rust Reference:
Temporary lifetimes
When using a value expression in most place expression contexts, a temporary unnamed memory location is created initialized to that value and the expression evaluates to that location instead
This applies, because String::new() is a value expression and being just below &mut it is in a place expression context. Now the reference operator only has to pass through this temporary memory location, so it becomes the value of the whole right side (including the &mut).
When a temporary value expression is being created that is assigned into a let declaration, however, the temporary is created with the lifetime of the enclosing block instead
Since it is assigned to the variable it gets a lifetime until the end of the enclosing block.
This also answers this question about the difference between
let a = &String::from("abcdefg"); // ok!
and
let a = String::from("abcdefg").as_str(); // compile error
In the second variant the temporary is passed into as_str(), so its lifetime ends at the end of the statement.
Rust's MIR provides some insight on the nature of temporaries; consider the following simplified case:
fn main() {
let foo = &String::new();
}
and the MIR it produces (standard comments replaced with mine):
fn main() -> () {
let mut _0: ();
scope 1 {
let _1: &std::string::String; // the reference is declared
}
scope 2 {
}
let mut _2: std::string::String; // the owner is declared
bb0: {
StorageLive(_1); // the reference becomes applicable
StorageLive(_2); // the owner becomes applicable
_2 = const std::string::String::new() -> bb1; // the owner gets a value; go to basic block 1
}
bb1: {
_1 = &_2; // the reference now points to the owner
_0 = ();
StorageDead(_1); // the reference is no longer applicable
drop(_2) -> bb2; // the owner's value is dropped; go to basic block 2
}
bb2: {
StorageDead(_2); // the owner is no longer applicable
return;
}
}
You can see that an "invisible" owner receives a value before a reference is assigned to it and that the reference is dropped before the owner, as expected.
What I'm not sure about is why there is a seemingly useless scope 2 and why the owner is not put inside any scope; I'm suspecting that MIR just isn't 100% ready yet.

Understanding lifetimes: borrowed value does not live enough

fn main() {
let long;
let str1="12345678".to_string();
{
let str2 = "123".to_string();
long = longest(&str1, &str2);
}
println!("the longest string is: {}", long);
}
fn longest<'a>(x:&'a str, y:&'a str) -> &'a str{
if x.len() > y.len() {
x
} else {
y
}
}
gives
error[E0597]: `str2` does not live long enough
--> src/main.rs:6:31
|
6 | long = longest(&str1, &str2);
| ^^^^^ borrowed value does not live long enough
7 | }
| - `str2` dropped here while still borrowed
8 | println!("the longest string is: {}", long);
| ---- borrow later used here
My theory is that, since the funtion longest has only one lifetime parameter, the compiler is making both x and y to have the lifetime of str1. So Rust is protecting me from calling longest and possibly receive back str2 which has lifetime less than str1 which is the chosen lifetime for 'a.
Is my theory right?
Let's take a closer look at the signature for longest:
fn longest<'a>(x: &'a str, y: &'a str) -> &'a str
What this means is that for a given lifetime 'a, both arguments need to last at least for the length of that lifetime (or longer, which doesn't really matter since you can safely shorten any lifetime in this case without any particular difference) and the return value also lives as long as that lifetime, because the return value comes from one of the arguments and therefore "inherits" the lifetime.
The sole reason for that is that at compile time, you can't really be sure whether x or y will be returned when compiling the function, so the compiler has to assume that either can be returned. Since you've bound both of them with the same lifetime (x, y and the return value have to live at least for the duration of 'a), the resulting lifetime of 'a is the smallest one. Now let's examine the usage of the function:
let long;
let str1 = "12345678".to_string();
{
let str2 = "123".to_string();
long = longest(&str1, &str2);
}
You have two lifetimes here, the one outside the braces (the main() body lifetime) and the lifetime inside the braces (since everything between the braces is destroyed after the closing brace). Because you're storing the strings as String by using .to_string() (owned strings) rather than &'static str (borrowed string literals stored in the program executable file), the string data gets destroyed as soon as it leaves the scope, which, in the case of str2, is the brace scope. The lifetime of str2 ends before the lifetime of str1, therefore, the lifetime of the return value comes from str2 rather than str1.
You then try to store the return value into long — a variable outside the inner brace scope, i.e. into a variable with a lifetime of the main() body rather than the scope. But since the lifetime of str2 restricts the lifetime of the return value for longest in this situation, the return value of longest doesn't live after the braced scope — the owned string you used to store str2 is dropped at the end of the braced scope, releasing resources required to store it, i.e. from a memory safety standpoint it no longer exists.
If you try this, however, everything works fine:
let long;
let str1 = "12345678";
{
let str2 = "123";
long = longest(str1, str2);
}
println!("the longest string is: {}", long);
But why? Remember what I said about how you stored the strings, more specifically, what I said about borrowed string literals which are stored in the executable file. These have a 'static lifetime, which means the entire duration of the program's runtime existence. This means that &'static to anything (not just str) always lives long enough, since now you're referring to memory space inside the executable file (allocated at compile time) rather than a resource on the heap managed by String and dropped when the braced scope ends. You're no longer dealing with a managed resource, you're dealing with a resource managed at compile time, and that pleases the borrow checker by eliminating possible issues with its duration of life, since it's always 'static.
This code for the developer's perspective looks good and it should be because we are printing long in the outer scope so there should be no problem at all.
But Rust's compiler does a strict check on borrowed values and it needs to be sure that every value lives long enough for the variables that depend on that value.
Compiler sees that long depends on the value whose lifetime is shorter that it's own i.e. str, it gives an error. Behind the scenes this is done by a borrow checker.
You can see more details about borrow checker here

Do lifetime annotations in Rust change the lifetime of the variables?

The Rust chapter states that the annotations don't tamper with the lifetime of a variable but how true is that? According to the book, the function longest takes two references of the strings and return the longer one. But here in the error case
fn longest<'a>(x: &'a str, y: &'a str) -> &'a str {
if x.len() > y.len() {
x
} else {
y
}
}
fn main() {
let string1 = String::from("long string is long");
let result;
{
let string2 = String::from("xyz");
result = longest(string1.as_str(), string2.as_str());
}
println!("The longest string is {}", result);
}
it does actually change the lifetime of the result variable, doesn't it?
We’ve told Rust that the lifetime of the reference returned by the longest function is the same as the smaller of the lifetimes of the references passed in.
What the book is merely suggesting is that a lifetime parameter of a function cannot interfere with the affected value's lifetime. They cannot make a value live longer (or the opposite) than what is already prescribed by the program.
However, different function signatures can decide the lifetime of those references. Since references are covariant with respect to their lifetimes, you can turn a reference of a "wider" lifetime into a smaller one within that lifetime.
For example, given the definition
fn longest<'a>(a: &'a str, b: &'a str) -> &'a str
, the lifetimes of the two input references must match. However, we can write this:
let local = "I am local string.".to_string();
longest(&local, "I am &'static str!");
The string literal, which has a 'static lifetime, is compatible with the lifetime 'a, in this case mainly constrained by the string local.
Likewise, in the example above, the lifetime 'a has to be constrained to the nested string string2, otherwise it could not be passed by reference to the function. This also means that the output reference is restrained by this lifetime, which is why the code fails to compile when attempting to use the output of longest outside the scope of string2:
error[E0597]: `string2` does not live long enough
--> src/main.rs:14:44
|
14 | result = longest(string1.as_str(), string2.as_str());
| ^^^^^^^ borrowed value does not live long enough
15 | }
| - `string2` dropped here while still borrowed
16 | println!("The longest string is {}", result);
| ------ borrow later used here
See also this question for an extended explanation of lifetimes and their covariance/contravariance characteristics:
How can this instance seemingly outlive its own parameter lifetime?
First, it's important to understand the difference between a lifetime and a scope. References have lifetimes, which are dependent on the scopes of the variables they refer to.
A variable scope is lexical:
fn main() {
let string1 = String::from("long string is long"); // <-- scope of string1 begins here
let result;
{
let string2 = String::from("xyz"); // <-- scope of string2 begins here
result = longest(string1.as_str(), string2.as_str());
// <-- scope of string2 ends here
}
println!("The longest string is {}", result);
// <-- scope of string1 ends here
}
When you create a new reference to a variable, the lifetime of the reference is tied solely to the scope of the variable. Other references have different lifetime information attached to them, depending on where the reference came from and what information is known in that context. When you put named lifetime annotations on a type, the type-checker simply ensures that the lifetime information attached to any references is compatible with the annotations.
fn main() {
let string1 = String::from("long string is long");
let result;
{
let string2 = String::from("xyz");
// The lifetime of result cannot be longer than `'a`
result = longest(string1.as_str(), string2.as_str());
// But a reference to string2 also has lifetime `'a`, which means that
// the lifetime `'a` is only valid for the scope of string2
// <-- i.e. to here
}
// But then we try to use it here — oops!
println!("The longest string is {}", result);
}
We’ve told Rust that the lifetime of the reference returned by the longest function is the same as the smaller of the lifetimes of the references passed in.
Sort of. We did tell this information to Rust, however, the borrow-checker will still check if it is true! If it's isn't already true then we will get an error. We can't change the truthfulness of that information, we can only tell Rust the constraints we want, and it will tell us if we are right.
In your example, you could make the main function valid by changing the lifetime annotations on longest:
fn longest<'a, 'b>(x: &'a str, y: &'b str) -> &'a str {
if x.len() > y.len() {
x
} else {
y // oops!
}
}
But now you get an error inside longest because it no longer meets the requirements: it is now never valid to return y because its lifetime could be shorter than 'a. In fact, the only ways to implement this function correctly are:
Return x
Return a slice of x
Return a &'static str — since 'static outlives all other lifetimes
Use unsafe code

Resources