如何在Rust trait中暴露参数元组类型实现FnOnce转FnMut?
将任意参数数量的
FnOnce闭包转换为FnMut以适配ffi_helpers::split_closure 我正在使用Michael F. Bryan博客中介绍的ffi_helpers::split_closure,但这个函数仅接受FnMut类型的闭包,而我需要处理FnOnce类型的闭包。针对单参数场景,我已经通过Option包装实现了转换:
pub fn fn_mut_from_fn_once<A, R>(fn_once: impl FnOnce(A) -> R) -> impl FnMut(A) -> R { let mut fn_once = Some(self); move |arg| { let fn_once = fn_once.take().unwrap(); fn_once(arg) } }
但这个实现只支持一元参数的FnOnce,于是我尝试通过辅助Trait实现通用版本,却卡在了Trait方法的返回类型约束上——不知道如何在Trait定义中描述返回值对应的FnMut闭包参数列表:
pub trait AsFnMut<Args, Ret> { fn as_fn_mut(self) -> impl FnMut(/* TODO 如何传递参数类型? */) -> Ret; } // 按参数个数编写的impl(TODO 后续打算用宏重构): impl<Func, Ret> AsFnMut<(), Ret> for Func where Func: FnOnce() -> Ret, { fn as_fn_mut(self) -> impl FnMut() -> Ret { let mut fn_once = Some(self); #[allow(non_snake_case)] move || { let fn_once = fn_once.take().unwrap(); fn_once() } } } impl<Func, Ret, A> AsFnMut<(A,), Ret> for Func where Func: FnOnce(A) -> Ret, { fn as_fn_mut(self) -> impl FnMut(A) -> Ret { let mut fn_once = Some(self); #[allow(non_snake_case)] move |A| { let fn_once = fn_once.take().unwrap(); fn_once(A) } } } impl<Func, Ret, A, B> AsFnMut<(A, B), Ret> for Func where Func: FnOnce(A, B) -> Ret, { fn as_fn_mut(self) -> impl FnMut(A, B) -> Ret { let mut fn_once = Some(self); #[allow(non_snake_case)] move |A, B| { let fn_once = fn_once.take().unwrap(); fn_once(A, B) } } }
我曾尝试用关联类型解决,但方向明显错误:
pub trait AsFnMut<Args, Ret> { type SpecificFnMutTrait; fn as_fn_mut(self) -> impl SpecificFnMutTrait; }
解决方案
在Rust 1.75及以上版本中,Fn系列Trait已支持类型参数化(FnMut<Args, Output = Ret>),我们可以直接用这个特性定义Trait的返回值约束,同时通过宏自动生成不同参数个数的实现,避免重复代码:
1. 正确的Trait定义
pub trait AsFnMut<Args, Ret> { fn as_fn_mut(self) -> impl FnMut<Args, Output = Ret>; }
2. 宏生成多参数实现
// 生成不同参数个数的AsFnMut实现的宏 macro_rules! impl_as_fn_mut { ($($arg:ident),*) => { impl<Func, Ret, $($arg),*> AsFnMut<($($arg,)*), Ret> for Func where Func: FnOnce($($arg),*) -> Ret, { fn as_fn_mut(self) -> impl FnMut($($arg),*) -> Ret { let mut fn_once = Some(self); move |$($arg),*| { // 注意:闭包被调用超过一次会触发panic let fn_once = fn_once.take().expect("FnOnce closure can only be called once"); fn_once($($arg),*) } } } }; } // 为0到3个参数生成实现(可根据需求扩展更多) impl_as_fn_mut!(); impl_as_fn_mut!(A); impl_as_fn_mut!(A, B); impl_as_fn_mut!(A, B, C);
注意事项
- 转换后的
FnMut闭包只能被调用一次,第二次调用会触发expect的panic——这是FnOnce的本质决定的,我们只是用Option包装模拟了FnMut的接口。 - 必须使用Rust 1.75及以上版本,才能支持
FnMut<Args, Output = Ret>这种类型化写法。
内容的提问来源于stack exchange,提问作者MHebes
相关产品推荐
相关产品推荐

