Mem::replace not mentioned in "The Book"

I only just discovered that to move a value out of a structure, due to Rust's ownership rules, you have to use something like std::mem::replace.

I think it's a little surprising this isn't mentioned in the book, that this cannot be done using simple assignment operations ( and instead a library call is needed ). I was also a little surprised that the compiler couldn't figure out that the equivalent sequence of assigns was "ok". That is:

let save = mem::replace( &mut self.x, X::new( ) );

is logically equivalent to this code that isn't allowed:

let save = self.x;
self.x = X::new();

Is it too hard for the compiler to figure out that the "moved out" value is over-written? Or perhaps is there some reason why it would be unsafe to permit this?

Maybe the book cannot cover everything, but my feeling is this rather basic operation ought to be covered? Is that unreasonable?

1 Like

No, that's only when you are trying to move out of a value behind a reference. The following works perfectly fine:

struct X;
struct Y {
    x: X,
}

let y = Y { x: X };
let field = y.x;

It would lead to reading uninitialized (moved-from) memory in the destructor of the value behind the reference if a panic occurred between moving out and reinitializing with a new value.

2 Likes

Yeah, mem::replace is easily one of the most underrated standard library functions, and I agree that it should be taught more. There have also been some other proposals to make it more prominent or more discoverable.

7 Likes

replace is great! Make sure to also get acquainted with its relatives swap and take.

Edit: You may also be interested in the interior mutable versions: Cell::replace, Cell::swap, Cell::take.

3 Likes

First, +1 to replace being super-important to Rust.

The core issue is in making sure that things are left in a good state in the presence of things like panics. x.y = foo(x.y);, for example, could leave x (or what it's referencing) in a bad state if the function panics.

Some of the cases Rust could probably do more work and allow, but it'd make it somewhat more complex to understand. So mem::takeing or similar -- to leave a valid value behind -- is generally good in that it's clear what's happening.

Here's an article on the topic: Moving from borrowed data and the sentinel pattern

1 Like

Thanks everyone for the excellent replies, it helps to know that I am not going mad ( a feeling I sometimes get when learning Rust ). I do feel it's difficult to guess you need use std::mem when encountering the issue ( of course a search engine often helps, which is how I found the solution, but that seems a bit of a cheat ). Perhaps the compiler error message could hint at the solution?

1 Like

I do feel like it’s indeed a reasonable idea to change a compiler error message like

error[E0507]: cannot move out of `*y` which is behind a mutable reference
 --> src/main.rs:5:13
  |
5 |     let z = *y;
  |             ^^
  |             |
  |             move occurs because `*y` has type `Box<i32>`, which does not implement the `Copy` trait
  |             help: consider borrowing here: `&*y`

For more information about this error, try `rustc --explain E0507`.

(on this playground example)
to contain a mention of std::mem::replace under help:, too.

At least rustc --explain E0507 does already mention it, though. Admitted, you have to scroll down a bit to get to the relevant section

Moving a member out of a mutably borrowed struct will also cause E0507 error:

struct TheDarkKnight;

impl TheDarkKnight {
    fn nothing_is_true(self) {}
}

struct Batcave {
    knight: TheDarkKnight
}

fn main() {
    let mut cave = Batcave {
        knight: TheDarkKnight
    };
    let borrowed = &mut cave;

    borrowed.knight.nothing_is_true(); // E0507
}

It is fine only if you put something back. mem::replace can be used for that:

use std::mem;

let mut cave = Batcave {
    knight: TheDarkKnight
};
let borrowed = &mut cave;

mem::replace(&mut borrowed.knight, TheDarkKnight).nothing_is_true(); // ok!

For more information on Rust's ownership system, take a look at the
References & Borrowing section of the Book.

3 Likes

Mea culpa, although I have used --explain in the past, I didn't think to check it here. You live and learn.

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.