Reputation: 13053
I am encountering an issue where the compiler says that data is borrowed mutably more than once. I am borrowing the data in a loop, however the borrows are only used locally in the loop, and so the next iteration the borrow should have finished.
The (for me) confusing thing is, that it works for a local variable, but not when the borrow happens through a method defined in a trait, and the variable is passed as a mutable reference to a generic typed parameter that is bounded to that trait.
The following code is a simplified example, but shows the exact same error:
trait GetElem<'a, T> {
type ElemRef: Deref<Target=T>;
fn get_at(&'a mut self, index: usize) -> Self::ElemRef;
}
struct Ref<'a, T>(&'a T);
impl<'a, T> Deref for Ref<'a, T> {
type Target = T;
fn deref(&self) -> &Self::Target {
self.0
}
}
impl<'a, T: 'a> GetElem<'a, T> for Vec<T> {
type ElemRef = Ref<'a, T>;
fn get_at(&'a mut self, index: usize) -> Self::ElemRef {
Ref(self.get(index).unwrap())
}
}
fn test<'a, C: 'a + GetElem<'a, i32>>(passed_data: &'a mut C) {
let mut local_data = vec![1, 2];
let x = local_data.get_at(0);
let tmp = *x;
let y = local_data.get_at(1);
let _ = tmp + *y;
let x = passed_data.get_at(0);
let tmp = *x;
let y = passed_data.get_at(1);
let _ = tmp + *y;
}
Or see it on the playground
So, in the example, I have both a passed_data
(which is behind a mutable reference to a type implementing the GetElem<i32>
trait) and a local_data
, which is a Vec<i32>
.
For both, I attempt to do the same thing: get a mutable reference to the first element, copy the value to a temporary variable, get a mutable reference to the second value, and then compute the sum of both.
This works for the local_data
, but fails for the passed_data
with the following error:
error[E0499]: cannot borrow `*passed_data` as mutable more than once at a time
Upvotes: 0
Views: 183
Reputation: 70980
&'a mut self
is an antipattern.
You tell the compiler "I borrow self
, and I can keep the reference alive as long as 'a
". And 'a
is defined in the struct (or the trait), that means you can use the struct only once.
You do not want that. You wanted to say, "I borrow self
, and I return a type that is related to this borrow". If this was a reference, you wouldn't have a problem. It was fn get_at<'a>(&'a mut self) -> &'a T
(which is another antipattern, BTW, downgrade &mut
to &
. But there are times you want that). Or just fn get_at(&mut self) -> &T
, using lifetime elision. If this was a struct, like Ref<'a, T>
, You also wouldn't have a problem - just return fn get_mut(&mut self) -> Ref<'_, T>
(or without elision fn get_mut<'a>(&'a mut self) -> Ref<'a, T>
. But this is not one of those. This is an associated type.
You want to be generic over a lifetime in an associated type. This is called GAT (Generic Associated Types). And is not possible in stable Rust, unfortuantely. It is possible with nightly:
#![feature(generic_associated_types)]
use std::ops::Deref;
trait GetElem<T> {
type ElemRef<'a>: Deref<Target = T> + 'a
where
Self: 'a;
fn get_at(&mut self, index: usize) -> Self::ElemRef<'_>;
}
struct Ref<'a, T>(&'a T);
impl<T> Deref for Ref<'_, T> {
type Target = T;
fn deref(&self) -> &Self::Target {
self.0
}
}
impl<T> GetElem<T> for Vec<T> {
type ElemRef<'a>
where
T: 'a,
= Ref<'a, T>;
fn get_at(&mut self, index: usize) -> Self::ElemRef<'_> {
Ref(self.get(index).unwrap())
}
}
And here's how you use it:
fn test<C: GetElem<i32>>(passed_data: &mut C) {
let mut local_data = vec![1, 2];
let x = local_data.get_at(0);
let tmp = *x;
let y = local_data.get_at(1);
let _ = tmp + *y;
let x = passed_data.get_at(0);
let tmp = *x;
drop(x);
let y = passed_data.get_at(1);
let _ = tmp + *y;
}
You may wonder why I need drop()
(a new scope for x
will do, too, but I prefer this form). Well, there is no reason why I would not need. Pre-NLL (Non-Lexical Lifetimes), you would need that for references too - the reference drops at the end of the scope, and only then the borrow is free. However, NLL improved this, because if I don't use the reference anymore - there isn't a reason to not consider it as dropped. However, with structs this is not true: a struct can implement Drop
and watch the reference there, and the compiler can't drop it earlier because drop order is guaranteed.
Upvotes: 2