理想的 Rust 用于存储/释放外部分配的内存作为引用

问题描述 投票:0回答:1

外部 C 库通过 FFI 分配/释放内存。我的 Rust 代码需要保存对它的引用并执行 Drop。在这种情况下,存储对该结构的引用并随后释放它的惯用方法是什么?

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

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?
    }
}
rust ffi ref
1个回答
0
投票

我会更改您的代码中的一些架构决策:

  • Wrapper
    不应该有一生。它拥有自己的价值,不依赖于任何外部因素。当考虑到它的析构函数破坏了它所依赖的所有东西这一事实时,这一点应该是显而易见的,没有任何外部的东西必须被“归还”。请注意,生命周期不会让任何东西保持活动状态,它们只是编译器跟踪依赖关系的一种方式。而且
    Wrapper
    不依赖于任何东西,它所包含的一切都是它自己拥有的。
  • allocate_obj
    绝对需要是
    unsafe
    ,因为没有可靠的方法可以在不做出不安全假设的情况下实现它。阅读this了解有关健全性的更多信息。
  • 请勿使用参考。与
    'a
    一样,引用用于存储对所拥有事物的依赖关系,而我们自己就是所有者。相反,我们直接使用原始指针。您的情况正是原始指针的用途。

综上所述,这里有一些代码演示了工作代码的样子:

// 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
© www.soinside.com 2019 - 2024. All rights reserved.