简体   繁体   中英

How to alter two fields at once in a Rust struct?

Consider the following simple example:

pub struct Bar {
    curr: Vec<i32>,
    prev: Vec<i32>,
}

pub fn main() {
    let mut b = Bar { curr: vec![1, 2, 3], prev: vec![2, 3, 4] };

    foo(&mut b);
}

pub fn foo(bar: &mut Bar) {
    let next = vec![3, 4, 5];

    bar.prev = bar.curr;
    bar.curr = next;
}

The use of Vec doesn't matter; the point is that Bar has two fields which don't implement Copy . This doesn't compile:

error[E0507]: cannot move out of borrowed content
  --> derp.rs:15:16
   |
15 |     bar.prev = bar.curr;
   |                ^^^ cannot move out of borrowed content

It's not hard to see why: by moving bar.curr without replacing it immediately, we have to move bar itself, which we're not allowed to do, as it's only borrowed mutably, not actually owned.

However this is a very common use case (in this case -- keeping the last two outputs of a function, for example) and I feel like there must be an idiomatic Rust use case. I realize this can be gotten around by using a single tuple (curr, prev) and assigning it at once, but (supposing the function foo was written long after the struct Bar has been in use) the refactoring could be pretty frustrating.

Assigning two values at once doesn't seem legal: the code (bar.prev, bar.curr) = (bar.curr, next) doesn't compile as the left side isn't a legal left-hand-side value.

It is somewhat interesting that the following code compiles:

pub struct Bar {
    curr: Vec<i32>,
    prev: Vec<i32>,
}

pub fn main() {
    let b = Bar { curr: vec![1, 2, 3], prev: vec![2, 3, 4] };

    foo(b);
}

pub fn foo(mut bar: Bar) -> Bar {
    let next = vec![3, 4, 5];

    bar.prev = bar.curr;
    bar.curr = next;

    bar
}

While the line bar.prev = bar.curr seems to require move privileges, it doesn't seem to use them, as the following line bar.curr = next shouldn't compile if bar has been moved.

Additionally, if you take the bar.curr = next line out , it no longer compiles ( bar is returned after being moved), so it seems like the compiler is smart enough to figure out how to resolve this issue (that the fields all end up stably assigned), but can't do the same task for mutable pointers.

So I guess (a) is this a bug, (b) is this a known bug, and (c) is there a workaround so I can still do this with mutable pointers?

Use std::mem::replace or std::mem::swap .

pub fn foo(bar: &mut Bar) {
    use std::mem;
    let next = vec![3, 4, 5];
    bar.prev = mem::replace(&mut bar.curr, next);
}

It is somewhat interesting that the following code compiles [...]

This is because you own the structure, so the compiler can safely break it apart. It can't do this when the structure is borrowed or behind some kind of pointer. The key question is: what happens if you panic half-way through a modification (answer: it's possible code higher in the call stack could observe the invalid value, and Rust won't allow that to happen).

This isn't a bug, it's just how Rust works.

You can use std::mem::swap :

pub fn foo(bar: &mut Bar) {
    let next = vec![3, 4, 5];

    std::mem::swap(&mut bar.prev, &mut bar.curr);
    bar.curr = next;
}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM