Lifetime bounds between function arguments - rust

The code wants to constrain x and y to the same lifetime and uses a function to do so. But the compiler does not error when y is dropped. Does this mean lifetime bounds do not work between function arguments, irrespective of function return value?
static S: String = String::new();
// `x` and `y` have the same lifetime
fn foo<'a>(x: &'a String, y: &'a String) -> &'static String {
if x == y {
&S
} else {
&S
}
}
fn main() {
let z;
let x = &"x".to_string();
{
let y = &"y".to_string();
z = foo(x, y);
} // drops `y`
dbg!(x);
dbg!(z);
}

Requiring "the same" lifetime just means that the compiler must find some single lifetime that matches the scopes of objects referenced by x and y. It doesn't mean that they have to have identical scopes.
Since your function doesn't use the lifetime in return position, and they are non-mutable, it is not useful. A useful argument-only lifetime will typically involve mutation - for example:
fn append<'a>(v: &mut Vec<&'a str>, s: &'a str) {
v.push(s);
}
If you tried to omit the lifetime here, the function wouldn't compile, because then it would allow appending short-lived references to a vector that expects e.g. static ones.

Related

Function works with inferred lifetime but not explicit lifetime

In this code:
struct Obj<'a> {
inside: &'a mut i32
}
fn take_and_return<'o>(obj: Obj<'o>) -> Obj<'o> {
obj
}
fn run_me_1() {
let mut v = 42;
let s: Obj<'_> = Obj {
inside: &mut v
};
take_and_return(s);
}
I wanted to introduce named lifetime for s in run_me_1.
I used Rust Analyzer's suggestion:
fn run_me_2<'a>() {
let mut v = 42;
let s: Obj<'a> = Obj {
inside: &mut v
};
take_and_return(s);
}
But then I got the following error:
error[E0597]: `v` does not live long enough
--> src/lib.rs:20:11
|
17 | fn run_me_2<'a>() {
| -- lifetime `'a` defined here
18 | let mut v = 42;
19 | let s: Obj<'a> = Obj {
| ------- type annotation requires that `v` is borrowed for `'a`
20 | inside: &mut v
| ^^^^^^ borrowed value does not live long enough
...
23 | }
| - `v` dropped here while still borrowed
My understanding is that take_and_return takes ownership of obj, so obj must last forever, so 'o must last forever. That explains why run_me_2 fails to compile.
My questions are:
Why does run_me_1 compile?
What did the inferrer put into that '_ in run_me_1?
How can I fix run_me_2 so that it compiles?
Playground link to code
My understanding is that take_and_return takes ownership of obj, so obj must last forever, so 'o must last forever.
That kind of statement is true if the value is owned and has a 'static lifetime bound, typically expressed as T: 'static. take_and_return doesn't require T: 'static, it takes a concrete type associated with a lifetime 'o which it must not outlive (because it contains a reference with that lifetime).
run_me_1 compiles not because the object is static, but because the variable v clearly outlives the value returned by take_and_return, which is immediately dropped, while v is still live. If you modified run_me_1 so that the value returned by take_and_return() actually outlived v, it would fail to compile too. For example:
fn run_me_1_modified() {
let x;
{
let mut v = 42;
let s: Obj<'_> = Obj { inside: &mut v };
x = take_and_return(s);
}
println!("{:p}", x.inside); // or use x in any other way
}
What did the inferrer put into that '_ in run_me_1?
It put an anonymous lifetime corresponding to the part of the source where v is live.
run_me_2() is a different beast. It basically says, "I will let my caller choose any lifetime it wishes, and then I'll create an Obj associated with that lifetime, after which I'll pass it to take_and_return(), get an Obj with the same lifetime back, and destroy it. This can't be right because Obj { &mut v } clearly requires that the lifetime specified by Obj be a subset of the lifetime of v. But we're technically allowing our caller to choose the lifetime of obj, so it might as well call us with run_me_2<'static>.
Note that a declaration like fn run_me_2<'a>() { ... } doesn't make too much sense because only the static lifetime can be named without referring to a previously defined value. Normally lifetimes are connecting to existing values. The canonical example is something like:
fn search<'a, 'b>(haystack: &'a str, needle: &'b str) -> Option<&'a str>
...which says that the return value will be coming from haystack, and not from needle. On the other hand, fn run_me_2<'a>() is almost useless because it doesn't allow the caller to choose a useful lifetime based on a value it cares about, but only to provide 'static as the only possible name.
How can I fix run_me_2 so that it compiles?
If you insist on having a named lifetime for Obj, then you need to introduce a function that receives a value connected to that lifetime. For example, you could pass the reference to the variable to an inner function:
fn run_me_2() {
fn inner_fn<'a>(r: &'a mut i32) {
let s: Obj<'a> = Obj {
inside: r,
};
take_and_return(s);
}
let mut v = 42;
inner_fn(&mut v);
}

Prevent cannot borrow `*self` as immutable because it is also borrowed as mutable when accessing disjoint fields in struct?

Prior question that this is not a duplicate of:
cannot borrow `*self` as mutable because it is also borrowed as immutable
This question is not relevant b/c the answer ended up being "the Rust compiler has a bug", which I'm pretty sure is not the case here.
I have the following structs:
struct Foo {
data: Vec<Bar>,
a: usize,
}
struct Bar {
b: usize
}
Inside impl Foo, I have the following methods:
fn example(&mut self, c: usize) {
let baz = &mut self.data[c];
let z = self.calc(c);
baz.b = 42 + z;
}
fn calc(&self, x: usize) -> usize {
self.a * x
}
Of course, the Rust compiler throws an error saying roughly "a mutable borrow occurs when you create baz, then you do an immutable borrow when you call self.calc and lastly you later use the mutable borrow when you assign to baz.a.
However, I'm accessing disjoint fields on the struct because calc never reads from data that is being written to through baz.
Is there a way to inform the Rust compiler of this?
The problem is the signature of Foo::calc, which takes &self as the receiver. This guarantees to calc that there are no mutable references to all of self, including any of its fields; that is, all of Foo is guaranteed to be immutable from the body of calc's point of view. This is not possible with the code as it is, because self.calc(c) requires to immutably borrow self (all of self) while a mutable borrow is still active.
The fact that calc never reads from data that is being written to baz is irrelevant. In Rust, all there is to know about a function or method is exposed in the signature; the compiler never looks "into" a function to figure out if it's a special case or not.
You can avoid the problem by not requiring calc to take &self as a receiver. For instance, by borrowing individual fields before the call (Self::calc(&self.a, c)) or, as in the example below, by not borrowing self at all:
impl Foo {
fn example(&mut self, c: usize) {
let baz = &mut self.data[c];
// This avoids borrowing `self` in its entirety...
let z = Self::calc(self.a, c);
baz.b = 42 + z;
}
fn calc(a: usize, x: usize) -> usize {
a * x
}
}
I ended up doing:
fn example(&mut self, c: usize) {
let baz = &self.data[c];
// some stuff that requires reading `baz`
let z = self.calc(c);
let baz = &mut self.data[c];
baz.b = 42 + z;
}
fn calc(&self, x: usize) -> usize {
self.a * x
}

Problems with lifetime/borrow on str type

Why does this code compile?
fn longest<'a>(x: &'a str, y: &'a str) -> &'a str {
if x.len() > y.len() {
x
} else {
y
}
}
fn main() {
let x = "eee";
let &m;
{
let y = "tttt";
m = longest(&x, &y);
}
println!("ahahah: {}", m);
}
For me, there should be a compilation error because of lifetimes.
If I write the same code with i64, I get an error.
fn ooo<'a>(x: &'a i64, y: &'a i64) -> &'a i64 {
if x > y {
x
} else {
y
}
}
fn main() {
let x = 3;
let &m;
{
let y = 5;
m = ooo(&x, &y);
}
println!("ahahah: {}", m);
}
The error is:
error[E0597]: `y` does not live long enough
--> src/main.rs:103:25
|
103 | m = ooo(&x, &y);
| ^^ borrowed value does not live long enough
104 | }
| - `y` dropped here while still borrowed
105 | println!("ahahah: {}", m);
| - borrow later used here
There are a few things we need to know to understand this. The first is what the type of a string literal is. Any string literal (like "foo") has the type &'static str. This is a reference to a string slice, but moreover, it's a static reference. This kind of reference lasts for the entire length of the program and can be coerced to any other lifetime as needed.
This means that in your first piece of code, x and y are already both references and have type &'static str. The reason the call longest(&x, &y) still works (even though &x and &y have type &&'static str) is due to Deref coercion. longest(&x, &y) is really de-sugared as longest(&*x, &*y) to make the types match.
Let's analyze the lifetimes in the first piece of code.
fn main() {
// x: &'static str
let x = "eee";
// Using let patterns in a forward declaration doesn't really make sense
// It's used for things like
// let (x, y) = fn_that_returns_tuple();
// or
// let &x = fn_that_returns_reference();
// Here, it's the same as just `let m;`.
let &m;
{
// y: &'static str
let y = "tttt";
// This is the same as `longest(x, y)` due to autoderef
// m: &'static str
m = longest(&x, &y);
}
// `m: &static str`, so it's still valid here
println!("ahahah: {}", m);
}
(playground)
With the let &m; you may have meant something like let m: &str to force its type. This I think actually would ensure that the lifetime of the reference in m starts with that forward declaration. But since m has type &'static str anyway, it doesn't matter.
Now let's look at the second version with i64.
fn main() {
// x: i64
// This is a local variable
// and will be dropped at the end of `main`.
let x = 3;
// Again, this doesn't really make sense.
let &m;
// If we change it to `let m: &i64`, the error changes,
// which I'll discuss below.
{
// Call the lifetime roughly corresponding to this block `'block`.
// y: i64
// This is a local variable,
// and will be dropped at the end of the block.
let y = 5;
// Since `y` is local, the lifetime of the reference here
// can't be longer than this block.
// &y: &'block i64
// m: &'block i64
m = ooo(&x, &y);
} // Now the lifetime `'block` is over.
// So `m` has a lifetime that's over
// so we get an error here.
println!("ahahah: {}", m);
}
(playground)
If we change the declaration of m to let m: &i64 (which is what I think you meant), the error changes.
error[E0597]: `y` does not live long enough
--> src/main.rs:26:21
|
26 | m = ooo(&x, &y);
| ^^ borrowed value does not live long enough
27 | } // Now the lifetime `'block` is over.
| - `y` dropped here while still borrowed
...
30 | println!("ahahah: {}", m);
| - borrow later used here
(playground)
So now we explicitly want m to last as long as the outer block, but we can't make y last that long, so the error happens at the call to ooo.
Since both these programs are dealing with literals, we actually can make the second version compile. To do this, we have to take advantage of static promotion. A good summary of what that means can be found at the Rust 1.21 announcement (which was the release the introduced this) or at this question.
In short, if we directly take a reference to a literal value, that reference may be promoted to a static reference. That is, it's no longer referencing a local variable.
fn ooo<'a>(x: &'a i64, y: &'a i64) -> &'a i64 {
if x > y {
x
} else {
y
}
}
fn main() {
// due to promotion
// x: &'static i64
let x = &3;
let m;
{
// due to promotion
// y: &'static i64
let y = &5;
// m: &'static i64
m = ooo(x, y);
}
// So `m`'s lifetime is still active
println!("ahahah: {}", m);
}
(playground)
Your examples are not exactly the same. A string literal, "eee" has type &str, not str, so the corresponding code with integers looks like this:
fn ooo<'a>(x: &'a i64, y: &'a i64) -> &'a i64 {
if x > y {
x
} else {
y
}
}
fn main() {
let x = &3;
let &m;
{
let y = &5;
m = ooo(&x, &y);
}
println!("ahahah: {}", m);
}
And it compiles.
The reason that this (and the &str example) works is because local inline literal references are given the 'static lifetime. That is, they are placed in the static data portion of the final binary and are available in memory for the full lifespan of the program.

Why does returning a string slice instead of a usize prevent mutating a String?

This code compiles:
fn main() {
let mut s = String::from("some_string");
let n = f1(&s);
s.clear();
println!("n = {}", n);
}
fn f1(s: &String) -> usize {
10
}
fn f2(s: &String) -> &str {
"def"
}
However, replacing the call to f1() by f2() causes a compilation failure. It appears to me that both f1() and f2() do an immutable borrow, and s.clear() does a mutable borrow, so I should get compilation error in both cases. What am I missing?
Here is the minimum code necessary to reproduce the issue:
fn f1(s: &String) -> usize { unimplemented!() }
fn f2(s: &String) -> &str { unimplemented!() }
fn main() {
let mut s = String::from("some_string");
let n = f1(&s);
s.clear();
println!("n = {}", n);
}
Lifetime analysis is performed based on function signatures.
You will note in the above code that I have used unimplemented!() as the body of the functions, and the problem is exactly the same. This is normal.
In the vast majority of cases1, a function signature fully specifies the interface of a function, and it is unnecessary to look at its implementation.
As a corollary, this also means that whether the lifetime in a return type is linked to the lifetime in any of the arguments is fully specified within the signature, and in this case the full signature of f2 is therefore:
fn f2<'a>(s: &'a String) -> &'a str;
Whether the implementation of f2 is "def" (with the 'static lifetime) or &*s (with the 'a lifetime) does not matter; only the signature matters, and the signature uses the same lifetime due to the elision rules.
1 The one exception I know of concerns the -> impl Trait feature and whether the resulting object implements Send or Sync.
In the case of f1, the return type is not linked to the argument, therefore the borrow of the argument ends at the end of the call to f1:
fn main() {
let mut s = String::from("some_string");
let n = {
// Immutable borrow of s starts here.
f1(&s)
// Immutable borrow of s ends here.
};
s.clear();
println!("n = {}", n);
}
In the case of f2, the return type has the same lifetime as the argument, and therefore is considered to extend the borrow. In Rust 2015, the borrow would extend until the return value went out of scope (lexical borrow); with Rust 2018, the borrow extends until the last use of the return value (non-lexical borrow).
In your case, both are basically identical:
fn main() {
let mut s = String::from("some_string");
let n = {
// Immutable borrow of s starts here.
f2(&s)
};
s.clear(); // Conflicting attempt to mutably borrow s.
println!("n = {}", n);
// Immutable borrow of s ends here.
}
You could observe the difference by switching the order of s.clear() and println!.
The Rust reference says:
If there is exactly one lifetime used in the parameters (elided or not), that lifetime is assigned to all elided output lifetimes.
This means that your method
fn f2(s: &String) -> &str {
"def"
}
is interpreted by Rust as:
fn f2<'a>(s: &'a String) -> &'a str {
"def"
}
Since "def" has the lifetime 'static, its lifetime can be shortened to 'a when being returned from the function (so the compiler won't complain here), but when calling the function the compiler cannot infer that the true lifetime of the string was really 'static. To do this, you must explicitly mark it as 'static yourself:
fn f2(s: &String) -> &'static str {
"def"
}
f1 and f2 both take an immutable borrow. However, the lifetime of the borrow from f1 ends at the end of f1, because you're just returning a usize and not anything from the actual string.
However, f2 returns a &str, which is borrowing your underlying String, s. Since n stays alive, the immutable borrow of s continues until n is no longer used. Effectively, this prevents your call to s.clear() from "pulling the rug out from under" your pointer s.

Why do generic lifetimes not conform to the smaller lifetime of a nested scope?

According to The Rust Programming Language:
Since scopes always nest, another way to say this is that the generic lifetime 'a will get the concrete lifetime equal to the smaller of the lifetimes of x and y.
fn main() {
let x = "abcd";
let result;
{
let y = "qwerty";
result = longest(x, y);
}
println!("The longest string is {} ", result);
}
fn longest<'a>(x: &'a str, y: &'a str) -> &'a str {
if x.len() > y.len() {
x
} else {
y
}
}
In the main function, "the smaller of the lifetimes of x and y" is the nested scope. This should be the lifetime of the value in result as well, but the result contains the correct value from outside of that nested scope.
Why does this code work correctly?
That's only true when talking about lifetimes derived from borrowing local variables. In this case, the relevant lifetime is the lifetime of the string data, which for string literals is 'static. In other words, the &strs are pointing to data stored elsewhere (in the static data segment, specifically), so they don't interact with stack lifetimes at all.
If we change the example slightly, we can induce the behaviour you're expecting:
fn main() {
let x = "abcd";
let result;
{
let y = "qwerty";
result = longest(&x, &y);
}
println!("The longest string is {} ", result);
}
fn longest<'a>(x: &'a &'static str, y: &'a &'static str) -> &'a &'static str {
if x.len() > y.len() {
x
} else {
y
}
}
Which fails to compile with:
error[E0597]: `y` does not live long enough
--> src/main.rs:6:35
|
6 | result = longest(&x, &y);
| ^ borrowed value does not live long enough
7 | }
| - `y` dropped here while still borrowed
...
10 | }
| - borrowed value needs to live until here
This fails because now we're talking about borrows into the stack.

Resources