Reputation: 17276
iI am struggling to understand a particular instance of an ownership rule in Rust, particularly as I believe the example should compile when it does not.
I have something like this:
pub struct Structure {
pub collection1: VecDeque<something>,
pub collection2: VecDeque<something_else>,
}
impl Structure {
pub fn my_function(&mut self) {
for thing in self.collection1.into_iter() { // create consuming iterator
// do some stuff
}
}
}
This example paraphrases the actual production code I am working with but I believe this is enough to show the problem.
The compiler produces this error:
error[E0507]: cannot move out of `self.collection1` which is behind a mutable reference
I don't understand why exactly this is the case or what can be done about it.
Thinking about this, without considering ownership rules, as if we were in any other language (Hint: Maybe C++) we would come to this conclusion:
for thing in self.collection1.into_iter()
consumes the VecDeque
, and at the end of the function call self.collection1
is just empty.
This is the behaviour I want to achieve, and it feels like it should be a common thing to want to do and should be possible.
Thinking about things in terms of Rust ownership:
I would guess that collection1.into_iter()
transfers ownership of (moves) collection1
. Since we are operating on self
with a reference, clearly we can't move part of it, regardless of the mutability.
So then there should perhaps be a way to "swap" (again: thinking like C++) the contents of collection1
with a new VecDeque
which we can create at the start of my_function
. But I don't know how to do that. I tried de-structuring self
and then setting self
to a new instance of Structure
. This didn't compile.
At the end of the function call I want to replace self.collection1
with something else. This strongly suggests that I should somehow be able to move out the contents of self.collection1
and replace or re-assign it with some new thing.
Here's a sketch of how that might look like.
pub fn my_function(&mut self) {
let tmp = self.collection1;
self.collection1 = VecDeque::new();
for thing in collection1.into_iter() {
self.collection1.push_back(thing);
}
}
With this in mind, it's obvious this should compile into something sensible. But I don't know exactly how to do it. Perhaps I am missing something obvious?
While researching this question, I discovered that I can use std::mem::take
or std::mem::replace
to find a workable solution - but the questions still stands. Why doesn't the above work, when it seems intuitive that it should?
Here are those examples which do compile:
pub fn my_function(&mut self) {
// replaces self.collection1 with default value, ie: empty container
let tmp = std::mem::take(&mut self.collection1);
self.collection1 = VecDeque::new();
for thing in collection1.into_iter() {
self.collection1.push_back(thing);
}
}
// This also works
// replaces self.collection1 with something else
let tmp = std::mem::replace(&mut self.collection1, VecDeque::new());
I believe std::mem::swap
should also work, but I couldn't get this to compile:
let tmp = VecDeque::new();
std::mem::swap(self.collection1, tmp); // does not compile ? why ?
Upvotes: 2
Views: 107
Reputation: 27550
What you describe is called drain
in Rust.
pub struct Structure {
pub collection1: VecDeque<something>,
}
impl Structure {
pub fn my_function(&mut self) {
for element in self.collection1.drain(..) {
// do some stuff
}
}
}
Which can easily be found by looking through the methods on VecDeque
...
The problem with your second example is simply that this line:
let tmp = self.collection1;
is not allowed, and I don't see why it should be, if you want to exchange the value in collection1
you can use the functions you already found, so any complex compiler logic is just wasted. It's also obfuscating the swap
you're doing.
Rust often tends to be explicit rather than implicit in many places, this is one of them.
std::mem::swap(self.collection1, tmp); // does not compile ? why ?
doesn't compile because you're not giving mutable references to swap
(same reason your take
and remove
examples don't work either, among more issues) try
std::mem::swap(&mut self.collection1, &mut tmp);
Upvotes: 3