Rust futures::select 外部使用Box::pin与pin_mut的疑问
futures::select! 宏与Pin相关疑问解答
首先先纠正一个常见的文档理解偏差:select! 宏对传入表达式的实际约束,从来不是「传入的future类型本身必须直接实现Unpin + FusedFuture」,而是「宏在展开后可以安全获取该future的Pin<&mut Self>可变引用用于轮询,且对应future实现FusedFuture」。
问题1:fuse()返回的类型对Unpin future无Pin约束效果,为什么示例里还要做Pin包装?
首先明确一个事实:如果你传入的fuse后future本身确实是Unpin(比如示例里没有自引用的简单async fn返回值),不做Pin包装直接传给select! 完全可以通过编译并正常运行。
示例里统一用Box::pin或者pin_mut!做包装,是一套面向所有场景的通用兼容写法:
Future::poll方法的签名固定要求传入Pin<&mut Self>,对Unpin类型来说,&mut T可以隐式转换为Pin<&mut T>,不需要额外包装就能满足poll调用要求;但如果future是!Unpin类型(比如包含跨await自引用的async块/async fn),必须先Pin住才能安全调用poll,否则移动future会导致内部自引用悬垂。- 统一对所有要传入select! 的future做Pin处理,不需要开发者逐个判断每个future是否实现Unpin,不管future内部有没有自引用,都能满足安全轮询的要求,不会出现trait bound编译错误。
- 对Unpin类型做Pin包装本身没有任何运行时开销,不会影响性能。
问题2:为什么Pin包装的类型可以满足select!的Unpin要求,通过编译检查?
这是对Unpin trait和Pin类型的典型认知误区:Pin包装的指针类型本身是否Unpin,和它指向的内部值是否Unpin没有关系。
标准库对Pin类型做了无条件的trait实现:只要包裹的指针类型P实现Unpin,Pin<P>就自动实现Unpin。而我们常用的两种Pin包装:
Box<T>本身是存在栈上的指针,永远实现Unpin,因此Pin<Box<T>>不管内部T是不是Unpin,自身永远满足Unpin约束&mut T本身是栈上的引用,永远实现Unpin,因此pin_mut!生成的Pin<&mut T>不管内部T是不是Unpin,自身也永远满足Unpin约束
同时,futures库对指向FusedFuture的Pin指针做了转发实现:只要Pin指向的内部值实现FusedFuture,这个Pin指针自身也会实现FusedFuture。
这就是为什么Pin包装的类型完全满足select!宏对传入表达式的trait要求,可以正常通过编译。
示例代码逻辑说明
async fn async_identity_fn(arg: usize) -> usize { arg } // 调用fuse()将普通future转换为FusedFuture:future完成后后续轮询会直接返回Pending,避免被select!重复轮询 let fut_1 = async_identity_fn(1).fuse(); let fut_2 = async_identity_fn(2).fuse(); // 堆上固定future,得到的Pin<Box<Fuse<_>>>类型自动实现Unpin + FusedFuture let mut fut_1 = Box::pin(fut_1); // 栈上固定future,得到的Pin<&mut Fuse<_>>类型自动实现Unpin + FusedFuture pin_mut!(fut_2); // select! 同时支持两种入参:直接传入Unpin的裸FusedFuture、传入Pin包装的FusedFuture let res = select! { a_res = fut_1 => a_res, b_res = fut_2 => b_res, };
内容的提问来源于stack exchange,提问作者sunboy_zgz
相关产品推荐
相关产品推荐

