Rust で Redis module から HSCAN を呼ぶサンプル

HSCAN を呼び出す Redis module を Rust で書きます。

Redis module から HSCAN を呼んで配列を返す - umoriguのブログ の Rust 版です。

環境

  • Ubuntu 18.04
  • Redis 4.0.9
  • Rust 1.33 

ソースコード

github.com

src/lib.rs メイン処理 (fn hscan_hello_redis_command()):

extern "C" fn hscan_hello_redis_command(
    ctx: *mut RedisModuleCtx,
    argv: *mut *mut RedisModuleString,
    argc: c_int,
) -> Status {
    let args = parse_args(argv, argc).unwrap();
    if args.len() != 2 {
        return rm_wrong_arity(ctx);
    }
    let key_str = &args[1];

    rm_log(ctx, "notice", "Before call()");
    rm_reply_with_array(ctx, REDISMODULE_POSTPONED_ARRAY_LEN);
    rm_reply_with_string_buffer(ctx, key_str);
    let reply = rm_call(ctx, "HSCAN", key_str, "0");
    if rm_call_reply_type(reply) != ReplyType::Array {
        rm_log(ctx, "warning", "not array");
        return Status::Err;
    }
    rm_log(ctx, "notice", "After call()");
    let length0 = rm_call_reply_length(reply);
    if length0 != 2 {
        rm_log(ctx, "warning", "length0 is NOT 2");
        return Status::Err;
    }
    let r0 = rm_call_reply_array_element(reply, 0);
    if rm_call_reply_type(r0) == ReplyType::String {
        let mut len = 0;
        let s = rm_call_reply_string_ptr(r0, &mut len);
        match from_byte_string(s, len) {
            Ok(result) => {
                let _ = rm_reply_with_string_buffer(ctx, &result);
            }
            Err(_msg) => rm_log(ctx, "error", "from_utf8_error"),
        }
    } else {
        rm_reply_with_string_buffer(ctx, "ERR");
    }
    let r1 = rm_call_reply_array_element(reply, 1);
    let length = rm_call_reply_length(r1);

    for i in 0..length {
        let r = rm_call_reply_array_element(r1, i);
        if rm_call_reply_type(r) == ReplyType::String {
            let mut len = 0;
            let s = rm_call_reply_string_ptr(r, &mut len);
            match from_byte_string(s, len) {
                Ok(result) => {
                    let _ = rm_reply_with_string_buffer(ctx, &result);
                }
                Err(_msg) => rm_log(ctx, "error", "from_utf8_error"),
            }
        } else {
            rm_reply_with_string_buffer(ctx, "[non str]");
        }
    }
    rm_free_call_reply(reply);
    rm_log(ctx, "notice", "NOTICE!");
    rm_reply_with_long_long(ctx, length);
    rm_reply_with_string_buffer(ctx, "end");
    rm_reply_set_array_length(ctx, (length + 4) as i64);
    return Status::Ok;
} 

解説

Rustのコードから unsafeなしで呼び出せるようにRedis module API の関数を呼び出すラッパー関数を それぞれ作成する。

例:

    // int RedisModule_ReplyWithArray(RedisModuleCtx *ctx, long len);
    static RedisModule_ReplyWithArray:
        extern "C" fn(ctx: *mut RedisModuleCtx, len: c_long) -> Status;

に対して

fn rm_reply_with_array(ctx: *mut RedisModuleCtx, len: c_long) -> Status {
    unsafe { RedisModule_ReplyWithArray(ctx, len) }
}

のようなラッパー関数を定義して、Rust側で呼び出す - rm_reply_with_array(ctx);

Redis (C) 側の関数が可変長引数をとる場合、まず実際に利用する1つの引数パターンを使い関数プロトタイプを Rust で定義する。今回は RedisModule_Log()RedisModule_Call() が該当する。

    // void RedisModule_Log(RedisModuleCtx *ctx, const char *levelstr, const char *fmt, ...);
    static RedisModule_Log:
        extern "C" fn(ctx: *mut RedisModuleCtx, levelstr: *const u8, fmt: *const u8);
    // RedisModuleCallReply *RedisModule_Call(RedisModuleCtx *ctx, const char *cmdname, const char *fmt, ...);
    static RedisModule_Call: extern "C" fn(
        ctx: *mut RedisModuleCtx,
        cmdname: *const u8,
        fmt: *const u8,
        arg0: *const u8,
        arg1: *const u8,
    ) -> *mut RedisModuleCallReply;

なお、可変長引数の関数を可変長引数のまま扱うには別のテクニックが必要。

redis-cell/raw.rs at master · brandur/redis-cell · GitHub では引数の数ごとに Rust module を分けて対応している ( call1, call2, call3 module )

ビルド・配置

$ cargo build
$ sudo cp target/debug/librust_hscanhello_redis_module.so /opt/
$ ls -l /opt/librust_hscanhello_redis_module.so
-rwxr-xr-x 1 root root 3783208 Oct 14 11:49 /opt/librust_hscanhello_redis_module.so

実行

$ redis-cli
127.0.0.1:6379> module load /opt/librust_hscanhello_redis_module.so
OK
127.0.0.1:6379> hscan hello 0
1) "0"
2) (empty list or set)
127.0.0.1:6379> hset hello a a1
(integer) 1
127.0.0.1:6379> hset hello b b1
(integer) 1
127.0.0.1:6379> hscan hello 0
1) "0"
2) 1) "a"
   2) "a1"
   3) "b"
   4) "b1"
127.0.0.1:6379> rusthscan hello
1) "hello"
2) "0"
3) "a"
4) "a1"
5) "b"
6) "b1"
7) (integer) 4
8) "end"
127.0.0.1:6379> 

HSCANを内部で実行するRedis moduleをRustで書くことができた。

参考