< 返回版块

HC97 发表于 2021-05-13 10:10

struct Foo;

impl Foo {
    fn mutate_and_share(&mut self) -> &Self {&*self}
    fn share(&self) {}
}

fn main() {
    let mut foo = Foo;
    let loan = foo.mutate_and_share();
    foo.share();
    loan.share();
}
error[E0502]: cannot borrow `foo` as immutable because it is also borrowed as mutable
  --> src/main.rs:11:5
   |
10 |     let loan = foo.mutate_and_share();
   |                --- mutable borrow occurs here
11 |     foo.share();
   |     ^^^ immutable borrow occurs here
12 |     loan.share();
   |     ---- mutable borrow later used here
}

在 Rust 高级编程里看到生命周期的局限一节,原示例没有最后一行‘loan.share()’,已经可以正常编译,但是加上最后一行之后就会报错。

我的疑问是,为什么 loan 的生命周期是和参数 &mut foo 关联而不是和原来的所有者 foo 关联呢,如果是把 loan 和 foo 关联起来,参数 &mut foo 的生命周期就可以限制在这一条语句里,这样会有什么安全隐患吗?

或者说,在传入参数和返回值都是引用的情况下,为什么返回值的生命周期要和参数关联,而不是和拥有所有权的本体关联。

评论区

写评论
作者 HC97 2021-05-18 21:23

感谢解答

--
👇
deities-online:

struct Foo;

impl Foo {
    fn mutate_and_share(&mut self) -> &Self {&*self}
    fn share(&self) {}
}

fn main() {
    let mut foo = Foo; 
    let loan = foo.mutate_and_share();  // A
    foo.share(); // B
    loan.share(); // C
}

我想您的主要疑惑是:根据rust的语法规则,share类型的借用是可以共存的,上面代码中明明肉眼可见都只有share类型的借用存在,为什么不行,对吧?

这里涉及到一个知识点要 重借用 ,就是把自己借到的东西再借给别人。rust为了精确的借用分析,在编译器中会记录每次借用的信息。记录的形式大致是 谁 以什么方式 从谁 那里 借了什么 要用多久,双方的承诺是什么。

在 A 处,变量 foo 首先把自己以可变引用的方式借给了 mutate_and_share 方法,而该方法又将 self 以重借用 的方式借给了变量 loan。根据rust的隐藏生存周期规则,loan与muttate_and_share具有相同的生存期。

我们现在来填一下表格,就一切都清楚了。

以什么方式 从谁那里 借了什么 可以用多久 什么时候还回 对借出者的约束
mutate_and_share 以独占的方式 从foo那里 借了一个foo实例 只要foo存在就一直可以用 自己再次借出之后,不知道别人什么时候还我,loan 什么时候还我,我就什么时候还给foo,但总的来说不可能超过 foo 的生存期 foo,你现在在已经以独占的方式借给我了,我没有还给你之前,你不可以以任何形式借给别人
loan 以共享的方式 从 mutate_and_share 那里 借了一个foo实例 只要 mutate_and_share 可以借多久就可以用多久 我最后一次被使用后就还,但总的来说,不能超过 mutate_and_share 可以借给我的最长时间,也就是foo的生存期 mutate_and_share,你现在已经以共享引用的方式借给我了,在我还给你之前,你不可以用独占的方式把自己借给别人,但可以以共享的方式借给别人

loan 在 C 处调用完 share() 之后就不再用了,就当即与 mutate_and_share 解除了借用约定, mutate_and_share 随后也与 foo 解除了借用约定。

如果没有 B 这一行,那么一切都符合约定,编译器不会报错。但加入 B 这一行时,可以看到,当share想向foo借一个共享引用时,此时 loan 还未与 mutate_and_share 解除约定,那自然 mutate_and_share 就无法与 foo 解除约定。而 mutate_and_share 与 foo 有一个约定就是,我和你没有解除约定前你不可以以任何形式借给其它人。

这就是加入B后,编译器报错的原因。

deities-online 2021-05-17 12:21

struct Foo;

impl Foo {
    fn mutate_and_share(&mut self) -> &Self {&*self}
    fn share(&self) {}
}

fn main() {
    let mut foo = Foo; 
    let loan = foo.mutate_and_share();  // A
    foo.share(); // B
    loan.share(); // C
}

我想您的主要疑惑是:根据rust的语法规则,share类型的借用是可以共存的,上面代码中明明肉眼可见都只有share类型的借用存在,为什么不行,对吧?

这里涉及到一个知识点要 重借用 ,就是把自己借到的东西再借给别人。rust为了精确的借用分析,在编译器中会记录每次借用的信息。记录的形式大致是 谁 以什么方式 从谁 那里 借了什么 要用多久,双方的承诺是什么。

在 A 处,变量 foo 首先把自己以可变引用的方式借给了 mutate_and_share 方法,而该方法又将 self 以重借用 的方式借给了变量 loan。根据rust的隐藏生存周期规则,loan与muttate_and_share具有相同的生存期。

我们现在来填一下表格,就一切都清楚了。

以什么方式 从谁那里 借了什么 可以用多久 什么时候还回 对借出者的约束
mutate_and_share 以独占的方式 从foo那里 借了一个foo实例 只要foo存在就一直可以用 自己再次借出之后,不知道别人什么时候还我,loan 什么时候还我,我就什么时候还给foo,但总的来说不可能超过 foo 的生存期 foo,你现在在已经以独占的方式借给我了,我没有还给你之前,你不可以以任何形式借给别人
loan 以共享的方式 从 mutate_and_share 那里 借了一个foo实例 只要 mutate_and_share 可以借多久就可以用多久 我最后一次被使用后就还,但总的来说,不能超过 mutate_and_share 可以借给我的最长时间,也就是foo的生存期 mutate_and_share,你现在已经以共享引用的方式借给我了,在我还给你之前,你不可以用独占的方式把自己借给别人,但可以以共享的方式借给别人

loan 在 C 处调用完 share() 之后就不再用了,就当即与 mutate_and_share 解除了借用约定, mutate_and_share 随后也与 foo 解除了借用约定。

如果没有 B 这一行,那么一切都符合约定,编译器不会报错。但加入 B 这一行时,可以看到,当share想向foo借一个共享引用时,此时 loan 还未与 mutate_and_share 解除约定,那自然 mutate_and_share 就无法与 foo 解除约定。而 mutate_and_share 与 foo 有一个约定就是,我和你没有解除约定前你不可以以任何形式借给其它人。

这就是加入B后,编译器报错的原因。

作者 HC97 2021-05-17 11:08

感谢回答,我明白了

--
👇
amao: 第一个问题,有不同,虽然实质都是不可变借用,但是你同时打印它们俩试试,你可以把let loan=&*(&mut foo)理解为独占只读的效果(和同时使用多个不可变借用的语义不同)。 第二个问题,&mut foo和loan绑定在了一起对么?但是具体怎么使用是你的事。举个可能不恰当的例子,皇帝把尚方宝剑给了你,你先斩后奏的权利,但是你要求自己不要使用这个权利;除非你死了,尚方宝剑才会被皇帝重新分配给别人。

--
👇
HC97: 不好意思我没表达清楚,通过你之前的解答我已经明白编译失败的原因是存在 &mut foo,我想问的是 let loan = &*(&mut foo)let loan = &foo 这两种借用方式结果有什么不同,因为从表面上看 loan 就是一个不可变借用,对它的操作不会影响 foo 的状态,为什么不把 &mut foo 的生命周期限制在 let loan = &*(&mut foo) 这条语句,而是要让它延续到 loan 的生命周期结束呢?

--
👇
amao: 我没太明白你说的直接获取是什么意思,可能是我解释的还不够清楚。再换个方式说一下。通过foo.mutate_and_share()得到了loan,和let loan = &*(&mut foo)一个意思,如果后面你不使用loan,这句运行完后,可变借用就还给foo了。如果你后面使用了loan,比如loan.share(),那这个foo的可变借用就要等loan用完了再还回去,也就是说,let loan = &*(&mut foo)和最后一次使用loan之间是foo可变借用的生命周期,根据借用规则,这之间不能再有foo的借用了

--
👇
HC97: 通过可变借用得到的不可变借用与直接获取的不可变借用有什么区别吗?能不能具体说明一下

--
👇
amao: 得到loan的过程发生了foo的可变借用,这个loan是foo的可变借用解引用后再不可变借用,也就是说虽然看起来loan是不可变借用,但也是建立在可变借用基础上的。如果后面没有loan的使用,那么mutate_and_share函数结束后,可变借用就还回去了,foo.share()可以执行;如果后面还有使用loan,那么说明可变借用还在使用,foo.share()就违反了借用规则。同样道理,你可以把loan.share()放到foo.share()上面,一样可以编辑通过,因为loan.share()结束时,可变借用就还回去了。

amao 2021-05-15 01:00

第一个问题,有不同,虽然实质都是不可变借用,但是你同时打印它们俩试试,你可以把let loan=&*(&mut foo)理解为独占只读的效果(和同时使用多个不可变借用的语义不同)。 第二个问题,&mut foo和loan绑定在了一起对么?但是具体怎么使用是你的事。举个可能不恰当的例子,皇帝把尚方宝剑给了你,你先斩后奏的权利,但是你要求自己不要使用这个权利;除非你死了,尚方宝剑才会被皇帝重新分配给别人。

--
👇
HC97: 不好意思我没表达清楚,通过你之前的解答我已经明白编译失败的原因是存在 &mut foo,我想问的是 let loan = &*(&mut foo)let loan = &foo 这两种借用方式结果有什么不同,因为从表面上看 loan 就是一个不可变借用,对它的操作不会影响 foo 的状态,为什么不把 &mut foo 的生命周期限制在 let loan = &*(&mut foo) 这条语句,而是要让它延续到 loan 的生命周期结束呢?

--
👇
amao: 我没太明白你说的直接获取是什么意思,可能是我解释的还不够清楚。再换个方式说一下。通过foo.mutate_and_share()得到了loan,和let loan = &*(&mut foo)一个意思,如果后面你不使用loan,这句运行完后,可变借用就还给foo了。如果你后面使用了loan,比如loan.share(),那这个foo的可变借用就要等loan用完了再还回去,也就是说,let loan = &*(&mut foo)和最后一次使用loan之间是foo可变借用的生命周期,根据借用规则,这之间不能再有foo的借用了

--
👇
HC97: 通过可变借用得到的不可变借用与直接获取的不可变借用有什么区别吗?能不能具体说明一下

--
👇
amao: 得到loan的过程发生了foo的可变借用,这个loan是foo的可变借用解引用后再不可变借用,也就是说虽然看起来loan是不可变借用,但也是建立在可变借用基础上的。如果后面没有loan的使用,那么mutate_and_share函数结束后,可变借用就还回去了,foo.share()可以执行;如果后面还有使用loan,那么说明可变借用还在使用,foo.share()就违反了借用规则。同样道理,你可以把loan.share()放到foo.share()上面,一样可以编辑通过,因为loan.share()结束时,可变借用就还回去了。

作者 HC97 2021-05-14 10:00

不好意思我没表达清楚,通过你之前的解答我已经明白编译失败的原因是存在 &mut foo,我想问的是 let loan = &*(&mut foo)let loan = &foo 这两种借用方式结果有什么不同,因为从表面上看 loan 就是一个不可变借用,对它的操作不会影响 foo 的状态,为什么不把 &mut foo 的生命周期限制在 let loan = &*(&mut foo) 这条语句,而是要让它延续到 loan 的生命周期结束呢?

--
👇
amao: 我没太明白你说的直接获取是什么意思,可能是我解释的还不够清楚。再换个方式说一下。通过foo.mutate_and_share()得到了loan,和let loan = &*(&mut foo)一个意思,如果后面你不使用loan,这句运行完后,可变借用就还给foo了。如果你后面使用了loan,比如loan.share(),那这个foo的可变借用就要等loan用完了再还回去,也就是说,let loan = &*(&mut foo)和最后一次使用loan之间是foo可变借用的生命周期,根据借用规则,这之间不能再有foo的借用了

--
👇
HC97: 通过可变借用得到的不可变借用与直接获取的不可变借用有什么区别吗?能不能具体说明一下

--
👇
amao: 得到loan的过程发生了foo的可变借用,这个loan是foo的可变借用解引用后再不可变借用,也就是说虽然看起来loan是不可变借用,但也是建立在可变借用基础上的。如果后面没有loan的使用,那么mutate_and_share函数结束后,可变借用就还回去了,foo.share()可以执行;如果后面还有使用loan,那么说明可变借用还在使用,foo.share()就违反了借用规则。同样道理,你可以把loan.share()放到foo.share()上面,一样可以编辑通过,因为loan.share()结束时,可变借用就还回去了。

amao 2021-05-14 02:07

我没太明白你说的直接获取是什么意思,可能是我解释的还不够清楚。再换个方式说一下。通过foo.mutate_and_share()得到了loan,和let loan = &*(&mut foo)一个意思,如果后面你不使用loan,这句运行完后,可变借用就还给foo了。如果你后面使用了loan,比如loan.share(),那这个foo的可变借用就要等loan用完了再还回去,也就是说,let loan = &*(&mut foo)和最后一次使用loan之间是foo可变借用的生命周期,根据借用规则,这之间不能再有foo的借用了

--
👇
HC97: 通过可变借用得到的不可变借用与直接获取的不可变借用有什么区别吗?能不能具体说明一下

--
👇
amao: 得到loan的过程发生了foo的可变借用,这个loan是foo的可变借用解引用后再不可变借用,也就是说虽然看起来loan是不可变借用,但也是建立在可变借用基础上的。如果后面没有loan的使用,那么mutate_and_share函数结束后,可变借用就还回去了,foo.share()可以执行;如果后面还有使用loan,那么说明可变借用还在使用,foo.share()就违反了借用规则。同样道理,你可以把loan.share()放到foo.share()上面,一样可以编辑通过,因为loan.share()结束时,可变借用就还回去了。

JavaHello 2021-05-13 19:10

--
👇
JavaHello: ```rust fn mutate_and_share(&mut self) -> &Self { // &*self &Foo }


这样是不是可以理解了呢
也不对,感觉是对于 Foo 这种不占内存空间的结构体做了特殊处理
JavaHello 2021-05-13 18:47
    fn mutate_and_share(&mut self) -> &Self {
        // &*self
        &Foo
    }

这样是不是可以理解了呢

作者 HC97 2021-05-13 13:36

通过可变借用得到的不可变借用与直接获取的不可变借用有什么区别吗?能不能具体说明一下

--
👇
amao: 得到loan的过程发生了foo的可变借用,这个loan是foo的可变借用解引用后再不可变借用,也就是说虽然看起来loan是不可变借用,但也是建立在可变借用基础上的。如果后面没有loan的使用,那么mutate_and_share函数结束后,可变借用就还回去了,foo.share()可以执行;如果后面还有使用loan,那么说明可变借用还在使用,foo.share()就违反了借用规则。同样道理,你可以把loan.share()放到foo.share()上面,一样可以编辑通过,因为loan.share()结束时,可变借用就还回去了。

amao 2021-05-13 12:13

得到loan的过程发生了foo的可变借用,这个loan是foo的可变借用解引用后再不可变借用,也就是说虽然看起来loan是不可变借用,但也是建立在可变借用基础上的。如果后面没有loan的使用,那么mutate_and_share函数结束后,可变借用就还回去了,foo.share()可以执行;如果后面还有使用loan,那么说明可变借用还在使用,foo.share()就违反了借用规则。同样道理,你可以把loan.share()放到foo.share()上面,一样可以编辑通过,因为loan.share()结束时,可变借用就还回去了。

7sDream 2021-05-13 11:18

因为你这里省略了声明周期参数,默认会这样加上生命周期(规则可参考:https://doc.rust-lang.org/reference/lifetime-elision.html):

fn(&'a mut self) -> &'a Self

如果没有 loan.share();,编译器可以把 'a (理解为一个泛型参数)的实际类型设置为只包含 let laon = ...; 这一条语句。

如果有了对 loan 的使用,那么 'a 的实际类型就必须从 let laon = ...; 到函数结束为止。

这就相当于你有一个 &mut Foo 的借用一直持续到函数结束,所以 foo.share(); 会报错。

Neutron3529 2021-05-13 11:06

我的感觉是,Rust不能返回一个没有定义的lifetime

具体为什么最好看看大神的见解,下面只是我的浅见


你的程序是

fn mut_share(&mut self)->&Self

对一般的函数

fn func(x:&mut T)->&T

如果&T&mut T活得长,(而它们指向了同一个地址)

有可能&mut T失效(比如地址被free掉)而&T仍然存活,可能会UAF

如果想要按照你写的编译,或许可以试试std::marker里面的一些神奇结构体

1 共 12 条评论, 1 页