< 返回版块

xmh0511 发表于 2022-07-31 11:10

struct Test<'a> {
    v: &'a i32,
}

trait MyTestTrait {
    type Output;
    fn borrow_mut(self) -> Self::Output;
}

impl<'a> MyTestTrait for &'a mut Test<'a> {
    type Output = i32;
    fn borrow_mut(self) -> i32 {
       0
    }
}
fn main(){
   let i = 0;
   let mut m = Test { v: &i };
   m.borrow_mut();
   let i = m.v; // 报错
}

编译器报错,错误内容大致是

79 |     let i = m.v;
   |             ^^^
   |             |
   |             use of borrowed `m`
   |             borrow later used here

但是很奇怪,在调用borrow_mut()自动创建的借用在调用之后就不在被使用了,为什么不能再调用之后对m继续使用了,这里违反了什么规则了吗?

评论区

写评论
Neutron3529 2022-08-04 13:20

https://doc.rust-lang.org/nomicon/subtyping.html#variance

给不明就里的萌新附一个链接好了

在这个链接上我看到了nomicon,果然是我想简单了……

--
👇
Pikachu: 比如说在下面这个例子中,用到了&'a Test<'a>,但是这里的'a就不必跟整个struct的lifetime相等,是可以通过编译的。这里的关键点在于,&'a T对于T是covariant的,而&'a mut T对于T是invariant的。

struct Test<'a> {
    v: &'a i32,
}

fn borrow<'a>(t: &'a Test<'a>) -> i32 {
    *t.v
}

fn main(){
   let i = 0;
   let mut m = Test { v: &i };
   borrow(&m);
   let _i = &mut m.v; // Correct
}

--
👇
Neutron3529: 刚刚发现我回错人了……

我把应该给你说的东西回给了一个不懂rust语法的萌新……

在你发现问题出在&'a mut上面的时候,你其实已经注意到答案了。

Pikachu 2022-08-04 12:32

比如说在下面这个例子中,用到了&'a Test<'a>,但是这里的'a就不必跟整个struct的lifetime相等,是可以通过编译的。这里的关键点在于,&'a T对于T是covariant的,而&'a mut T对于T是invariant的。

struct Test<'a> {
    v: &'a i32,
}

fn borrow<'a>(t: &'a Test<'a>) -> i32 {
    *t.v
}

fn main(){
   let i = 0;
   let mut m = Test { v: &i };
   borrow(&m);
   let _i = &mut m.v; // Correct
}

--
👇
Neutron3529: 刚刚发现我回错人了……

我把应该给你说的东西回给了一个不懂rust语法的萌新……

在你发现问题出在&'a mut上面的时候,你其实已经注意到答案了。

Pikachu 2022-08-04 12:05

但我犹豫的点在于,Test<'a>应该是covariant的,`&'a对于'a`也是covariant的,所以理论上应该允许把它转换成合适的生命周期大小。(这段是错误思路)。

写到一半我发现了,&'a mut TT是invariant的,所以不能做转换。这就解释了所有的问题。

--
👇
Neutron3529: 刚刚发现我回错人了……

我把应该给你说的东西回给了一个不懂rust语法的萌新……

在你发现问题出在&'a mut上面的时候,你其实已经注意到答案了。

--
👇
Pikachu: 这不能解释所有的问题。下面的两段程序,都用了&mut,但是一个可以编译一个不能。

struct Test<'a> {
    v: &'a i32,
}

fn borrow_mut<'a>(t: &mut Test<'a>) -> i32 {
    *t.v
}

fn main(){
   let i = 0;
   let mut m = Test { v: &i };
   borrow_mut(&mut m);
   let _i = m.v; // OK
}
struct Test<'a> {
    v: &'a i32,
}

fn borrow_mut<'a>(t: &'a mut Test<'a>) -> i32 {
    *t.v
}

fn main(){
   let i = 0;
   let mut m = Test { v: &i };
   borrow_mut(&mut m);
   let _i = m.v; // Error
}

--
👇
gftea: ```rust fn main(){ let i = 0; let mut m = Test { v: &i }; // m.borrow_mut(); // 等价为以下调用, 已经借用了mut reference MyTestTrait::borrow_mut(&mut m); let i = m.v; // 报错 }




AndyJado 2022-08-03 13:46

嘻嘻, 现在看懂了, a outlives b, thx!

Neutron3529 2022-08-03 10:55

刚刚发现我回错人了……

我把应该给你说的东西回给了一个不懂rust语法的萌新……

在你发现问题出在&'a mut上面的时候,你其实已经注意到答案了。

--
👇
Pikachu: 这不能解释所有的问题。下面的两段程序,都用了&mut,但是一个可以编译一个不能。

struct Test<'a> {
    v: &'a i32,
}

fn borrow_mut<'a>(t: &mut Test<'a>) -> i32 {
    *t.v
}

fn main(){
   let i = 0;
   let mut m = Test { v: &i };
   borrow_mut(&mut m);
   let _i = m.v; // OK
}
struct Test<'a> {
    v: &'a i32,
}

fn borrow_mut<'a>(t: &'a mut Test<'a>) -> i32 {
    *t.v
}

fn main(){
   let i = 0;
   let mut m = Test { v: &i };
   borrow_mut(&mut m);
   let _i = m.v; // Error
}

--
👇
gftea: ```rust fn main(){ let i = 0; let mut m = Test { v: &i }; // m.borrow_mut(); // 等价为以下调用, 已经借用了mut reference MyTestTrait::borrow_mut(&mut m); let i = m.v; // 报错 }



Neutron3529 2022-08-03 10:53

...

我写的答案你根本没看是吗?

这就是一个生命周期的问题

&'a mut Test<'a>的意思是,这个mut借用要活得跟Test<'a>里面的'a一样长,而Test<'a>的生命周期必然短于'a

也就是说你只能&'a mut借这玩意一次。

后面不管你操作什么,都因为&'a mut的存在而失效。

这个&'a mut是强制的,哪怕你加作用域来个

{
   m.borrow_mut(); // why this line passes the compile checker? m don't have type &mut Test 
}

都不能让这玩意多用一次。

所以问题是程序写错,正确写法是&'b mut Test<'a>

👇
AndyJado:

// m.borrow_mut() triggers autoRef, be like:
let a = &mut m;

//a拿走了m的所有权, borrow_mut()拿走了a的所有权,吐出了i32.
a.borrow_mut(); 

// m和a都已经被drop了:
let i = m.v;

我发现问问题还是这里强呀!

https://users.rust-lang.org/t/a-werid-dot-syntax-question-in-trait-impl-with-minimum-code/79249/2

AndyJado 2022-08-03 08:54
// m.borrow_mut() triggers autoRef, be like:
let a = &mut m;

//a拿走了m的所有权, borrow_mut()拿走了a的所有权,吐出了i32.
a.borrow_mut(); 

// m和a都已经被drop了:
let i = m.v;

我发现问问题还是这里强呀!

https://users.rust-lang.org/t/a-werid-dot-syntax-question-in-trait-impl-with-minimum-code/79249/2

Pikachu 2022-08-02 16:14

https://doc.rust-lang.org/book/ch05-03-method-syntax.html#wheres-the---operator

--
👇
AndyJado: ``` m.borrow_mut();

为什么这行代码能通过类型编译啊, m的类型根本不是trait impl 的类型啊!


talk is cheap👇:

struct Test<'a> { v: &'a i32, }

trait MyTestTrait { type Output; fn borrow_mut(self) -> Self::Output; }

trait Trait2 { type Output; fn borrow_mut2(self) -> Self::Output; }

impl<'a> MyTestTrait for &'a mut Test<'a> { type Output = i32; fn borrow_mut(self) -> i32 { 0 } }

impl Trait2 for Test<'_> { type Output = i32; fn borrow_mut2(self) -> i32 { 0 } }

fn main(){ let i = 0; let mut m = Test { v: &i }; m.borrow_mut(); // why this line passes the compile checker? m don't have type &mut Test

let mut m2 = Test { v: &i }; m2.borrow_mut2(); }

AndyJado 2022-08-02 10:48
m.borrow_mut();

为什么这行代码能通过类型编译啊, m的类型根本不是trait impl 的类型啊!

talk is cheap👇:


struct Test<'a> {
    v: &'a i32,
}

trait MyTestTrait {
    type Output;
    fn borrow_mut(self) -> Self::Output;
}

trait Trait2 {
    type Output;
    fn borrow_mut2(self) -> Self::Output;
}

impl<'a> MyTestTrait for &'a mut Test<'a> {
    type Output = i32;
    fn borrow_mut(self) -> i32 {
       0
    }
}

impl Trait2 for Test<'_> {
    type Output = i32;
    fn borrow_mut2(self) -> i32 {
       0
    }
}

fn main(){
   let i = 0;
   let mut m = Test { v: &i };
   m.borrow_mut(); // why this line passes the compile checker? m don't have type &mut Test 

   let mut m2 = Test { v: &i };
   m2.borrow_mut2();
}
Pikachu 2022-08-02 02:53

这不能解释所有的问题。下面的两段程序,都用了&mut,但是一个可以编译一个不能。

struct Test<'a> {
    v: &'a i32,
}

fn borrow_mut<'a>(t: &mut Test<'a>) -> i32 {
    *t.v
}

fn main(){
   let i = 0;
   let mut m = Test { v: &i };
   borrow_mut(&mut m);
   let _i = m.v; // OK
}
struct Test<'a> {
    v: &'a i32,
}

fn borrow_mut<'a>(t: &'a mut Test<'a>) -> i32 {
    *t.v
}

fn main(){
   let i = 0;
   let mut m = Test { v: &i };
   borrow_mut(&mut m);
   let _i = m.v; // Error
}

--
👇
gftea: ```rust fn main(){ let i = 0; let mut m = Test { v: &i }; // m.borrow_mut(); // 等价为以下调用, 已经借用了mut reference MyTestTrait::borrow_mut(&mut m); let i = m.v; // 报错 }


gftea 2022-08-01 14:56
fn main(){
   let i = 0;
   let mut m = Test { v: &i };
   // m.borrow_mut();
   // 等价为以下调用, 已经借用了mut reference
   MyTestTrait::borrow_mut(&mut m); 
   let i = m.v; // 报错
}
Easonzero 2022-08-01 11:22

说引用是Copy的确实不严谨, 主要是说这种case, 我不知道应该用什么词准确描述:

struct T;

trait M {
    fn test(self);
}

impl M for &mut T {
    fn test(self) {}
}

fn main() {
    let mut x = T;
    let y = &mut x;
    
    y.test();
    y.test(); // y并没有被move走
}

--
👇
xmh0511:

--
👇
Easonzero: 如楼上所说, 你的生命周期标记过于严格, 感觉生命周期问题很多时候可以参考下mir:

// _1 是 i
// _2 是 m
bb0: {
    _1 = const 0_i32;
    _4 = &_1;
    _3 = _4;
    Deinit(_2); 
    (_2.0: &i32) = move _3;
    _6 = &mut _2;
    _5 = <&mut Test as MyTestTrait>::borrow_mut(move _6) -> bb1;
    // _7 = (_2.0: &i32);
}

所以根据trait的生命周期约束, _6的生命周期被推断为与_4相同, 且_6是可Copy的, 不会因为move而提前释放, 进而导致了_7与_6的生命周期重叠, 触发错误.

当然这个解释是根据mir反推的, 个人觉得这个现象确实是不合理的, 与临时变量的直觉理解不符, 不过rust生命周期检查反直觉的case也不差这一个...

另外,我用编译器测试了一下& mut Test 这种引用类型是直接被move走的,不可Copy

    let md = & mut d;  
    {
        let cd = md; //move 进cd了
    }
    md;
作者 xmh0511 2022-07-31 21:10

--
👇
Easonzero: 如楼上所说, 你的生命周期标记过于严格, 感觉生命周期问题很多时候可以参考下mir:

// _1 是 i
// _2 是 m
bb0: {
    _1 = const 0_i32;
    _4 = &_1;
    _3 = _4;
    Deinit(_2); 
    (_2.0: &i32) = move _3;
    _6 = &mut _2;
    _5 = <&mut Test as MyTestTrait>::borrow_mut(move _6) -> bb1;
    // _7 = (_2.0: &i32);
}

所以根据trait的生命周期约束, _6的生命周期被推断为与_4相同, 且_6是可Copy的, 不会因为move而提前释放, 进而导致了_7与_6的生命周期重叠, 触发错误.

当然这个解释是根据mir反推的, 个人觉得这个现象确实是不合理的, 与临时变量的直觉理解不符, 不过rust生命周期检查反直觉的case也不差这一个...

另外,我用编译器测试了一下& mut Test 这种引用类型是直接被move走的,不可Copy

    let md = & mut d;  
    {
        let cd = md; //move 进cd了
    }
    md;
作者 xmh0511 2022-07-31 21:02

但是,rust里面不是说,引用的生命周期标注不会改变任何引用的声明周期吗。标注只是为了帮助编译器用来校对引用是否可以在当前的生命周期标注下有效使用的。比较困惑这一点。

Neutron3529 2022-07-31 17:40
impl<'a:'b,'b> MyTestTrait for &'b mut Test<'a> {
    type Output = i32;
    fn borrow_mut(self) -> i32 {
       0
    }
}
Easonzero 2022-07-31 15:53

如楼上所说, 你的生命周期标记过于严格, 感觉生命周期问题很多时候可以参考下mir:

// _1 是 i
// _2 是 m
bb0: {
    _1 = const 0_i32;
    _4 = &_1;
    _3 = _4;
    Deinit(_2); 
    (_2.0: &i32) = move _3;
    _6 = &mut _2;
    _5 = <&mut Test as MyTestTrait>::borrow_mut(move _6) -> bb1;
    // _7 = (_2.0: &i32);
}

所以根据trait的生命周期约束, _6的生命周期被推断为与_4相同, 且_6是可Copy的, 不会因为move而提前释放, 进而导致了_7与_6的生命周期重叠, 触发错误.

当然这个解释是根据mir反推的, 个人觉得这个现象确实是不合理的, 与临时变量的直觉理解不符, 不过rust生命周期检查反直觉的case也不差这一个...

Pikachu 2022-07-31 13:00

你的代码里有个很奇怪的地方。&'a mut Test<'a>这个地方,两个生命周期不太应该是一样的,因为一个指的是Test这个struct的生命周期,一个指的是reference的生命周期。我把前一个删掉以后,代码就能通过编译了。

https://play.rust-lang.org/?version=stable&mode=debug&edition=2021&gist=f0017c26d995c48b3435e5030bdbdf22

直觉上这个生命周期应该就是问题所在,但是我还是没想清楚为什么会导致这个问题。蹲一下其他大佬来进一步解释吧。

1 共 17 条评论, 1 页