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
继续使用了,这里违反了什么规则了吗?
1
共 17 条评论, 1 页
评论区
写评论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的。--
👇
Neutron3529: 刚刚发现我回错人了……
我把应该给你说的东西回给了一个不懂rust语法的萌新……
在你发现问题出在&'a mut上面的时候,你其实已经注意到答案了。
比如说在下面这个例子中,用到了
&'a Test<'a>
,但是这里的'a
就不必跟整个struct的lifetime相等,是可以通过编译的。这里的关键点在于,&'a T
对于T
是covariant的,而&'a mut T
对于T
是invariant的。--
👇
Neutron3529: 刚刚发现我回错人了……
我把应该给你说的东西回给了一个不懂rust语法的萌新……
在你发现问题出在&'a mut上面的时候,你其实已经注意到答案了。
但我犹豫的点在于,
Test<'a>
应该是covariant的,`&'a对于
'a`也是covariant的,所以理论上应该允许把它转换成合适的生命周期大小。(这段是错误思路)。写到一半我发现了,
&'a mut T
对T
是invariant的,所以不能做转换。这就解释了所有的问题。--
👇
Neutron3529: 刚刚发现我回错人了……
我把应该给你说的东西回给了一个不懂rust语法的萌新……
在你发现问题出在&'a mut上面的时候,你其实已经注意到答案了。
--
👇
Pikachu: 这不能解释所有的问题。下面的两段程序,都用了&mut,但是一个可以编译一个不能。
--
👇
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; // 报错 }
嘻嘻, 现在看懂了, a outlives b, thx!
刚刚发现我回错人了……
我把应该给你说的东西回给了一个不懂rust语法的萌新……
在你发现问题出在&'a mut上面的时候,你其实已经注意到答案了。
--
👇
Pikachu: 这不能解释所有的问题。下面的两段程序,都用了&mut,但是一个可以编译一个不能。
--
👇
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; // 报错 }
...
我写的答案你根本没看是吗?
这就是一个生命周期的问题
&'a mut Test<'a>
的意思是,这个mut借用要活得跟Test<'a>
里面的'a
一样长,而Test<'a>的生命周期必然短于'a也就是说你只能
&'a mut
借这玩意一次。后面不管你操作什么,都因为
&'a mut
的存在而失效。这个
&'a mut
是强制的,哪怕你加作用域来个都不能让这玩意多用一次。
所以问题是程序写错,正确写法是
&'b mut Test<'a>
。👇
AndyJado:
我发现问问题还是这里强呀!
https://users.rust-lang.org/t/a-werid-dot-syntax-question-in-trait-impl-with-minimum-code/79249/2
我发现问问题还是这里强呀!
https://users.rust-lang.org/t/a-werid-dot-syntax-question-in-trait-impl-with-minimum-code/79249/2
https://doc.rust-lang.org/book/ch05-03-method-syntax.html#wheres-the---operator
--
👇
AndyJado: ``` m.borrow_mut();
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(); }
为什么这行代码能通过类型编译啊, m的类型根本不是trait impl 的类型啊!
talk is cheap👇:
这不能解释所有的问题。下面的两段程序,都用了&mut,但是一个可以编译一个不能。
--
👇
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; // 报错 }
说引用是Copy的确实不严谨, 主要是说这种case, 我不知道应该用什么词准确描述:
--
👇
xmh0511:
--
👇
Easonzero: 如楼上所说, 你的生命周期标记过于严格, 感觉生命周期问题很多时候可以参考下mir:
所以根据trait的生命周期约束, _6的生命周期被推断为与_4相同, 且_6是可Copy的, 不会因为move而提前释放, 进而导致了_7与_6的生命周期重叠, 触发错误.
当然这个解释是根据mir反推的, 个人觉得这个现象确实是不合理的, 与临时变量的直觉理解不符, 不过rust生命周期检查反直觉的case也不差这一个...
另外,我用编译器测试了一下
& mut Test
这种引用类型是直接被move走的,不可Copy--
👇
Easonzero: 如楼上所说, 你的生命周期标记过于严格, 感觉生命周期问题很多时候可以参考下mir:
所以根据trait的生命周期约束, _6的生命周期被推断为与_4相同, 且_6是可Copy的, 不会因为move而提前释放, 进而导致了_7与_6的生命周期重叠, 触发错误.
当然这个解释是根据mir反推的, 个人觉得这个现象确实是不合理的, 与临时变量的直觉理解不符, 不过rust生命周期检查反直觉的case也不差这一个...
另外,我用编译器测试了一下
& mut Test
这种引用类型是直接被move走的,不可Copy但是,rust里面不是说,引用的生命周期标注不会改变任何引用的声明周期吗。标注只是为了帮助编译器用来校对引用是否可以在当前的生命周期标注下有效使用的。比较困惑这一点。
如楼上所说, 你的生命周期标记过于严格, 感觉生命周期问题很多时候可以参考下mir:
所以根据trait的生命周期约束, _6的生命周期被推断为与_4相同, 且_6是可Copy的, 不会因为move而提前释放, 进而导致了_7与_6的生命周期重叠, 触发错误.
当然这个解释是根据mir反推的, 个人觉得这个现象确实是不合理的, 与临时变量的直觉理解不符, 不过rust生命周期检查反直觉的case也不差这一个...
你的代码里有个很奇怪的地方。
&'a mut Test<'a>
这个地方,两个生命周期不太应该是一样的,因为一个指的是Test
这个struct的生命周期,一个指的是reference
的生命周期。我把前一个删掉以后,代码就能通过编译了。https://play.rust-lang.org/?version=stable&mode=debug&edition=2021&gist=f0017c26d995c48b3435e5030bdbdf22
直觉上这个生命周期应该就是问题所在,但是我还是没想清楚为什么会导致这个问题。蹲一下其他大佬来进一步解释吧。