程序如下:
use std::cell::RefCell;
#[derive(Debug)]
struct Data {
id: usize,
val: [u32; 10],
}
fn main() {
let mut data = Data {
id: 0,
val: [0; 10],
};
let mut p_data = &mut data;
p_data.val[p_data.id] = 10;
println!("{:?}", data);
let rdata = RefCell::new(Data {
id: 0,
val: [0; 10],
});
let mut p_rdata = rdata.borrow_mut();
p_rdata.val[p_rdata.id] = 10;
println!("{:?}", rdata);
}
我想请教一下,为什么使用Refcell包裹以后,系统会报错:
196 | p_rdata.val[p_rdata.id] = 10;
| ------------^^^^^^^----
| | |
| | immutable borrow occurs here
| mutable borrow occurs here
| mutable borrow later used here
|
不使用Refcell的话,可以正常编译运行。 本人rust小白,感谢大家的帮助
1
共 7 条评论, 1 页
评论区
写评论大家都给的最简单的办法:
嗯,这也是推荐做法。代码逻辑更清晰,意图更明显。
为什么不能直接
p_rdata.val[p_rdata.id] = 10
呢主要是因为 Deref / DerefMut 带来的间接性,这是 MIR 中关键的部分:
嗯,它为什么不能是两阶段借用呢?两阶段借用的 MIR 例子 有这样 和 这样的,里面的细节真的很微妙,真的不值得 Rust 的使用者去仔细研究。
有没有其他做法呢
经验的做法是基于 slice,因为它的 Index / IndexMut 是编译器内置的操作
playground
其 MIR 关键部分:
想要弄懂这些细节真的很麻烦(我也不保证上面的注释没问题),要知道这里的两种语法糖(Deref(Mut) 和 IndexMut),还要拆解借用。这几乎等价于下面的操作:
重点是什么(初学者忠告)
仔细看了下发现这个问题还是挺有意思的,之前的回答都没办法解释为什么
&mut Data
和RefMut<Data>
在P.val[P.id] = 10
这个表达式上的差异.先看看为啥
&mut T
能够支持P.val[P.id] = 10
的写法,直观上看同一行出现了对P的可变和不可变引用,但是实际上可变引用和不可变引用发生在Data的不同字段上,rust通过splitting borrows
机制放行了这种代码,可以参考这个链接https://doc.rust-lang.org/nomicon/borrow-splitting.html;猜测
RefMut<T>
不支持P.val[P.id]
是因为编译器没有办法对RefMut应用splitting borrows
机制,除了显式拆分不可变引用(let id = P.id
),应该也可以直接用可变引用的方式让编译器开心:let p_rdata = &mut *p_rdata;
看错报错行了,我提出的问题只有在这个修复这个报错以后运行时才能体现; 其他人已经提到过了把对p_rdata.id单独成行拷出来; 我自己的经验是同一行某个变量的可变和不可变引用是可以同时出现的,只要在执行流程上两者不会同时存在就好,比如:
第二行就同时出现了a的可变和不可变引用。 不过参数比较多的时候判断起来比较麻烦,一般直接写出来,编译报错改了就好(面向编译器编程🤦).
--
👇
tottiandlsg: 非常感谢您的回复! 我可不可以这样理解,原则是不允许同时操作同一个可变引用的,但是对于结构体的不同成员,其内存地址是不一样的,编译器可以推断出其不会造成内存安全,所以不会报错。但是使用RefCell进行包裹后,编译器会将其整体看待,认为是在同时操作同一内存,所以会报错。
--
👇
aj3n: 因为原始的引用类型(&T/&mut T)的失效编译器能帮你推断出来, 但是RefCell的引用类型(Ref/RefMut)则没有特殊对待, 需要手动在
p_rdata.val[p_rdata.id] = 10;
之后加一句drop(p_rdata); 可以了解下关键词non-lexical lifetimes, drop scope;非常感谢您的回复! 我可不可以这样理解,原则是不允许同时操作同一个可变引用的,但是对于结构体的不同成员,其内存地址是不一样的,编译器可以推断出其不会造成内存安全,所以不会报错。但是使用RefCell进行包裹后,编译器会将其整体看待,认为是在同时操作同一内存,所以会报错。
--
👇
aj3n: 因为原始的引用类型(&T/&mut T)的失效编译器能帮你推断出来, 但是RefCell的引用类型(Ref/RefMut)则没有特殊对待, 需要手动在
p_rdata.val[p_rdata.id] = 10;
之后加一句drop(p_rdata); 可以了解下关键词non-lexical lifetimes, drop scope;一般来说的经验就是borrow_mut等可变引用不能同时出现在同一行。
既然id可以copy,那先把id给copy出来再运算就可以了。
不过在使用borrow_mut的时候最好用作用域包起来或者用完就drop掉,免得后面再用麻烦,比如这个例子,不drop掉,打印都打印不出来,只能打印出一个borrow的状态。
不知道这么理解的对不对。
p_rdata.val[p_rdata.id]
,p_rdata已经被p_rdata.val(deref_mut)借用为&mut self了,然后p_rdata.id(deref)借用不可变引用,然后报错了。可以这么改,这会先通过(*p_rdata.deref()).id获取到id,然后再使用。id字段实现了copy。
因为原始的引用类型(&T/&mut T)的失效编译器能帮你推断出来, 但是RefCell的引用类型(Ref/RefMut)则没有特殊对待, 需要手动在
p_rdata.val[p_rdata.id] = 10;
之后加一句drop(p_rdata); 可以了解下关键词non-lexical lifetimes, drop scope;