通过参数结构体模式实现Rust闭包默认参数的方案探讨
Rust中用参数结构体实现类似默认参数的方案
问题描述
原本的do_a_b函数接受两个FnMut闭包参数:
fn do_a_b(mut a: impl FnMut(), mut b: impl FnMut()) { a(); b(); } fn main() { let do_a = || println!("Doing A!"); do_a_b(do_a, || println!("Doing B!")) }
想要通过参数结构体模式实现类似默认参数的效果:允许只指定部分参数,未指定的自动填充为空闭包||{}。但尝试为Args实现Default时遇到类型不兼容问题——自定义闭包是匿名类型,无法与为Args<fn(), fn()>实现的Default匹配,导致结构体更新语法..Default::default()失效。
可行解决方案
方案1:类型擦除+构建器(通用且稳定)
使用Box<dyn FnMut()>做类型擦除,统一Args的字段类型,同时提供构建器方法简化参数设置:
struct Args { pub a: Box<dyn FnMut()>, pub b: Box<dyn FnMut()>, } impl Default for Args { fn default() -> Self { Self { a: Box::new(|| {}), b: Box::new(|| {}), } } } impl Args { // 初始化默认空闭包的Args pub fn new() -> Self { Default::default() } // 链式设置a参数 pub fn a(mut self, a: impl FnMut() + 'static) -> Self { self.a = Box::new(a); self } // 链式设置b参数 pub fn b(mut self, b: impl FnMut() + 'static) -> Self { self.b = Box::new(b); self } } fn do_a_b(mut args: Args) { (args.a)(); (args.b)(); } fn main() { // 仅设置a参数,b用默认空闭包 let args = Args::new().a(|| println!("Doing A!")); do_a_b(args); // 仅设置b参数,a用默认空闭包 let args = Args::new().b(|| println!("Doing B!")); do_a_b(args); // 同时设置两个参数 let args = Args::new().a(|| println!("Doing A!")).b(|| println!("Doing B!")); do_a_b(args); }
优点:支持所有类型的FnMut闭包(包括捕获变量的),语法简洁;缺点:存在动态分发的轻微性能开销。
方案2:静态分发的构建器模式(无动态开销)
如果希望保留静态分发,可通过构建器的类型推导来替换默认的空闭包:
// 初始构建器,默认使用空函数指针 struct ArgsBuilder<A, B> where A: FnMut(), B: FnMut(), { a: A, b: B, } impl ArgsBuilder<fn(), fn()> { fn new() -> Self { Self { a: || {}, b: || {}, } } // 替换a参数,推导新的类型 fn a<NewA: FnMut()>(self, a: NewA) -> ArgsBuilder<NewA, fn()> { ArgsBuilder { a, b: self.b } } // 替换b参数,推导新的类型 fn b<NewB: FnMut()>(self, b: NewB) -> ArgsBuilder<fn(), NewB> { ArgsBuilder { a: self.a, b } } // 构建最终的Args结构体 fn build(self) -> Args<impl FnMut(), impl FnMut()> { Args { a: self.a, b: self.b } } } struct Args<A: FnMut(), B: FnMut()> { pub a: A, pub b: B, } fn do_a_b<A: FnMut(), B: FnMut()>(mut args: Args<A, B>) { (args.a)(); (args.b)(); } fn main() { let do_a = || println!("Doing A!"); let args = ArgsBuilder::new().a(do_a).build(); do_a_b(args); let args = ArgsBuilder::new().b(|| println!("Doing B!")).build(); do_a_b(args); }
优点:完全静态分发,无性能损耗;缺点:构建器的类型会随参数替换变化,语法比方案1稍繁琐。
Rust的限制与相关RFC
当前Rust确实不支持默认函数参数,也无法直接让结构体更新语法自动适配不同的泛型类型(因为Default实现是针对具体泛型参数的)。
相关的改进讨论包括:
- RFC 2137:Default Function Arguments:直接为函数添加默认参数支持,目前处于活跃讨论阶段,尚未合并到稳定版。
- 结构体默认值与类型推导的优化:社区有讨论让结构体更新语法更灵活,但暂无明确的落地计划。
内容的提问来源于stack exchange,提问作者Rufus
相关产品推荐
相关产品推荐

