我有一个Rust项目的想法,我希望有一个可重用的库,允许TOML文件指定要调用的函数序列,沿着传递给这些函数的所需参数。这些函数需要是异步的。
我想要的是:
#[derive(Serialize, Deserialize)]
struct Config {
functions: IndexMap<String, dyn Fn() -> dyn Future<Output = Result<(), ()>>>,
}
但这并不起作用(我认为有多种原因)。
我已经设法让PoC使用Enum作为中间类型:
use indexmap::IndexMap;
use serde::{Deserialize, Serialize};
use toml::toml;
async fn without_args() -> Result<(), ()> {
println!("Function without args");
Ok(())
}
async fn with_args(arg: &str) -> Result<(), ()> {
println!("Function with the arg = {arg}!");
Ok(())
}
async fn sum_args(x: u64, y: u64) -> Result<(), ()> {
println!("Function for the sum {x} + {y} = {}!", x + y);
Ok(())
}
#[derive(Deserialize, Serialize)]
#[serde(tag = "function", content = "args")]
enum FnOptions {
Without,
With(String),
Sum(u64, u64),
}
impl FnOptions {
async fn perform(&self) -> Result<(), ()> {
match self {
FnOptions::Without => without_args().await,
FnOptions::With(arg) => with_args(arg).await,
FnOptions::Sum(x, y) => sum_args(*x, *y).await,
}
}
}
#[derive(Serialize, Deserialize)]
struct Config {
functions: IndexMap<String, FnOptions>,
}
#[tokio::main]
async fn main() {
let toml = toml! {
[functions.foo]
function = "Without"
[functions.bar]
function = "With"
args = "baz"
[functions.sum]
function = "Sum"
args = [ 1, 2 ]
};
let config: Config = toml.try_into().unwrap();
for func in config.functions {
func.1.perform().await.unwrap();
}
}
我也可以删除Enum,只使用一个match语句,将函数名直接匹配到要调用的函数,但我认为这样更干净。
然而,这对于库来说不够通用,因为用户需要手动维护Enum和perform()
函数,该函数基于Enum变体调用正确的函数。
也许我可以创建一个宏来完成这个任务,但是我对宏没有太多的经验(没有使用proc-macros)。
我希望我错过了一个明显的解决方案。
1条答案
按热度按时间oprakyz71#
你可以有一个全局的函数名Map,你可以注册可以从配置中调用的函数。
然后你会像这样使用图书馆
这只是一个快速实现,它肯定可以改进。这只是给予你一个想法。例如,注册的函数不必是全局的。你可以有一个包含函数的结构体,并有方法
register
和perform
。你可以为FnArgs
实现反序列化器,而不是使用中间结构体进行转换。你可以使用Vec<String>
作为args,但是我使用了枚举作为例子,所以你可以有一些变体来避免解析(例如,你可以有一个特殊的整型参数变体)。这主要是为了给予你一些想法,你可以如何实现这个功能。缺点是所有可以通过config访问的函数必须有相同的签名,并且需要解析它们的参数。使用proc宏可以删除一些样板文件,但我没有太多经验。