0

I'm trying to build a graphics engine that works with the fairly common two-buffer pattern. One buffer (current_buffer) is displayed while the next (next_buffer) is prepared, then the next buffer is moved into the current buffer, and is subsequently repopulated by a new buffer, repeating.

I know there are a lot of other questions about cannot move out of borrowed content errors, so I spent a while looking at all I can find so far before finally resorting to asking this as a question.

Here's my code:

pub fn update(&mut self, dt: f64) {
    if self.is_change_buffer {
        self.current_buffer = self.next_buffer;
        self.next_buffer = Buffer { ... }; // New buffer
    }
}

I'm running into problems with moving self.next_buffer to self.current_buffer, which is understandable considering that self.current_buffer = self.next_buffer would break the "one owner" principle. How do I tell the borrow checker that I'm not trying to alias self.next_buffer, I'm trying to move it, and put something new in its place?

A caveat due to a framework I'm working with is that the function signature of update() must be:

pub fn (&mut self, dt: f64) -> ()

Also, a Buffer cannot be cloned. Because of the way Buffers are stored, cloning them would be an extremely expensive operation, and so cannot practically be done at every screen refresh.

TheEnvironmentalist
  • 2,694
  • 2
  • 19
  • 46
  • Why don't you have two concrete buffers and just swap a reference to them? [playground](https://play.rust-lang.org/?version=stable&mode=debug&edition=2015&gist=3a49b3d69d3ba612caaef67ce3a27f07) – Optimistic Peach Oct 27 '18 at 22:56

2 Answers2

4

How do I tell the borrow checker that I'm not trying to alias self.next_buffer, I'm trying to move it, and put something new in its place?

The borrow checker is not complaining about aliasing – it is complaining because you are moving a field out of a struct that you only borrowed, which is not allowed. However, moving out and immediately replacing with a new value is allowed, and there is a dedicated function for exactly this use case, called std::mem::replace(). With this function, your code becomes

self.current_buffer = mem::replace(&mut self.next_buffer, Buffer { ... });
Sven Marnach
  • 574,206
  • 118
  • 941
  • 841
  • Indeed your solution produces slightly shorter code than mine, so it will be a bit faster [playground](https://play.rust-lang.org/?version=nightly&mode=release&edition=2015&gist=bd47aad8cb3e07dee8f4aa2bdaf2b3db). – rodrigo Oct 27 '18 at 19:43
  • 1
    @rodrigo Yours assigns `self.next_buffer` twice – the first time in the `swap()` call, but it is immediately overwritten with the new buffer. This isn't really buffer swapping, since a new buffer is created. – Sven Marnach Oct 27 '18 at 20:23
1

Probably the easiest solution is to use std::mem::swap:

std::mem::swap(&mut self.next_buffer, &mut self.current_buffer);
self.next_buffer = Buffer { ... }; // New buffer

You can also assign first and then swap, but I find it more natural this way.

Shepmaster
  • 388,571
  • 95
  • 1,107
  • 1,366
rodrigo
  • 94,151
  • 12
  • 143
  • 190
  • I think `mem::replace()` would be more appropriate here. – Sven Marnach Oct 27 '18 at 19:12
  • @SvenMarnach: That works, but I like the ring of `swap` because this operation in double-buffer is usually called _swapping_ and sometimes the old buffer gets reused. With your suggestion it would look like `let next = std::mem::replace(&mut self.next_buffer, new Buffer { ... }); self.current_buffer = next;`, which looks nice too, if you don't plan to reuse the old buffer. – rodrigo Oct 27 '18 at 19:25
  • I'll just add this as a separate answer. :) – Sven Marnach Oct 27 '18 at 19:30