为什么我不能连续两次调用可变方法(并存储结果)?

7
在下面的例子中:
struct SimpleMemoryBank {
    vec: Vec<Box<i32>>,
}

impl SimpleMemoryBank {
    fn new() -> SimpleMemoryBank {
        SimpleMemoryBank{ vec: Vec::new() }
    }

    fn add(&mut self, value: i32) -> &mut i32 {
        self.vec.push(Box::new(value));
        let last = self.vec.len() - 1;
        &mut *self.vec[last]
    }
}

fn main() {
    let mut foo = SimpleMemoryBank::new();

    // Works okay
    foo.add(1);
    foo.add(2);

    // Doesn't work: "cannot borrow `foo` as mutable more than once at a time"
    let one = foo.add(1);
    let two = foo.add(2);
}

add()可以连续调用多次,只要我不存储函数调用的结果。但是如果我存储函数的结果(let one = ...),那么就会出现错误:

problem.rs:26:15: 26:18 error: cannot borrow `foo` as mutable more than once at a time
problem.rs:26     let two = foo.add(2);
                            ^~~
problem.rs:25:15: 25:18 note: previous borrow of `foo` occurs here; the mutable borrow prevents subsequent moves, borrows, or modification of `foo` until the borrow ends
problem.rs:25     let one = foo.add(1);
                            ^~~
problem.rs:27:2: 27:2 note: previous borrow ends here
problem.rs:17 fn main() {
...
problem.rs:27 }
              ^
error: aborting due to previous error

这是否是问题#6393:借用作用域不应总是词法的体现?

我该如何解决这个问题?本质上,我想将一个新的Box添加到向量中,然后返回对它的引用(以便调用者可以使用它)。


1
Downvoter:您能否解释一下我如何改进这个问题? - Cornstalks
1个回答

12

这正是Rust的设计目标所在,防止你发生该问题。如果你真的这么做了,会发生什么呢:

let one = foo.add(1);
foo.vec.clear();
println!("{}", one);

如果foo.add通过将新值推送到向量的开头来工作会怎样呢?会发生糟糕的事情!主要问题是,当你占用一个变量的借用时,你不能再改变这个变量。如果你能够改变它,那么你可能会使得内存失效,然后你的程序可能会做一些事情,最好的情况是崩溃。

这是否与问题#6393的表现相同:借用范围不应始终为词法作用域?

有点像,但并不完全是。在这个例子中,你从未使用过onetwo,因此理论上非词法作用域可以让它编译通过。但是,你接下来声明:

我想向向量添加一个新的Box,然后返回一个对它的引用(以便调用者可以使用它)

这意味着你真正的代码应该是:

let one = foo.add(1);
let two = foo.add(2);
do_something(one);
do_something(two);

因此,变量的生命周期将会重叠。

在这种情况下,如果你只是想要一个存储变量的地方,这些变量不能被单独释放,不重叠且无法移动,请尝试使用TypedArena

extern crate arena;

use arena::TypedArena;

fn main() {
    let arena = TypedArena::new();
    let one = arena.alloc(1);
    let two = arena.alloc(2);

    *one = 3;
    println!("{}, {}", one, two);
}

啊,你说得对!我忘了 foo.vec 可以直接访问。 - Cornstalks
哦哦哦,TypedArena 看起来非常有用。不幸的是我只能给你一个赞 :( - Cornstalks
1
您IP地址为143.198.54.68,由于运营成本限制,当前对于免费用户的使用频率限制为每个IP每72小时10次对话,如需解除限制,请点击左下角设置图标按钮(手机用户先点击左上角菜单按钮)。 - Cornstalks
1
@Cornstalks,这是一个很好的观点,从概念上讲你是对的。就我所见,Rust并不区分栈分配和堆分配(Box),而是将生命周期简单地分组成层次结构。如果你确实知道自己处于这种情况下,那么你可以使用unsafetransmute来指示这一点(这在概念上类似于TypedArena的做法)。 - Shepmaster

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接