I've started studying Rust and I'm trying to implement a simple 1D cellular automata. I want to represent the automata state (Board
) as struct holding the size and two different vectors (of same size). I tried:
struct Board {
n: usize,
cur: Vec<u32>,
next: Vec<u32>,
}
impl Board {
fn new(size: usize) -> Board {
Board {
n: size,
cur: vec![0;size],
next: vec![0;size],
}
}
}
So far so good. I'm also able of mutating both vectors. But then I want to be able to swap both vectors (or rather their references), such as:
fn swap(&mut self) -> &Board {
let tmp = self.cur;
self.cur = self.next;
self.next = tmp;
self
}
It fails, with an cannot move out of borrowed content [E0507]
which I think I can understand. I also tried mem::swap
which I found in a similarly title question without success.
How can I make this example work ? (Since I'm a total beginner with Rust, don't hesitate to suggest a different data representation).
What's the problem?
You are punching holes in your data:
fn swap(&mut self) -> &Board {
let tmp = self.cur; // 1
self.cur = self.next; // 2
self.next = tmp; // 3
self
}
If we analyze line by line:
self.cur
is now uninitializedself.next
is now uninitializedIf for some reason computation is interrupted before line (3) has a change to tighten up the situation, self
is now poisoned and may cause all kinds of nasty things to happen. Notably, its destructor could attempt to free memory twice for example.
In theory, you could have the compiler check for temporary holes and prove, without doubt, that:
self
while a hole is punchedand indeed at some point it was considered... but the truth is that it's complicated and there are readily available work-arounds.
So?
The answer lies in std::mem
which exposes functions to perform such low-level operations in a safe manner. While the functions themselves are implemented using unsafe
under the hood, they lean on their understanding of the language and runtime to expose safe interfaces.
The two particular functions that will interest you are:
replace
: replaces the content of dest: &mut T
by src: T
and returns the what was previously contained behind dest
swap
: exchanges the content of its argumentsWith those two simple and safe primitives, you can avoid punching holes in your data.
As you noticed, mem::swap
is the way to go:
fn swap(&mut self) -> &Board {
std::mem::swap(&mut self.cur, &mut self.next);
self
}
This works. Note that when using .
you are dereferencing self
. So while self
has the type &mut Board
, self.cur
has the type Vec<u32>
. Therefore the compiler complained about "move out of borrowed content" and we need the additional &mut
s.
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