Yuri Astrakhan
Yuri Astrakhan

Reputation: 10025

Ideomatic Rust to storing/freeing externally allocated memory as a ref

An external C library allocates/frees memory via FFI. My rust code is a thin wrapper layer called by C, and allows other Rust developers to write safe Rust code without dealing with FFI from C. My layer needs to hold a reference to C-allocated object and do the Drop when requested by C code to cleanup. What would be the idiomatic way to store a reference to that struct and later free it in this case?

P.S. I am writing an FFI layer to allow safe Rust plugins to Varnish (large C program). Varnish calls my wrapper layer, which needs to restore context on each callback from some *void pointers before passing them to user Rust code. Sometimes the callback is to clean up resources - so I must restore context, perform drop on user's T, followed by a C-call to free on the FFI C struct that held a pointer to T.

// Code generated by bindgen, from a C header file
#[repr(C)]
pub struct c_object {
    // Here I store a pointer to T - a Rust-allocated user obj
    pub user_obj: *mut ::std::ffi::c_void,
}

extern "C" {
    pub fn allocate_obj() -> *const c_object;
    pub fn free_obj(obj: *mut *const c_object);  // double-ref to set my ptr to NULL
}


// Safe Rust wrapper I need to design
// lifetime tied to some context object not specified here
struct Wrapper<'a> {
    // storing it as a reference to use Rust's ref checks
    // and avoid null-ref checks on each access
    obj: &'a c_object,
}

impl<'a> Wrapper<'a> {
    fn from_ptr(ptr: *const c_object) -> Self {
        Wrapper {
            // Asserting that the pointer is not null just once
            obj: ptr.as_ref().unwrap(),
        }
    }
}

impl Drop for Wrapper<'_> {
    fn drop(&mut self) {
        // How to call free_obj() here?
        // I need to pass a double-ref to set my ptr to NULL
        // Essentially after this drop Wrapper instance content is undefined?
    }
}

Upvotes: 2

Views: 120

Answers (1)

Finomnis
Finomnis

Reputation: 22808

There are a couple of architectural decisions in your code that I would change:

  • Wrapper should not have a lifetime. It owns its value, and doesn't depend on anything external. This should be obvious when thinking about the fact that its destructor destroys everything it depends on, nothing external has to be 'given back'. Be aware that lifetimes don't keep anything alive, they are just a way for the compiler to track dependencies. And Wrapper doesn't depend on anything, everything it contains is owned by itself.
  • Wrapper::from_ptr absolutely needs to be unsafe, as there's no sound way of implementing it without making unsafe assumptions. Read this for more information about soundness.
  • Do not use a reference. Like 'a, a reference is for storing a dependency to something owned, and we are the owner ourselves. Instead, us a raw pointer directly. Your situation is exactly what raw pointers are for.

All of that said, here's some code that demonstrates how working code would look like:

// Code generated by bindgen, from a C header file
#[repr(C)]
pub struct c_object {
    // some fields
    x: core::ffi::c_uint,
}

extern "C" {
    pub fn allocate_obj() -> *const c_object;
    pub fn free_obj(obj: *mut *const c_object); // double-ref to set my ptr to NULL
}

// Dummy implementation for demo purposes
pub mod dummy_c_impls {
    use super::c_object;

    #[no_mangle]
    pub extern "C" fn allocate_obj() -> *const c_object {
        let ptr = Box::into_raw(Box::new(c_object { x: 42 }));
        println!("Allocate {:?}", ptr);
        ptr
    }
    #[no_mangle]
    pub extern "C" fn free_obj(obj: *mut *const c_object) {
        if let Some(obj) = unsafe { obj.as_mut() } {
            let mut ptr = std::ptr::null();
            std::mem::swap(obj, &mut ptr);

            if !ptr.is_null() {
                println!("Free {:?}", ptr);
                unsafe {
                    std::mem::drop(Box::from_raw(ptr as *mut c_object));
                }
            }
        }
    }
}

// Define Wrapper in a mod to prevent other objects from modifying ptr
mod wrapper {
    use super::{c_object, free_obj};

    // Safe Rust wrapper I need to design
    // lifetime tied to some context object not specified here
    pub struct Wrapper {
        // storing it as a reference to use Rust's ref checks
        // and avoid null-ref checks on each access
        ptr: *const c_object,
    }

    impl Wrapper {
        // Absolutely needs unsafe here, as there's not way of
        // implementing this function without unsafe assumptions
        pub unsafe fn from_ptr(ptr: *const c_object) -> Self {
            Self { ptr }
        }
    }

    impl Drop for Wrapper {
        fn drop(&mut self) {
            unsafe {
                free_obj(&mut self.ptr);
            }
        }
    }

    impl core::ops::Deref for Wrapper {
        type Target = c_object;

        fn deref(&self) -> &Self::Target {
            unsafe { self.ptr.as_ref() }.unwrap()
        }
    }
}

fn main() {
    use wrapper::Wrapper;

    let value = unsafe { Wrapper::from_ptr(allocate_obj()) };
    println!("{}", value.x);
}
Allocate 0x2681b09aef0
42
Free 0x2681b09aef0

Upvotes: 0

Related Questions