最近在自学 Rust ,因为之前粗略看过一些博客教程,所以这次直接看Programming Rust这本书的翻译版,在第 5 章“引用”中遇到一个例子,感觉不能理解。

首先看书中图 5-9 的所有权树,讲了对于可变和不可变引用时的访问关系,我以为自己理解了,可是后面看到这个例子,结果又看不明白,代码是这样的:

...
也可以再次借用一个可变引用:
let mut v = (136, 139);
let m = &mut v;
let m0 = &mut m.0;  // ok:从可变引用重新借用可变引用
*m0 = 137;
let r1 = &m.1;      // ok:从可变引用重新借用共享引用,并且和 m0 没有重叠部分
v.1;		        // error:通过其他路经访问仍然是禁止的
println!("{}", r1); // r1 在这里使用

其实从第一个 ok 注释那里我就没搞明白,不是说变量只能同时存在一个可变引用吗,为什么有可变引用(“引用”为名词)m 的情况下还能从 m 那再可变引用(“引用”为动词)一个 m0 ?

然后我问 gpt ,它是这么回答的:

...

嵌套 mut 引用的场景

假设你有一个可变变量,并希望对其创建嵌套的可变引用。以下 是一些常见的情况及其分析。

情况 1:对整个变量的可变引用,然后尝试对部分值的可变引用

fn main() {
    let mut x = (1, 2);

    let r1 = &mut x;       // 对整个变量 `x` 的可变引用
    let r2 = &mut r1.0;    // 尝试对 `x` 的一部分创建可变引用

    *r2 = 42;              // 修改部分值
    r1.1 = 99;             // 修改另一部分
}

错误原因: Rust 编译器会报错,提示 不能同时借用 x 的多个可变引用。这是因为:

r1 是对整个 x 的可变引用,它控制了 x 的独占访问权。 当你尝试对 r1.0 ( x 的某个字段)创建可变引用时,r1 的作用域依然活跃,Rust 无法区分你是否会同时操作 x 的其他部分。

错误示例的编译器消息(可能类似于):

error[E0499]: cannot borrow `r1.0` as mutable more than once at a time

...

但事实上,我尝试了类似代码,并不会报错:

fn main() {
    let mut v = (136, 139);
    let m = &mut v;
    let m0 = &mut m.0;
    *m0 = 137;
    m.1 = 42;
    println!("{:?}", v)
}

其运行结果是:

(137, 42)

很多资料都没有讲解关于这种嵌套的引用会发生什么结果,也不知道什么情况是可以什么是不行,我没看过这例子时还以为压根不能创建 m0 呢,所以想着找 GPT 问问,结果告诉的内容是错的。

请问该如何解释上面书中的代码,如何更好地理解 Rust 在这种嵌套情况下创建引用的做法是否成功?请各位赐教

举报· 413 次点击
登录 注册 站外分享
3 条回复  
aloxaf 小成 昨天 23:22
rust 的借用检查器一直在改进 它早期是基于词法作用域的,所以会出现你书里说的那种情况,你在 godbolt 里测试早期的 rust 版本就能看到报错了。当时写 rust 程序常常需要把一些创建临时引用的代码用大括号另起一个作用域,看起来莫名其妙的,就是为了规避这个问题。 后面引入了 non-lexical lifetimes (NLL) 版本,会智能分析这个引用和其他引用之间是否存在冲突,而不是粗暴地看作用域,大部分情况下都能给出复合直觉的结果。少量 edge case 可能要等下一代检查器 polonius 了,不过这几年都没啥动静。
w568w 小成 昨天 22:58
Check: https://doc.rust-lang.org/nomicon/borrow-splitting.html 我记得这个行为有一些严格的 references ,不过找不到了。翻一下 Language references 吧。 简单来说:借用检查器理解一些基本的东西,它确实充分理解 struct ,知道可以同时借用 struct 的不相交字段。 至于最后一个例子,Rust 的作用域是语义的,也就是说: fn main() { let mut v = (136, 139); let m = &mut v; let m0 = &mut m.0; *m0 = 137; // <-- 从这一行开始,不再使用 m0 ,因此可以理解为 m0 在这里生命结束 m.1 = 42; println!("{:?}", v) }
boxrq 初学 昨天 22:52
m0 不是对 v 的直接引用,而是对 m.0 的引用。 关于"只能同时存在一个可变引用"的规则: 这个规则其实更准确的表述应该是:对于同一块内存区域,在同一时间只能有一个可变引用。 想象一下现实生活中的场景: 1. 你有一间房子(原始值 v ) 2. 你把整个房子的管理权交给物业(可变引用 m ) 3. 物业可以再把某个房间的使用权分配给他人(重借用 m0 )
返回顶部