如何在生成异步任务时让AsyncWrite具备可移动性?
解决
T cannot be unpinned编译错误 问题原因
AsyncWrite方法的Pin约束:AsyncWrite::write要求self必须是Pin<&mut Self>,而MutexGuard<T>只有当T实现Unpin时,才能自动转换为可Pin的引用,否则编译器无法保证T不会被移动,导致约束不满足。- 引用生命周期不匹配:
data是&[u8]类型的引用,直接移入tokio::spawn的异步块会触发生命周期错误——under_test中data的生命周期无法覆盖异步任务的整个执行周期。 - 未执行异步方法:
write是异步方法,仅调用方法只会创建一个Future对象,必须通过.await才能触发执行。
修复方案
1. 给泛型添加Unpin约束
在under_test的泛型参数中补充Unpin,由于测试用的TcpStream本身实现了Unpin,这个约束完全适配场景,能让MutexGuard<T>安全地被Pin。
2. 复制数据获取所有权
将&[u8]转换为Vec<u8>,让数据拥有独立所有权,可安全移入异步任务中,避免生命周期冲突。
3. 正确执行异步方法并处理结果
调用writer.write(&data).await触发异步写入,测试场景中可用.unwrap()简化错误处理。
修改后的完整代码
use std::sync::Arc; use tokio::io::{AsyncReadExt, AsyncWrite, AsyncWriteExt}; use tokio::net::{TcpListener, TcpStream}; use tokio::sync::Mutex; #[tokio::test] async fn test_spawn_move_writable() { async fn under_test<T: AsyncWrite + Unpin>(writer: Arc<Mutex<T>>, data: &[u8]) { let data = data.to_vec(); // 复制数据获取所有权 tokio::spawn(async move { let mut writer = writer.lock().await; writer.write(&data).await.unwrap(); // 等待异步写入完成 }) .await .unwrap(); } let addr = "127.0.0.1:3000"; let tcps = TcpListener::bind(addr).await.unwrap(); tokio::spawn(async move { loop { let (mut client, caddr) = tcps.accept().await.unwrap(); tokio::spawn(async move { let mut buf = vec![0; 16]; loop { let size = client.read(&mut buf).await.unwrap(); println!( "{:?}> {}", caddr, std::str::from_utf8(&buf[0..size]).unwrap() ); } }); } }); let tcps = TcpStream::connect(addr).await.unwrap(); let tcps = Arc::new(Mutex::new(tcps)); for data in [b"hello", b"world", b"rust", b"tokio"] { under_test(tcps.clone(), &data[..]).await; } }
额外说明
如果需要支持非Unpin的AsyncWrite实现,可以将Arc<Mutex<T>>改为Arc<Mutex<Pin<Box<T>>>>,通过Box::pin手动固定对象,满足Pin约束。但在当前测试场景下,直接添加Unpin约束是最简洁的方案。
内容的提问来源于stack exchange,提问作者progquester
相关产品推荐
相关产品推荐

