外部 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?
}
}
我会更改您的代码中的一些架构决策:
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