【问题标题】:How to alter two fields at once in a Rust struct?如何在 Rust 结构中一次更改两个字段?
【发布时间】:2017-08-18 15:15:25
【问题描述】:

考虑以下简单示例:

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 本身,我们不允许这样做,因为它只是可变借用,而不是实际拥有。

但是,这是一个非常常见的用例(在这种情况下——例如,保留函数的最后两个输出),我觉得必须有一个惯用的 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 行似乎需要 移动权限,但它似乎并没有使用它们,因为如果以下行bar.curr = next 不应该编译bar 已被移动。

另外,如果你把bar.curr = nextout,它不再编译(bar 在被移动后返回),所以看起来编译器足够聪明,可以弄清楚如何解决这个问题(所有字段最终都被稳定分配),但不能对可变指针执行相同的任务。

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

【问题讨论】:

    标签: rust borrow-checker


    【解决方案1】:

    使用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 的工作原理。

    【讨论】:

    • 学究式地,这不允许您一次修改同一个结构中的两个字段——关于如何做到这一点的任何想法?
    【解决方案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;
    }
    

    【讨论】:

    • 学究式地,这不允许您一次修改同一个结构中的两个字段——关于如何做到这一点的任何想法?
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2018-02-09
    • 1970-01-01
    • 2016-09-05
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多