如何将切片的每个元素作为单独的参数传递给可变参数C函数?

4
我正在使用Rust构建一个Redis模块。我找到了一些很好的示例,但是当处理应该接受变长参数的C函数时遇到了困难。
Redis模块C SDK有一个名为RedisModule_Call的函数,它接受一些特定的参数,然后是代表Redis命令的n个参数。从Redis模块SDK文档(用C编写)中可以看到:
RedisModuleCallReply *reply;
reply = RedisModule_Call(ctx,"INCR","sc",argv[1],"10");
RedisModule_Call的前三个参数是特定的,但其余参数代表可能有数百个参数的Redis命令。
在Rust中,我遵循 Redis-Cell中的模式,它是一个已成功实现的Rust Redis模块。该模块非常棒,但处理这个特殊问题的方式非常有限。实际上,它以一种有点蛮力的方式接受最多三个参数:
pub fn call(&self, command: &str, args: &[&str]) -> Result<Reply, CellError> {
   // ... code ... 
   let raw_reply = match args.len() {
        1 => raw::call1::call(/* ... */),
        2 => raw::call2::call(/* ... */),
        // ...

这些call1call2函数实际上只是处理不同参数长度的存根。
pub mod call2 {
    use redis::raw;

    pub fn call(
        ctx: *mut raw::RedisModuleCtx,
        cmdname: *const u8,
        fmt: *const u8,
        arg0: *mut raw::RedisModuleString,
        arg1: *mut raw::RedisModuleString,
    ) -> *mut raw::RedisModuleCallReply {
        unsafe { RedisModule_Call(ctx, cmdname, fmt, arg0, arg1) }
    }

    #[allow(improper_ctypes)]
    extern "C" {
        pub static RedisModule_Call: extern "C" fn(
            ctx: *mut raw::RedisModuleCtx,
            cmdname: *const u8,
            fmt: *const u8,
            arg0: *mut raw::RedisModuleString,
            arg1: *mut raw::RedisModuleString,
        ) -> *mut raw::RedisModuleCallReply;
    }
}

我需要能够在运行时传入n个参数,其中n由程序确定,因此硬编码的方法不实用。我知道Rust对可变参数函数的支持有限,并且我已经阅读了RFC 2137的一些内容,但我不确定是否适用于这种情况。
我正在寻找一种将参数向量应用于RedisModule_Call的方法,或者类似于参数展开语法的东西。我相对而言是Rust的新手,但我已经进行了搜索,但似乎找不到在Rust中解决这个问题的任何方法。
澄清一下 - 我可以轻松地将参数传递给RedisModule_Call(它是可变参数的),但是我无法找到一种在Rust中传递可变数量的参数到C函数的语法方式。我的目标是实现类似于以下内容:
impl Redis {
    pub fn call(&self, command: &str, args: &[&str]) -> Result<Reply, CellError> {
        /* ... */

       unsafe { RedisModule_Call(ctx, cmdname, fmt, ...args) }
       /* ... */ 

...args 是一种黑魔法,可以让 args 表示一个参数或 100 个参数,这相当于 RedisModule_Call(ctx, cmdname, fmt, args[0], args[1] /* ... and so on */)


1
我需要能够传入n个参数,其中n在运行时确定,因此硬编码的方法不实用。不清楚你在这里要求什么,C标准不允许这样的事情。 - Stargateur
1个回答

7

你现在还不能这样做,而且我敢打赌你可能永远也做不到。

要实现这个功能,你需要两个关键能力,但这两个能力都超出了你的控制范围:

  1. Redis需要提供一个接受va_list参数的函数,而不仅仅是...

    奇怪的是Redis目前没有提供这样的函数,但也许这表明其他实现模块的人完全避免了这个问题。

  2. Rust需要提供一种构造va_list参数的方法。

    尽管看起来RFC 2137将引入一个VaList类型,但所提议的API既没有提供创建它的方法,也没有提供在其中设置值的方法。

请注意,即使在C语言中,你也无法轻松或可移植地做到想要的事情


那么你该怎么办呢?假设你正在实现消耗变参参数的代码,你可以从调用中删除变参,而是只传递一个C中的项目集合,它只是一个指针和一个长度:

extern "C" {
    fn call(n_args: i32, ...);
}

fn x(args: &[i32]) {
    unsafe { call(2, args.len(), args.as_ptr()) };
}

如果您无法控制另一侧读取代码的方式,一个可能的(也就是说:可怕的)想法是在切片的足够大的子集上进行模式匹配,并调用可变参数函数:

extern "C" {
    fn call(n_args: i32, ...);
}

fn x(args: &[i32]) {
    unsafe {
        match args {
            [] => call(0),
            [a] => call(1, a),
            [a, b] => call(2, a, b),
            _ => panic!("Didn't implement this yet"),
        }
    }
}

另请参阅:


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接