Scope for structs and raw pointers for FFI/C library

I started experimenting a bit with FFI and bindings for some C libraries, and have some questions regarding the scope of structs along with raw pointers.

I used bindgen to create bindings for a C library. The generated bindings use c_void in arguments quite heavily for callbacks to first initialize a context:

fn some_init_func(context: *mut *mut c_void, ...)

updating it:

fn some_update_func(context: *mut c_void, ...)

and eventually some action/finalizing:

fn some_action_func(context: *mut c_void, ...)

So I type cast the raw c_void pointer to a Rust struct I'd like to work with and then modify it as required. For example, simplifying the some_init_func above:

fn some_init_func(context: *mut *mut c_void, ...) {
    let mut rust_context = MyContext::new();
    ...
    let raw_rust_context = &mut rust_context as *mut _ as *mut c_void;
    *context = raw_rust_context;
}

Question 1
In this case, will the raw pointer argument context still be valid after some_init_func has run? To me it feels like Rust would drop the struct rust_context (since it's only scoped to this function), does it also clean up the memory so the raw context pointer would not be correctly initialized any more? The rust book, in the chapter on unsafe, says:

Raw pointers don't have any automatic clean up

I'm not sure I understand it in the example above.

Question 2
Later on, when a callback occurs to the function that prepares and updates some field in this context struct, I type cast the argument to a struct again but do I also need to cast it back to a raw pointer after mutating it?

fn some_update_func(context: *mut c_void, ...) {
    let mut rust_context = &mut *(context as *mut MyContext);
    rust_context.index = 42;

    // Is the above enough to update the raw pointer context? Or do I need to also
    // cast the rust Struct back to a raw pointer and reassign to context?
    // e.g.
    // let raw_rust_context = &mut rust_context as *mut _ as *mut c_void;
    // *context = raw_rust_context;
}
  • Question 1 : indeed, Rust does drop rust_context and all the bytes that make it, so you have an invalid pointer.
    So you should either:
    • Give the rust_context to C. This is only possible if MyContext has a C-compatible layout:
    #[repr(C)]
    struct MyContext {
        // ...
    }
    
    // !!! there need to be some memory allocated at the end of `context` !!!
    extern "C" fn some_init_func(context: *mut c_void) {
        unsafe { std::ptr::write(context as *mut MyContext, MyContext::new()) }
    }
    
    • Use a box:
    // This time, Rust allocate the memory needed
    extern "C" fn some_init_func(context: *mut *mut c_void) {
        let context_ptr = Box::leak(Box::new(MyContext::new())) as *mut _ as *mut c_void;
        unsafe { *context = context_ptr }
    }
    
    And maybe add a function to deallocate this box:
    extern "C" fn drop_context(context: *mut c_void) {
        drop(unsafe { Box::from_raw(context as *mut MyContext) })
    }
    
  • Question 2 : The some_update_func seems correct: you are passing a reference (well, here a pointer) to the function, which then modify what is behind this reference, so your context should be updated :slightly_smiling_face:
1 Like

Thank you, that makes sense!

I wish I knew how to Google these things, I used Box a lot but did not know about leak.

I'm going with the Box approach since I don't know if there's any memory allocated for this context (though I guess the only memory that needs to be allocated is for the pointer itself, right?), and I'm also provided with yet another handy callback further down the line that is specifically meant to clean up the memory for the context - a good place to "drop the box". :smiley:

Bonus question: For dropping the context, would it not be sufficient to simply create a box from the value? Would it not be automatically dropped/cleaned up using ordinary Rust rules when that function finishes?

It is sufficient to just let the Box go out of scope after creating it with from_raw(), though the intent is clearer with an explicit drop().

Also, note that you can convert a box into a pointer more directly with Box::into_raw().

1 Like