pin_mut!宏工作原理及Pin与底层值所有权相关问题
关于
pin_mut!宏的两个核心问题解析 在《Rust for Rustaceans》一书中,Jon Gjengset给出了pin_mut!宏的实现代码:
macro_rules! pin_mut { ($var:ident) => { let mut $var = $var; let mut $var = unsafe { Pin::new_unchecked(&mut $var) }; } }
书中提到:
对$var的遮蔽确保调用者无法丢弃Pin后继续使用未固定的值(这会违反所有!Unpin类型的Pin约定)。通过移动$var中存储的值,宏还确保调用者无法在不丢弃原始变量的情况下丢弃宏声明的$var绑定。
问题一:对$var的遮蔽如何确保调用者无法丢弃Pin后继续使用未固定的值?
我的思考:
- 我认为这是因为一旦类型为
Pin<T>的遮蔽值被丢弃,底层的T值也会被丢弃,因此Pin<T>被丢弃后,用户就无法再使用T的值。 - 我还认为该宏生效的原因是,$var被遮蔽后,对变量$var的任何引用都只会指向类型为
Pin<T>的$var,而非底层类型为T的值。 - 这似乎表明,对$var的遮蔽在某种程度上让底层类型为
T的值变成了“匿名”状态。
解答:
遮蔽的核心是覆盖原变量的命名绑定,从两个层面杜绝了违规操作:
- 宏第一行
let mut $var = $var;直接把原变量的T值移动到新的同名变量中,原变量绑定彻底失效——从此所有对$var的操作都指向这个新变量,原变量再也无法被访问。 - 第二行用
Pin<&mut T>类型的新值再次遮蔽变量,此时代码里的$var完全是Pin类型,原T类型的绑定被彻底覆盖。
当Pin<T>被丢弃时,持有T所有权的是第一行创建的那个被遮蔽的匿名变量——它和Pin的生命周期完全绑定在同一个作用域里。一旦Pin销毁(比如作用域结束),这个匿名T变量也会被销毁,自然不存在“丢弃Pin后还能使用未固定T”的可能。另外,因为原变量已经被移动,Pin是唯一能接触到T的途径,你根本绕不开Pin去直接操作T。
问题二:丢弃Pin<T>为何会导致底层T值被丢弃?如果被遮蔽的$var拥有Pin<T>类型的值,那么当$var生命周期结束时Pin<T>会被丢弃是合理的。但谁拥有T类型的值呢?Pin<T>并不拥有T,因为它的构造函数接受的是指针类型(如Box<T>、&mut T等)。如果没有所有者,那谁来丢弃它?如果有变量拥有它,那为什么编译时无法访问它?
解答:
关键要理清宏里的所有权流转逻辑:
- 原变量的
T值在宏第一行let mut $var = $var;中,被移动到了一个新的同名变量里——这个新变量就是T的所有者,它的生命周期和当前作用域绑定。 - 第二行的
Pin::new_unchecked(&mut $var)只是创建了一个指向这个新变量的Pin<&mut T>,Pin本身不拥有T,但它遮蔽了原有的T类型变量绑定。
持有T的变量并没有消失,只是无法再通过原来的名字访问了——变量遮蔽规则就是如此:同名变量多次声明时,后续绑定会覆盖前面的,前面的绑定就无法被引用。所以你只能通过最后那个Pin类型的$var接触到T,而不能直接访问持有T的被遮蔽变量。
当作用域结束时,Pin先被销毁,紧接着这个匿名的T变量也会被销毁,这就是“丢弃Pin会导致底层T被丢弃”的原因。
内容的提问来源于stack exchange,提问作者Jim
相关产品推荐
相关产品推荐

