使用Tokio实现Promise系统时遇Fn闭包所有权错误求助
解决Tokio oneshot Sender在Fn闭包中移动的错误
错误原因
tokio::sync::oneshot::Sender的send方法会获取自身所有权(方法签名为pub fn send(mut self, t: T) -> Result<(), T>),调用一次后tx就会被移动,无法再次使用。- 你的
task_prom函数要求回调实现Fn(i32)trait,Fn的核心要求是闭包可以被多次调用。但你的闭包在第一次执行时就会把tx移走,这违反了Fn的约束——Rust编译器必须保证闭包能重复调用,因此不允许在Fn闭包中移动捕获的非Copy类型变量。
解决方案
方案1:将回调类型改为FnOnce(推荐)
由于你的逻辑中callback(x)只会被调用一次(仅当x == 10时执行),完全可以把task_prom的回调参数类型从Fn改为FnOnce。FnOnce trait允许闭包消耗捕获的变量(即移动tx),正好匹配你的场景:
use std::time::Duration; use tokio::sync::oneshot::{channel}; use tokio::time::sleep; // 将Fn改为FnOnce async fn task_prom(callback: impl FnOnce(i32)) { for x in 1..=10 { if x == 10 { callback(x); } sleep(Duration::from_secs(1)).await; } } async fn handler() -> i32 { let (tx, rx) = channel::<i32>(); task_prom(move |x| tx.send(x).unwrap()).await; rx.await.unwrap() } #[tokio::main] async fn main() { let res = handler().await; println!("{}", res); }
方案2:用Arc<Mutex>包装Sender(适用于可能多次调用回调的场景)
如果你的业务逻辑需要回调被多次调用(尽管当前场景不需要),可以用Arc<Mutex>包装Sender,让闭包可以安全地多次访问Sender:
use std::sync::Arc; use std::time::Duration; use tokio::sync::{oneshot::{channel}, Mutex}; use tokio::time::sleep; async fn task_prom(callback: impl Fn(i32)) { for x in 1..=10 { if x == 10 { callback(x); } sleep(Duration::from_secs(1)).await; } } async fn handler() -> i32 { let (tx, rx) = channel::<i32>(); let tx = Arc::new(Mutex::new(tx)); task_prom(move |x| { // 克隆Arc,保证闭包可以被多次调用 let tx_clone = tx.clone(); tokio::spawn(async move { let mut tx = tx_clone.lock().await; let _ = tx.send(x); // oneshot第二次send会返回Err,忽略即可 }); }).await; rx.await.unwrap() } #[tokio::main] async fn main() { let res = handler().await; println!("{}", res); }
注意:oneshot通道的
Sender只能成功发送一次数据,后续调用send会返回Err,因此方案1更贴合你当前的需求。
内容的提问来源于stack exchange,提问作者danixl30 ct
相关产品推荐
相关产品推荐

