【问题标题】:Create a vector of closures from any type to any other type创建从任何类型到任何其他类型的闭包向量
【发布时间】:2017-11-26 18:46:07
【问题描述】:

我正在尝试创建一个函数向量,其想法是第一个函数的输出将通过管道传输到第二个函数的输入,等等。

我很难理解如何(如果可能的话)将其编码到 Rust 类型系统中。我尝试使用泛型,但遇到了问题,因为泛型期望函数向量中的每个元素的输入和输出始终相同。

例如,功能一可能是i32 -> String,功能二可能是String -> bool,功能三可能是bool -> f64

我的尝试:

fn main() {
    let mut funcs: Vec<Box<Fn(i32) -> i32>> = Vec::new();

    funcs.push(Box::new(|a| a * 2));
    funcs.push(Box::new(|b| b * 3));

    // This won't work since it's not a Fn(i32) -> i32
    funcs.push(Box::new(|c| String::from(c)));

    // How can I create a vec of Fn(anything) -> anything where the anythings can be different for every item in the vector?
}

我开始认为在 Rust 中唯一可行的方法是使用宏创建具有特定数量元素的结构。

【问题讨论】:

  • 为什么需要将其存储在Vec 中?这似乎是这里冲突的核心。 Vec 定义为具有相同类型的项目列表,使其不适合您想要的。
  • @loganfsmyth 我还可以使用什么其他类型的集合?我想要一个结构有一组函数
  • 如果您的目标是创建一个共同馈送的函数链,例如,您可以返回一个将两个函数组合在一起的新函数,从而完全避免收集。对于组合的函数链,集合不是一个好的数据结构。如果您的目标是获取集合并按顺序调用每个函数,最好让结构存储一个将调用链的函数,这样结构就会知道整个链的输入和输出类型。
  • 从任何类型到任何其他类型——当输出匹配后续输入时会发生什么?
  • 我觉得你最好把你的问题作为细节来问。这对我来说有点像XY problem。我了解您要解决的问题,但 Vec 并不是解决该问题的好方法。使用Any,您可以在此处获得针对您提出的特定问题的答案,但对于您的问题的真正解决方案,答案是不要使用 Vec。我个人认为事件循环很容易遵循,它可以解决您的真正目标,但可能有很多方法可以考虑这一点。

标签: generics rust


【解决方案1】:

您可以在 Box 中使用 Any 特征,但是:

  • 如果类型不匹配,您将收到运行时错误,而不是编译时错误
  • 性能可能会很糟糕

Playground

use std::any::Any;

pub type FnAnyToAny = Fn(Box<Any>) -> Box<Any>;

pub fn make_any_to_any<I, O, F>(f: F) -> Box<FnAnyToAny>
where
    I: 'static,
    O: 'static,
    F: Fn(I) -> O + 'static,
{
    Box::new(move |i: Box<Any>| -> Box<Any> {
        let i: Box<I> = Box::<Any + 'static>::downcast(i).expect("wrong input type");
        Box::new(f(*i))
    })
}

pub fn run_all_any<I, O>(funcs: &Vec<Box<FnAnyToAny>>, i: I) -> O
where
    I: 'static,
    O: 'static,
{
    let i: Box<Any> = Box::new(i);
    let o = funcs.iter().fold(i, |acc, f| f(acc));
    let o: Box<O> = Box::<Any + 'static>::downcast(o).expect("wrong output type");
    *o
}

fn main() {
    let mut funcs: Vec<Box<FnAnyToAny>> = Vec::new();

    funcs.push(make_any_to_any(|a: i32| a * 2));
    funcs.push(make_any_to_any(|b: i32| b * 3));

    funcs.push(make_any_to_any(|c: i32| format!("{}", c)));

    println!("{:?}", run_all_any::<i32, String>(&funcs, 4));
}

在您提到的 cmets 中,您实际上想要处理大量输入项并在单独的线程中运行每个函数。我认为rayon crate 应该可以很好地处理这个问题:

Playground

extern crate rayon;
use rayon::prelude::*;

fn main() {
    let input = vec![1, 2, 3, 4];

    // without parallel processing you'd start with:
    // let output = input.into_iter()
    let output = input
        .into_par_iter()
        .map(|a| a * 2)
        .map(|b| b * 3)
        .map(|c| format!("{}", c))
        .collect::<Vec<_>>();

    println!("{:?}", output);
}

如果你真的等了很多,例如对于网络数据(而不是实际需要 CPU 时间),您可能需要查看 futures crate,尤其是 futures::stream::Stream

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2011-04-28
    • 1970-01-01
    • 1970-01-01
    • 2012-07-27
    • 1970-01-01
    • 2022-01-26
    • 2021-06-12
    相关资源
    最近更新 更多