一个 second mutable borrow 的错误

2021-06-23 15:53:11 +08:00
 xiaopanzi

学 Rust 有点走火入魔了,感觉这是一个简单的 Bug,但还解决不了。代码放在 Rust Playground

内容其实是设计模式中 Facade Pattern 的实现:这里把无关代码都删了,Tuner 用来调频,Amplifier 是放大器,HomeTheater 中有 Tuner 和 Amplifier 。

最后在测试的 main:

let amp = Amplifier::new("Amplifier");
let tuner = Tuner::new("AM/FM Tuner");

let mut home_theater = HomeTheaterFacade::new(amp, tuner);
home_theater.listen_radio();
home_theater.stop_radio();
}

这样写的话就报错:cannot borrow home_theater as mutable more than once at a time 。当然,我只写 listen_radiostop_radio 就没有问题。


对比下面的代码,

#[derive(Default)]
struct Foo {
    i: i32,
    j: i32,
}
impl Foo {
    fn set_i(&mut self, i: i32) {
        self.i = i;
    }
    fn set_j(&mut self, j: i32) {
        self.j = j;
    }
}

感觉下面的代码和前面的类似,为什么没有 second mutable borrow 的错误?

let mut foo = Foo::default();
foo.set_i(10);
foo.set_j(64);
1432 次点击
所在节点    Rust
8 条回复
hotdogwc
2021-06-23 16:10:08 +08:00
因为你 listen_radio() 的 borrow 生命周期和 home_theater 是一样长的,borrow checker 认为你的 mutable borrow 没结束,你调换一下 listen_radio() 和 stop_radio() 的调用顺序就不报错了啊
hotdogwc
2021-06-23 16:13:49 +08:00
并且你 Foo 那个例子,给 set_i 的 mutable borrow 一个和 foo 一样的生命周期,也会报错,跟你的代码情况一样
xiaopanzi
2021-06-23 16:16:28 +08:00
@hotdogwc 谢谢大佬。再请问一下,如果想在 listen_radio 里面实现 self.amp.set_tuner(&self.tuner),应该如何指定生命周期?
hotdogwc
2021-06-23 16:33:10 +08:00
xiaopanzi
2021-06-23 16:42:53 +08:00
@hotdogwc 多谢。我好好研究一下。发现把(引用和生命周期)全部魔改成 Rc<RefCell<Tuner>>,代码就好写多了。
Jirajine
2021-06-23 17:02:48 +08:00
根本原因是你创建了一个 self referential struct,你希望 Amplifier<'a>的'a 对应 tunner,但你创建 HomeTheaterFacade<'_>的时候'a 已经被填充为 HomeTheaterFacade 的 lifetime 了。而 safe rust 正常来说是不允许你创建 self referential struct 的,因为一 move 引用就失效了。
解决办法是要么用 raw pointer,并且保证 strcut 不要 move 。要么用 Pin 、Rc,这两者都需要分配到堆上。
irytu
2021-06-23 17:19:50 +08:00
还是整体代码组织的问题 重构吧 也许你说的用 refcell 去 borrow_mut 也行 不过最好还是把整体的思路改一下
PTLin
2021-07-06 15:36:44 +08:00
你这个问题其实精简一下是这样。
```rust
let mut a:(Vec<i32>,Option<&Vec<i32>>) = (vec![1], None);
a.1=Some(&a.0);
let b=&mut a;
println!("{:?}", b);
```
第二行过后,只要 a 还活着就持有 a.0 的一个引用,这时候就不能以任何的途径对 a.0 取可变引用。
所以第三行不会通过编译,因为这在你还对 a.0 有引用的情况下,又对 a.0 取了可变引用(因为使用 a 也能触及到 a.0),相反第三行可以取 a.1 的可变引用。
a 的两条路径每一条都可以单独操作,但是要是直接操作 a 就相当于直接操作了两个路径。

这是一个专为移动设备优化的页面(即为了让你能够在 Google 搜索结果里秒开这个页面),如果你希望参与 V2EX 社区的讨论,你可以继续到 V2EX 上打开本讨论主题的完整版本。

https://www.v2ex.com/t/785329

V2EX 是创意工作者们的社区,是一个分享自己正在做的有趣事物、交流想法,可以遇见新朋友甚至新机会的地方。

V2EX is a community of developers, designers and creative people.

© 2021 V2EX