以下代码会抛出异常。不知道该怎么理解,有没有懂的老哥?
fn main() {
{
let a = mpsc::channel();
let y:Sender<&str> = a.0;
let x = String::from("abc");
let s = &(x[..]);
y.send(s);
}
}
异常信息:
error[E0597]: `x` does not live long enough
--> src\main.rs:6:19
|
5 | let x = String::from("abc");
| - binding `x` declared here
6 | let s = &(x[..]);
| ^ borrowed value does not live long enough
7 | y.send(s);
8 | }
| -
| |
| `x` dropped here while still borrowed
| borrow might be used here, when `y` is dropped and runs the destructor for type `Sender<&str>`
|
= note: values in a scope are dropped in the opposite order they are defined
1
nagisaushio 87 天前 via Android
最后的 note 说了:x 会比 a 先 drop ,所以把定义反过来就好了
https://play.rust-lang.org/?version=stable&mode=debug&edition=2021&gist=76ac154fc9043bb360d366abb07918fe |
2
giiiiiithub OP @nagisaushio 可是为什么 x 会比 a 先 drop ?
|
3
nagisaushio 86 天前 via Android
@giiiiiithub 规定先定义的后 drop
|
4
araraloren 86 天前
This is called compiler error, not exception.
|
5
giiiiiithub OP @nagisaushio 可是把顺序不变,y 换成下边的代码,也是正常:
``` #[derive(Debug)] struct Foo; impl Foo { fn send(&self, t: &str) { println!("{}", t) } } fn main() { { // let a = mpsc::channel(); // let y:Sender<&str> = a.0; let y = Foo; let x = String::from("abc"); let s = &(x[..]); y.send(s); } } ``` |
6
giiiiiithub OP @araraloren 是的,多语言,脑子切换不及时
|
7
nagisaushio 86 天前 via Android
@giiiiiithub 你这个 Foo.send 完没有持有&str 所有权
|
8
giiiiiithub OP @nagisaushio 老哥,Channel 的 Sender 是咋获取到的?这俩传参是一样的
|
9
nagisaushio 86 天前
要把入参存在 Foo 内部,并且 Foo 要实现 Drop trait 。更符合 Sender 的模拟是这样的
https://play.rust-lang.org/?version=stable&mode=debug&edition=2021&gist=9cdb7165f175525b2a88f9902563083b |
10
giiiiiithub OP @nagisaushio 感谢,我简化了一下你的代码,也理解了其中一部分原委。这份代码编译错误,一部分是因为 Foo 实例的生命周期和被发送给&str 被标记了同样的生命周期'a 。
但是我还有不解: 1. 这份代码如果不实现 Drop 接口也能编译并运行通过,根本原因是什么?不实现 Drop 接口就不会释放吗? |
11
giiiiiithub OP @nagisaushio 不好意思,忘记贴代码了: https://play.rust-lang.org/?version=stable&mode=debug&edition=2021&gist=9645f22c4704aa689612a16a0cb8c022
具体来讲 send 方法内部不需要 self.0 = t; 也可以复现同样编译错误。 |
12
buxudashi 86 天前
看下面 2 种的不同。
你的: let mut y = Foo::default(); let x = String::from("abc"); let s = &(x[..]); y.send(s); 第 2 种: let x = String::from("abc");//这句放前面。 let mut y = Foo::default(); let s = &(x[..]); y.send(s); 程序的运行就是栈。跟洗盘子类似。先进后出。先放的盘子最后洗。 你的那个先放 y,x,s ,所以 X 要先弹出的。s 只是个引用。x 弹出了引用就没意义了。 你了解下 cpu 的加减法四则运算的栈方式。 |
13
buxudashi 86 天前
更精确点的描述是,当 abc 弄到堆上时,把 abc 的地址和 Len/cap 组成三合一的胖指针 x 压到栈里。
接着取这个胖指针的引用(不是 abc 的),弹出胖指针 x ,把 s 压到栈里。但这时 s 已经无效了。 |
14
giiiiiithub OP @buxudashi
这段代码要编译出错必须有几个条件: 1. x 必须在 y 之后创建 2. send 方法必须是&mut self ,不能是&mut self 3. 必须实现 Drop trait 你这个解释也只是解释了为什么有第一个条件 |
15
giiiiiithub OP @buxudashi 手误,修改第二条:2. send 方法必须是&mut self ,不能是& self
|
16
buxudashi 86 天前
试了一下,果然你说的是存在的。加了&mut ,输入的&str 估计编译器觉得你会用来改东西。毕竟用的是&mut.
这里应该是 drop 那的影响。你把 drop 删了有无 mut 也正常了。 这说明 y 有 mut 改写,所有参与改写的引用都得活着。 |
17
giiiiiithub OP @buxudashi 我感觉 rust 难的一点是,好像没有文档系统性地讲这些底层逻辑和关系。不知道它到底都有哪些原则。本来以为自己掌握了生命周期,咔咔来几个例子,又懵了。 我觉得这不是语言本身的问题,好像是文档的问题。(也可能是我没找到好的文档
|
18
giiiiiithub OP @nagisaushio 我把代码做了一个最小简化,并对生命周期做了一点展开。尝试分析一下原因。你看下对不对。
代码: https://play.rust-lang.org/?version=stable&mode=debug&edition=2021&gist=2de7b9f132bcea057bd22e4b66b966fa 1. 在 send 方法上,str 和 self 有同样的生命周期标记,并且 self 是&mut 。这就要求 str 的生命周期必须等于或长于 self 的生命周期。为什么? 因为 rust 看到 self 是&mut ,认为方法内 self 可能会依赖于 str ,比如有 self.0=str 这样的代码,编译器为了保证安全,必须做出这样的假设。这也解释了为什么 self 必须是&mut self ,而&self 不会产生编译错误,因为&self 是不可变的,并不会产生 self.0=str 这样的代码。 2. 之前的代码,Drop trait 只是表象,真正的原因是发生了借用。我最新的代码 main 函数最后一行是 let y = & foo; 这个借用行为,导致 rust 认为,foo 实例相关的依赖的生命周期也至少和 foo 实例的生命周期一样长,而这些依赖,也包含了 send 方法中的 str 。原因如上所说,rust 会假设 send 方法内有:self.0=str ,这样的代码。 但是很明显,传给 str 的 def 在 let y = & foo 之前,被丢弃了,于是导致 rust 假设的 self.0=str 这样的代码无法编译通过。 |
19
PTLin 85 天前
你这个其实不算生命周期问题,是 drop check 的问题。主要就是保证值析构时,析构函数不会访问到悬垂引用。
Sender 里 send 的行为可能是这样:send 时把值存在了内部某个地方,drop 的时候可能对这个值进行了某些操作,这种情况下这个值必然不能先于 Sender 被 drop ,所以调下 drop 顺序就能解决。 你这个 case 里 drop 另一个影响是,你在 send 后尝试对字符串进行 push 这种借出可变引用的行为也会报错,无论你是否调换了顺序。 可以看看这两个: https://doc.rust-lang.org/nomicon/dropck.html https://ng6qpa7sht.feishu.cn/docx/LGnVdlqwGoIUpuxUMWRcptEbndd |
20
PTLin 85 天前
附带一提,你这个例子里是 Sender 中的 SenderFlavor 实现了 Drop ,所以你在文档里看不到 Sender 实现了 Drop ,并且假如这个类型没实现 Drop ,就不会触发 Drop check ,你这个代码也就没问题。
|
21
giiiiiithub OP @PTLin 已经在 18 楼给出了解释,是多个原因综合的结果。不单纯是 drop 的问题,drop 只是表象。
|
22
giiiiiithub OP @PTLin 另外,mpmc 的 Sender 实现了 drop ,mpsc 的 Sender 依赖于 mpmc 的 Sender
|
23
PTLin 85 天前
@giiiiiithub 你标题的问题就是 Drop check 的问题,你 18 楼的问题是更复杂的生命周期协变 逆变的问题
https://doc.rust-lang.org/nomicon/subtyping.html https://rustcc.cn/article?id=38000317-0d25-40b1-aee9-81143322bec4 。 另外,你标题代码没有 use ,我没以为是 mpmc 。 |
24
giiiiiithub OP @PTLin
😓 @nagisaushio 给出了等效的构造,这个编译错误要出现,必须有几个条件同时满足,drop check 也只是表象。只说是 drop check 相当于只讲表皮原因。已经在 18 楼解释过了,不对是不是 drop check 再做解释了。除非有新的更为深入的解释。 如果使用生命周期的协变和逆变解释会更为麻烦。 |