Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to swap two fields of a struct

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).

like image 700
paradigmatic Avatar asked Feb 26 '16 11:02

paradigmatic


2 Answers

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:

  1. self.cur is now uninitialized
  2. self.next is now uninitialized
  3. everything is kosher again

If 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:

  • no function accesses self while a hole is punched
  • by the end of the scope, whether it is reached normally or by unwinding, the holes are filled up again

and 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 arguments

With those two simple and safe primitives, you can avoid punching holes in your data.

like image 118
Matthieu M. Avatar answered Nov 15 '22 07:11

Matthieu M.


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 &muts.

like image 21
Lukas Kalbertodt Avatar answered Nov 15 '22 08:11

Lukas Kalbertodt