I have a struct with a function next()
(similar to iterators but not an iterator). This method returns the next state after modification (preserving the original state). So: fn next(&A) -> A
.
I started with a simple struct where I didn't need a lifetime (struct A in the example) and I extended it to add a reference to a new struct (struct B).
The problem is that I now need to specify the lifetime for my struct and for some reason my method next()
refuses to work anymore.
I suspect that the lifetime of the new struct of every iteration is limited to the scope where it is created and I cannot move it outside of this scope.
Is it possible to preserve the behavior of my next()
method?
Try it here
#[derive(Clone)]
struct A(u32);
#[derive(Clone)]
struct B<'a>(u32, &'a u32);
impl A {
fn next(&self) -> A {
let mut new = self.clone();
new.0 = new.0 + 1;
new
}
}
impl<'a> B<'a> {
fn next(&self) -> B {
let mut new = self.clone();
new.0 = new.0 + 1;
new
}
}
fn main() {
let mut a = A(0);
for _ in 0..5 {
a = a.next();
}
let x = 0;
let mut b = B(0, &x);
for _ in 0..5 {
b = b.next();
}
}
The error is:
error[E0506]: cannot assign to `b` because it is borrowed
--> src/main.rs:31:9
|
31 | b = b.next();
| ^^^^-^^^^^^^
| | |
| | borrow of `b` occurs here
| assignment to borrowed `b` occurs here
The problem is here:
impl<'a> B<'a> {
fn next(&self) -> B {
let mut new = self.clone();
new.0 = new.0 + 1;
new
}
}
You didn't specify a lifetime for B
, the return type of next
. Because of Rust's lifetime elision rules, the compiler infers that you intended this:
impl<'a> B<'a> {
fn next<'c>(&'c self) -> B<'c> {
let mut new = self.clone();
new.0 = new.0 + 1;
new
}
}
Which means that the return value may not outlive self
. Or, put another way, self
has to live longer than the B
that is returned. Given the body of the function, this is a completely unnecessary requirement because those references are independent of each other. And it causes a problem here:
for _ in 0..5 {
b = b.next();
}
You are overwriting a value that the borrow-checker thinks is still borrowed by the call to next()
. Inside next
we know that there is no such relationship – the lifetime annotations do not reflect the constraints of what you're actually doing.
So what are the lifetime bounds here?
The lifetimes of references to B
are unrelated - each can exist without the other. So, to give the most flexibility to a caller, the lifetime of B
should be different from the lifetime of the reference to self
in next
.
However, each B
that is created with next()
holds a reference to the same u32
as is held by self
. So the lifetime parameter that you give to each B
must be the same.
Using explicitly named lifetimes, this is the result of combining both of those things:
impl<'a> B<'a> {
fn next<'c>(&'c self) -> B<'a> {
let mut new = self.clone();
new.0 = new.0 + 1;
new
}
}
Note that — even though the reference to self
here has lifetime 'c
— the type of self
is B<'a>
, where 'a
is the lifetime of the &u32
inside. Just the same as the return value.
But actually, the 'c
can be elided. So it's really just the same as this:
impl<'a> B<'a> {
fn next(&self) -> B<'a> {
let mut new = self.clone();
new.0 = new.0 + 1;
new
}
}
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