You need to enable JavaScript to run this app.
最新活动
大模型
产品
解决方案
定价
生态与合作
支持与服务
开发者
了解我们

如何编写可便捷接收函数列表的并发API?

如何编写可便捷接收函数列表的并发API?

兄弟,我太懂你这种想写出像C那样简洁调用的并发函数的需求了!在Rust里直接用&[&dyn Fn()]确实会踩坑——毕竟每个闭包都是独有的匿名类型,编译器根本没法把它们塞进同一个数组里,也不会自动帮你转成 trait 对象,这和Cstd::function隐式转换不一样。不过别担心,有几个办法能帮你实现近似的便捷调用,我给你详细说下:

方案一:用Vec<Box<dyn Fn()>>直接接收

这是最直接的思路,把每个闭包装箱成 trait 对象,因为Box<dyn Fn()>是大小固定的类型,能顺利放进Vec里。代码大概是这样:

fn fan_out(fns: Vec<Box<dyn Fn()>>) {
    let mut thread_handles = vec![];
    // 逐个启动线程执行函数
    for func in fns {
        thread_handles.push(std::thread::spawn(move || func()));
    }
    // 等待所有线程完成
    for handle in thread_handles {
        handle.join().unwrap();
    }
}

// 调用的时候给每个闭包套个Box::new
fan_out(vec![
    Box::new(|| println!("taco")),
    Box::new(|| println!("burrito")),
]);

这个方案的缺点是每次调用都要手动写Box::new,有点啰嗦,但胜在简单直接,容易理解。

方案二:用宏简化调用(最接近你想要的写法)

如果想完全去掉Box::new的冗余代码,写个宏来帮我们自动处理就好!宏可以在编译期把每个传入的闭包自动包裹成Box::new,然后收集成Vec传给实际的实现函数:

// 实际的逻辑实现函数
fn fan_out_impl(fns: Vec<Box<dyn Fn()>>) {
    let mut thread_handles = vec![];
    for func in fns {
        thread_handles.push(std::thread::spawn(move || func()));
    }
    for handle in thread_handles {
        handle.join().unwrap();
    }
}

// 定义宏来简化调用
macro_rules! fan_out {
    ($($func:expr),* $(,)?) => {
        fan_out_impl(vec![$(Box::new($func)),*])
    };
}

// 现在调用起来就和你想要的一模一样了!
fan_out!(
    || println!("taco"),
    || println!("burrito"),
);

这个宏还支持最后加个逗号的写法(就是fan_out!(|| println!("taco"), || println!("burrito"),);),更符合Rust的编码习惯,用起来和C++的写法几乎没差别。

方案三:泛型可变参数实现(静态分发,无额外开销)

如果追求极致性能,不想用 trait 对象带来的动态分发开销,可以用递归泛型trait来实现可变参数的调用。这种方案里每个闭包都保持自己的具体类型,完全是静态编译:

use std::thread;

// 对外暴露的入口函数,接收第一个闭包和剩余的参数
fn fan_out<F, Rest>(first_func: F, rest: Rest)
where
    F: FnOnce() + Send + 'static,
    Rest: FanOut,
{
    let handle = thread::spawn(first_func);
    rest.fan_out();
    handle.join().unwrap();
}

// 定义一个trait来处理剩余的参数
trait FanOut {
    fn fan_out(self);
}

// 终止条件:没有剩余参数时什么都不做
impl FanOut for () {
    fn fan_out(self) {}
}

// 递归处理剩余的每个参数
impl<F, Rest> FanOut for (F, Rest)
where
    F: FnOnce() + Send + 'static,
    Rest: FanOut,
{
    fn fan_out(self) {
        let handle = thread::spawn(self.0);
        self.1.fan_out();
        handle.join().unwrap();
    }
}

// 调用方式也很简洁,直接传多个闭包就行
fan_out(
    || println!("taco"),
    || println!("burrito"),
);

这个方案的优点是完全没有动态分发的开销,缺点是实现起来稍微复杂一点,而且参数是逐个传递的,不是放在数组/vec里,但调用体验依然很流畅。

总结一下:如果想要最接近你预期的简洁调用,宏的方案是首选;如果追求性能,泛型可变参数的方案更合适;如果能接受一点点代码冗余,直接用Vec<Box<dyn Fn()>>也完全没问题。

内容来源于stack exchange

火山引擎 最新活动