如何在Rust结构中一次更改两个字段?

时间:2017-08-18 15:15:26

标签: rust borrow-checker

考虑以下简单示例:

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;
}

使用Vec并不重要;关键是Bar有两个不实现Copy的字段。这不会编译:

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

不难理解为什么:通过移动bar.curr而不立即更换它,我们必须移动bar本身,我们不允许这样做,因为它& #39; s只是可变地借用,而不是实际拥有。

然而,这是一个非常常见的用例(在这种情况下 - 保持函数的最后两个输出,例如),我觉得必须有一个惯用的Rust用例。我意识到这可以通过使用单个元组(curr, prev)并立即分配它来解决,但是(假设函数foo在结构Bar被使用之后很久就被写了)重构可能非常令人沮丧。

一次分配两个值似乎不合法:代码(bar.prev, bar.curr) = (bar.curr, next)无法编译,因为左侧不是合法的左侧值。

以下代码编译有点有趣:

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
}

虽然行bar.prev = bar.curr似乎需要移动权限,但它似乎使用它们,如下一行{{1}如果移动了bar.curr = next,则不应该编译。

此外,如果您将bar输出,它将不再编译(移动后返回bar.curr = next),因此编译器似乎足够聪明找出如何解决这个问题(这些字段都会被稳定地分配),但不能为可变指针执行相同的任务。

所以我猜(a)这是一个错误,(b)这是一个已知的错误,(c)是否有解决方法所以我仍然可以用可变指针做这个?

2 个答案:

答案 0 :(得分:6)

使用std::mem::replacestd::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);
}
  

以下代码编译[...]

有点有趣

这是因为您拥有该结构,因此编译器可以安全地将其拆分。当结构被借用或在某种指针后面时,它不能这样做。关键问题是:如果你在修改过程中出现恐慌会发生什么(答案:调用堆栈中可能的代码更高可能会观察到无效值,Rust也不会允许这种情况发生)。

这不是一个错误,它只是Rust的工作方式。

答案 1 :(得分:2)

您可以使用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;
}