< 返回版块

0xe994a4 发表于 2023-05-16 09:46

fn main() {
    let mut data = 10;
    // 第一次可变借用 data 
    let ref1 = &mut data;
    // 对 ref1 解引用后,第二次可变借用 data
    let ref2 = &mut *ref1;

    // ref1 和 ref2 的作用域重叠,我理解这里应该违反了借用规则,但实际可以通过编译
    *ref2 += 2;
    *ref1 += 1;

    println!("{}", data);
}

猜想可以通过编译是因为 *ref1 有 data 的 write、read、own 权限。拥有 own 权限是因为 10 是整型,实现了 Copy trait。

接下来测试没有实现 Copy trait 的情况:

fn main() {
    let mut boxed_data = Box::new(10);
    let ref1 = &mut boxed_data;
    let ref2 = &mut *ref1;

    *ref2 = Box::new(1);
    *ref1 = Box::new(2);

    println!("{:?}", boxed_data);
}

同样可以编译通过,所以我的猜测不对。

我再贴一个例子:

fn main() {
    let mut data = 10;
    // 第一次可变借用 data 
    let ref1 = &mut data;
    // 对 ref1 解引用后,第二次可变借用 data
    let ref2 = &mut data; // 这种情况过不了编译

    *ref2 += 2;
    *ref1 += 1;

    println!("{}", data);
}

我的问题是:前两个例子和最后一个例子有啥区别,为啥最后一个例子编译不过?

评论区

写评论
zylthinking 2023-05-18 13:55

stack-borrow

--
👇
0xe994a4: 确实是 reborrow,官方没有正式的文档,民间有一堆的讨论:https://github.com/rust-lang/reference/issues/788。

但是感觉这样就让借用规则变复杂了。 首先,现在的文档强调特定作用域下只能存在一个可变借用。这个很好理解,我不希望有别的逻辑修改了值却没有同步给我。 然后,现在 reborrow 的出现,却允许了这种情况,而且还是编译器自行决定的。那我的代码在同伴的参与编写下,就有可能被改了,还编译通过了,但我却希望由借用规则限制这种事情。

--
👇
zylthinking: re-borrow

KuTuGu 2023-05-17 17:48

重借用可以理解为shadow
前两个代码能编译通过的原因是ref2临时shadow了ref1,ref2结束生命周期,再使用ref1是正常的;如果交换了使用顺序,是会报错的,因为同时存在两个可变借用,这也是为什么最后一个例子会报错:

fn main() {
    let mut data = 10;
    let ref1 = &mut data;    
    let ref2 = &mut *ref1;

    // ref2生命周期还没有结束,同时存在两个可变借用,编译报错
    *ref1 += 1;    
    *ref2 += 2;

    println!("{}", data);
}

参考资料:https://zjp-cn.github.io/translation/stacked-borrows/stacked-borrows.html

Lethexy 2023-05-17 14:40

em...关于最后一个例子编译不过,简单点说就是创建了两个data的可变引用,对rust来说在同一时间内,对于给定的变量,只能存在一个可变引用或多个不可变引用。具体区别就是前面的例子ref2其实是*ref1的可变引用,也就是说ref2和ref1之间没有数据竞争的风险。

--
👇
0xe994a4: 其实我的问题是前两个例子和最后一个例子有啥区别,为啥最后一个例子编译不过?我把问题补上。

--
👇
Lethexy: 我的理解是这样的: ref1 和 ref2 的作用域虽然重叠,但它们指向的数据是不同的。 具体来说,你理解不能同时存在可变引用的地方是,第一次可变借用 data 给了 ref1,此时 ref1 指向 data 的内存地址。而对 ref1 解引用后,表示通过 ref1 访问其所指向的内存地址(即 data),再取得该地址上的值,并将其赋给 ref2。因此,ref2 也指向了 data 所在的地址。但是这种情况并不会导致 ref1 和 ref2 同时修改同一个值,因为 ref2 是通过ref1去访问的data值。可以修改代码如下,会发现原代码并不会存在ref1 和 ref2 同时修改的情况,所以借用有效。

fn main() {
    let mut data = 10;
    let ref1 = &mut data;
    let ref2 = &mut *ref1;

    *ref2 +=2;
    *ref1 +=1;

    println!("{}", *ref2);//你会发现编译不过,原因是ref2其实是*ref1的可变引用
}

其实多个可变引用不能同时指向同一个数据的机制是为了解决数据竞争问题的,我理解的是这种情况并不会发生数据竞争,也就不存在编译报错。

ziyouwa 2023-05-17 13:54

直接回复居然没有星号,参考原文吧

--
👇
ziyouwa: 关键是生命周期,前两个例子能通过编译,是因为ref2的生命周期在ref2 += 2之后就结束了,如果printf的是ref2,就会报错。

ziyouwa 2023-05-17 13:54

关键是生命周期,前两个例子能通过编译,是因为ref2的生命周期在ref2 += 2之后就结束了,如果printf的是ref2,就会报错。

作者 0xe994a4 2023-05-17 10:16

确实是 reborrow,官方没有正式的文档,民间有一堆的讨论:https://github.com/rust-lang/reference/issues/788。

但是感觉这样就让借用规则变复杂了。 首先,现在的文档强调特定作用域下只能存在一个可变借用。这个很好理解,我不希望有别的逻辑修改了值却没有同步给我。 然后,现在 reborrow 的出现,却允许了这种情况,而且还是编译器自行决定的。那我的代码在同伴的参与编写下,就有可能被改了,还编译通过了,但我却希望由借用规则限制这种事情。

--
👇
zylthinking: re-borrow

作者 0xe994a4 2023-05-17 09:25

其实我的问题是前两个例子和最后一个例子有啥区别,为啥最后一个例子编译不过?我把问题补上。

--
👇
Lethexy: 我的理解是这样的: ref1 和 ref2 的作用域虽然重叠,但它们指向的数据是不同的。 具体来说,你理解不能同时存在可变引用的地方是,第一次可变借用 data 给了 ref1,此时 ref1 指向 data 的内存地址。而对 ref1 解引用后,表示通过 ref1 访问其所指向的内存地址(即 data),再取得该地址上的值,并将其赋给 ref2。因此,ref2 也指向了 data 所在的地址。但是这种情况并不会导致 ref1 和 ref2 同时修改同一个值,因为 ref2 是通过ref1去访问的data值。可以修改代码如下,会发现原代码并不会存在ref1 和 ref2 同时修改的情况,所以借用有效。

fn main() {
    let mut data = 10;
    let ref1 = &mut data;
    let ref2 = &mut *ref1;

    *ref2 +=2;
    *ref1 +=1;

    println!("{}", *ref2);//你会发现编译不过,原因是ref2其实是*ref1的可变引用
}

其实多个可变引用不能同时指向同一个数据的机制是为了解决数据竞争问题的,我理解的是这种情况并不会发生数据竞争,也就不存在编译报错。

Lethexy 2023-05-16 11:09

我的理解是这样的: ref1 和 ref2 的作用域虽然重叠,但它们指向的数据是不同的。 具体来说,你理解不能同时存在可变引用的地方是,第一次可变借用 data 给了 ref1,此时 ref1 指向 data 的内存地址。而对 ref1 解引用后,表示通过 ref1 访问其所指向的内存地址(即 data),再取得该地址上的值,并将其赋给 ref2。因此,ref2 也指向了 data 所在的地址。但是这种情况并不会导致 ref1 和 ref2 同时修改同一个值,因为 ref2 是通过ref1去访问的data值。可以修改代码如下,会发现原代码并不会存在ref1 和 ref2 同时修改的情况,所以借用有效。

fn main() {
    let mut data = 10;
    let ref1 = &mut data;
    let ref2 = &mut *ref1;

    *ref2 +=2;
    *ref1 +=1;

    println!("{}", *ref2);//你会发现编译不过,原因是ref2其实是*ref1的可变引用
}

其实多个可变引用不能同时指向同一个数据的机制是为了解决数据竞争问题的,我理解的是这种情况并不会发生数据竞争,也就不存在编译报错。

ssrlive 2023-05-16 11:04

將這兩句交換順序,編譯器就報錯,rust 編譯器是正確的。

    *ref2 += 2;
    *ref1 += 1;
zylthinking 2023-05-16 10:36

re-borrow

Grobycn 2023-05-16 09:53

因为你的代码里面, 两个借用的作用域是包含和被包含的关系吧。

1 共 11 条评论, 1 页