如何在Rust FnMut中传入变量避免转为FnOnce?(Tonic UDS场景)
解决Tonic UDS客户端外部传入路径的生命周期与FnMut问题
核心问题拆解
- 生命周期报错:
service_fn要求传入的闭包满足长期存活约束,直接引用外部path会因生命周期不足被编译器拒绝。 - FnOnce问题:用
move闭包后,若path是独占所有权类型(比如String),闭包会拿走所有权,导致只能被调用一次,不符合service_fn需要的FnMut(可多次调用)要求。
最优解决方案:用共享所有权类型包装路径
通过Arc<String>共享路径所有权,既满足生命周期要求,又能让闭包被多次调用:
use std::sync::Arc; use tonic::transport::{Endpoint, Uri}; use hyper::service::{Service, service_fn}; use tokio::net::UnixStream; // 外部传入路径的客户端创建函数 async fn create_uds_client(path: String) -> Result<MyClient, Box<dyn std::error::Error>> { // 用Arc包装路径,实现共享所有权 let shared_path = Arc::new(path); let endpoint = Endpoint::try_from("http://[::]")? .connect_with_connector(service_fn(move |_: Uri| { // 克隆Arc,让闭包获得共享引用,不消耗原始所有权 let path_clone = shared_path.clone(); async move { let stream = UnixStream::connect(path_clone.as_str()).await?; Ok::<_, Box<dyn std::error::Error + Send + Sync>>(stream) } })); let client = MyClient::new(endpoint); Ok(client) }
为什么这能解决问题
- 生命周期问题:
Arc的内部数据只要有引用存在就不会被释放,天然满足service_fn对闭包的'static生命周期要求。 - FnMut问题:每次调用闭包时克隆
Arc,闭包只持有克隆后的共享引用,原始Arc可以被反复克隆,所以闭包能被多次调用,符合FnMut的要求。
局限性替代方案:静态字符串
如果路径是编译期固定的(比如从环境变量初始化后不再变化),可以直接用&'static str:
async fn create_uds_client_static(path: &'static str) -> Result<MyClient, Box<dyn std::error::Error>> { let endpoint = Endpoint::try_from("http://[::]")? .connect_with_connector(service_fn(move |_: Uri| async move { let stream = UnixStream::connect(path).await?; Ok::<_, Box<dyn std::error::Error + Send + Sync>>(stream) })); let client = MyClient::new(endpoint); Ok(client) }
注意:这个方案仅适用于路径不会动态变化的场景,灵活性远不如Arc方案。
内容的提问来源于stack exchange,提问作者Markus A.
相关产品推荐
相关产品推荐

