< 返回版块

leolee0101 发表于 2023-02-25 13:47

rfc 2049-nll中,Reborrow constraints 节 的 Example 3,

let foo = Foo { ... };
let p: &'p mut Foo = &mut foo;
let q: &'q mut &'p mut Foo = &mut p;
let r: &'r mut Foo = &mut **q;
use(*p); // <-- This line should result in an ERROR
use(r);

对于为什么 r 的使用 需要 extend p 的 life , 我的理解是,r 重借用 的对象 foo 的原始借用是 p,r是 (隐式)间接 借用 foo (r是借用的 **q,而不是foo,尽管逻辑上是)。r需要p来表达自己 对 foo 的借用,所以 r live 的地方,p 就需要存在。 但是 为什么 q 的 life 也需要 extend ?

文中对这个的 解释,有点看不懂。

"This use of r must extend the lifetime of the borrows used to create both p and q. Otherwise, one could access (and mutate) the same memory through both *r and *p. (In fact, the real rustc did in its early days have a soundness bug much like this one.)"。

这句话是说,同时存在两种 可变借用 可以修改引用对象的途径 吗。可是 extend p 的 life ,不是依然是有两种修改引用对象的途径吗。

"When we create r by borrowing **q, that is the last direct use of q -- so you might think we can release the lock on p, since q is no longer in (direct) use. However, that would be unsound, since then r and *p could both be used to access the same memory. The key is to recognize that r represents an indirect use of q (and q in turn is an indirect use of p), and hence so long as r is in use, p and q must also be considered "in use" (and hence their "locks" still enforced)."

"since then r and *p could both be used to access the same memory" 是对 "*r and *p" 的笔误吗。

另外,当last direct use of q 后,如果 释放p上的锁,也只是和p本身的访问修改有关。这和 “用 *r ,*p 可以访问同一内存” 有什么关系。

评论区

写评论
作者 leolee0101 2023-02-26 22:17

嗯,明白了,不胜感谢!!

👇
苦瓜小仔: ```text

我只能解释到这了,看不懂别找我。。。

苦瓜小仔 2023-02-26 22:14
从代码的角度看(你的代码正常情况下是从上到下的顺序)
------- p
---- q  |
-- r |  |
   | |  |
-- r |  |
---- q  |
------- p

栈借用(当 r 在栈顶时,你要使用 q,意味着把 r 弹出;同理,要使用 p,则它必须在栈顶)
一摞杯子/碗,够形象了吧。。。whatever, a stack :)
| | |  | | |
| | |  | | |
| | r--r | |
| q------q |
p----------p

我只能解释到这了,看不懂别找我。。。

苦瓜小仔 2023-02-26 22:06

在一个堆栈里的是 同一个内存位置的吧

这个“栈”只是模型抽象。。。不是实际内存相关的栈。每次可用的独占借用,只能从一堆的最上面用,你要拿堆中间的,必须把其上的独占借用移除(弹出栈)才行。

嗯,我说的堆不是内存相关的堆,那只是一个量词。。。一堆碟子你能直接从中间拿一个吗?

q 也算 借用 foo 的吗?

这还用问吗。。。p 和 q 对不同的值独占借用,那分析有什么意义???谁也不打扰谁啊。

不管经过多少层解引用,最终能访问那个位置,都算在一个堆栈里

我这里只列的独占借用存在的情况。。。

如果在独占的栈借用存在时,使用共享借用,则把这个借用栈上的所有独占借用移除,把共享借用放置于栈上,所以这意味着在这个共享借用之前的独占借用都不再可用。

作者 leolee0101 2023-02-26 21:44

在一个堆栈里的是 同一个内存位置的吧,q 也算 借用 foo 的吗?不管经过多少层解引用,最终能访问那个位置,都算在一个堆栈里,是吧

--
👇
苦瓜小仔: 这是我现在的心智模型(结合栈借用)

苦瓜小仔 2023-02-26 21:31

这是我现在的心智模型(结合栈借用)

注意:栈借用与 NLL 并无实际联系,栈借用是对现有 Rust 借用行为的建模方式,而 NLL 是当前 Rust 借用的实现。


fn main() {
    let mut foo = Foo;
    {
        let mut p /* : &'p mut Foo         */ = &mut foo; // (1)
        let q     /* : &'q mut &'p mut Foo */ = &mut p;   // (2)
        use_foo_exclusively(q);                           // (3)
        let r     /* : &'r mut Foo         */ = &mut **q; // (4)
        use_foo_exclusively(r);                           // (5)
        use_foo_exclusively(p);                           // (6)
        //use_foo_exclusively(r); // 别这样用
    }
}

struct Foo;
fn use_foo_exclusively(_: &mut Foo) {}

针对独占借用:

栈底 -> 栈1 -> 栈2 -> ... -> 栈顶

其中栈顶的独占借用才是真正能用的(或者说能被显式使用的,显式使用指在源代码中使用)

序号 p q r
(1) alive (栈顶) - -
(2) 和 (3) alive (栈底) alive (栈顶) -
(4) 和 (5) alive (栈底) alive (栈 1) alive (栈顶)
(6) alive (栈底) alive (栈顶)[^q] dead

[^q]: 所以你可以在 (6) 写 use_foo_exclusively(q)。但写 use_foo_exclusively(p) 表示把 p 放置于栈顶(因为你正在使用它),从而 q 从栈上弹出,不能再被使用。同理,这也是那行“别这样用的”原因。

作者 leolee0101 2023-02-26 20:17

@ zhylmzr ,@苦瓜小仔 。大佬, 能指点下不

👇
leolee0101: 使用 *p 不仅需要访问p本身,还需要p有访问foo的能力。此时,r借走了p的可变访问能力,即使p上没有锁,不是 也不能使用 *p,吗

👇
zhylmzr:

如果有锁的话就无法访问p了,释放了锁就可以通过 *p 和 *r 访问同一块也就是 foo 的内存了

作者 leolee0101 2023-02-26 05:34

使用 *p 不仅需要访问p本身,还需要p有访问foo的能力。此时,r借走了p的可变访问能力,即使p上没有锁,不是 也不能使用 *p,吗

👇
zhylmzr:

如果有锁的话就无法访问p了,释放了锁就可以通过 *p 和 *r 访问同一块也就是 foo 的内存了

作者 leolee0101 2023-02-26 04:07

嗯,之前在理解的时候,有个误区,认为 可变借用 p 在life 内 都拥有 可变访问权力。但其实 r 已经借走了p的可变访问能力,此时,p尽管 life 延长了,其实并没有 可变访问能力。

👇 zhylmzr:

可是 extend p 的 life ,不是依然是有两种修改引用对象的途径吗。

怎么有两种修改引用的途径?Rust的各种规则就是为了避免数据竞争。

zhylmzr 2023-02-25 21:45

Because dereferencing a mutable reference does not stop the supporting prefixes from being enumerated, the supporting prefixes of **q are **q, *q, and q. 参考

let r: &'r mut Foo = &mut **q;

可以理解这段代码会依次枚举 q, *q, **q, 如果其中有引用生命周期的话,那么就会扩大它们的生命周期(原因在于r使用的时候,它引用的生命周期一定不能失效),也就是 'r: 'q: 'p

可是 extend p 的 life ,不是依然是有两种修改引用对象的途径吗。

怎么有两种修改引用的途径?Rust的各种规则就是为了避免数据竞争。

"since then r and *p could both be used to access the same memory" 是对 "*r and *p" 的笔误吗。

应该是的,r, p 都是指向 foo 的指针。

另外,当last direct use of q 后,如果 释放p上的锁,也只是和p本身的访问修改有关。这和 “用 *r ,*p 可以访问同一内存” 有什么关系。

如果有锁的话就无法访问p了,释放了锁就可以通过 *p 和 *r 访问同一块也就是 foo 的内存了

作者 leolee0101 2023-02-25 19:32

感谢 大佬的详细示例,马上去学习理解。

--
👇
苦瓜小仔: 你只要遵照下面的代码示例来理解和使用独占引用、共享引用就好了

苦瓜小仔 2023-02-25 16:35

你只要遵照下面的代码示例来理解和使用独占引用、共享引用就好了

fn main() {
    let mut foo = Foo;
    {
        let mut p /* : &'p mut Foo         */ = &mut foo;
        let q     /* : &'q mut &'p mut Foo */ = &mut p;
        use_foo_exclusively(q);
        let r     /* : &'r mut Foo         */ = &mut **q;
        use_foo_exclusively(r);
        use_foo_exclusively(p);
        //use_foo_exclusively(r); // 别这样用
    }

    { // 顺便给一下你选的段落的上下文的例子
        let mut r_a = &foo;
        let r_b = &r_a;
        let r_c = &**r_b;
        let foo2 = Foo;
        r_a = &foo2;
        use_ref_foo(r_c);
        use_ref_foo(r_a);
    }
}

struct Foo;
fn use_foo_exclusively(_: &mut Foo) {}
fn use_ref_foo(_: &Foo) {}

取消注释的话:

error[E0499]: cannot borrow `*p` as mutable more than once at a time
  --> src/main.rs:12:29
   |
8  |         let q     /* : &'q mut &'p mut Foo */ = &mut p;
   |                                                 ------ first mutable borrow occurs here
...
12 |         use_foo_exclusively(p);
   |                             ^ second mutable borrow occurs here
13 |         use_foo_exclusively(r);
   |                             - first borrow later used here

如果还想了解的话,看完 NLL 就去看栈借用 (stack borrows) 吧。

1 共 11 条评论, 1 页