Reputation: 10025
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
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.'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