skip to Main Content

I’ve been playing around with writing Redis Modules in Rust. This is my first attempt at using Rust FFI and bindings. How do I call this method and end up with a data value in Rust without destroying the Redis pointer?

extern "C" {
    pub static mut RedisModule_GetTimerInfo: ::std::option::Option<
        unsafe extern "C" fn(
            ctx: *mut RedisModuleCtx,
            id: RedisModuleTimerID,
            remaining: *mut u64,
            data: *mut *mut ::std::os::raw::c_void,
        ) -> ::std::os::raw::c_int,
    >;
}

See the RedisModule_GetTimerInfo API Docs for more details.

I ended up getting this to work, but it throws an error if I call it with the same id twice:

let mut ms = 0 as u64;
let val = "";
let ptr = Box::into_raw(Box::new(&mut val)) as *mut *mut c_void;
let ok = unsafe { RedisModule_GetTimerInfo.unwrap()(ctx, id, &mut ms, ptr) };
let mut data: Option<String> = None;
if ok == 0 {
    let val = unsafe { Box::from_raw(*ptr as *mut &str) };
    // trim nul bytes
    data = Some(val.trim_matches(char::from(0)).to_string());
}

This didn’t work because of how Box::from_raw owns the raw pointer and the pointer is destroyed when the box is dropped.

I tried countless ways to make this work without using Box::into_raw & Box::from_raw and all of times they either end up crashing Redis or end up as a pointer that I don’t know how to convert to &str.

Update: I originally had an example of using RedisModule_StopTimer which was a mistake. Corrected to use the method I was asking about.

2

Answers


  1. Chosen as BEST ANSWER

    Using one of the links @Shepmaster added, I was finally able to figure this out. I swear I tried some variation of this but didn't think to try double boxing...

    Here's what I did:

        let val = Box::new(Box::new("") as Box<&str>);
        let ptr = Box::into_raw(val);
        let ok = unsafe { RedisModule_GetTimerInfo.unwrap()(ctx, id, &mut ms, ptr as *mut *mut c_void) };
        let mut data: Option<String> = None;
        if ok == 0 {
            let val = unsafe {**ptr as &str};
            data = Some(val.trim_matches(char::from(0)).to_string());
        }
    

    Thanks all for your help!


  2. I’m one of the maintainers of the redismodule-rs crate, which provides a high-level Rust API for writing Redis modules.

    Prompted by your question, I looked into adding these timer APIs to the crate in a safe manner, and will push the code to the repo once I’m done with it.

    The following code shows how to retrieve the data safely:

    // Create local variables to hold the returned values
    let mut remaining: u64 = 0;
    let mut data: *mut c_void = std::ptr::null_mut();
    
    // Call the API and retrieve the values into the local variables
    let status = unsafe {
        RedisModule_GetTimerInfo.unwrap()(ctx, timer_id, &mut remaining, &mut data)
    };
    
    if status == REDISMODULE_OK {
        // Cast the *mut c_void supplied by the Redis API to 
        // a raw pointer of our custom type:
        let data = data as *mut T; // T is the type of the data, e.g. String
    
        // Dereference the raw pointer (we know this is safe,
        // since Redis should return our original pointer which
        // we know to be good), and turn in into a safe reference:
        let data = unsafe { &*data };
    
        println!("Remaining: {}, data: {:?}", remaining, data);
    }
    
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search