You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何以惯用Rust方式存储并释放外部C分配的内存引用?

惯用Rust方式处理FFI中C分配对象的所有权与释放

你的核心问题在于用引用管理C对象的所有权是错误的:Rust的引用代表"借用"而非"拥有",而Drop trait是为拥有所有权的类型设计的。要安全管理C分配的对象,我们需要让包装器明确持有对象的所有权,通过原始指针配合Rust的类型系统来保证安全。

修正后的包装器实现

use std::marker::PhantomData;
use std::ptr;

// bindgen生成的C代码(保留原定义)
#[repr(C)]
pub struct c_object {
    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);
}

// 安全的Rust包装器:拥有C对象的所有权
struct Wrapper<T> {
    obj: *mut c_object, // 用可变指针支持C侧置空操作
    _marker: PhantomData<T>, // 关联用户类型T,保证类型安全与生命周期跟踪
}

impl<T> Wrapper<T> {
    /// 从C返回的指针创建Wrapper
    /// 仅当指针来自`allocate_obj`且处于有效生命周期时安全
    fn from_ptr(ptr: *const c_object) -> Self {
        // Debug模式下断言指针非空,快速捕获C侧错误
        assert!(!ptr.is_null(), "c_object pointer cannot be null");
        Wrapper {
            obj: ptr as *mut c_object, // 转为可变指针以支持free_obj的置空操作
            _marker: PhantomData,
        }
    }

    /// 获取用户对象的不可变引用
    pub fn user_obj(&self) -> &T {
        unsafe {
            let c_obj = self.obj.as_ref().unwrap();
            assert!(!c_obj.user_obj.is_null(), "user_obj pointer cannot be null");
            &*(c_obj.user_obj as *const T)
        }
    }

    /// 获取用户对象的可变引用
    pub fn user_obj_mut(&mut self) -> &mut T {
        unsafe {
            let c_obj = self.obj.as_mut().unwrap();
            assert!(!c_obj.user_obj.is_null(), "user_obj pointer cannot be null");
            &mut *(c_obj.user_obj as *mut T)
        }
    }
}

impl<T> Drop for Wrapper<T> {
    fn drop(&mut self) {
        unsafe {
            // 1. 先释放Rust分配的用户对象T
            if let Some(c_obj) = self.obj.as_mut() {
                if !c_obj.user_obj.is_null() {
                    // 将裸指针转为Box,自动触发T的drop
                    Box::from_raw(c_obj.user_obj as *mut T);
                    // 置空避免悬垂指针
                    c_obj.user_obj = ptr::null_mut();
                }
            }

            // 2. 调用C的free_obj释放C结构体,同时将Wrapper内的指针置为NULL
            free_obj(&mut self.obj);
            // 此时self.obj已被C置空,后续访问会触发断言
        }
    }
}

// 若C对象支持线程安全,实现Send/Sync(根据Varnish的线程模型调整)
unsafe impl<T: Send> Send for Wrapper<T> {}
unsafe impl<T: Sync> Sync for Wrapper<T> {}

// 禁止Clone:C侧未提供克隆函数,避免非法复制指针
impl<T> Clone for Wrapper<T> {
    fn clone(&self) -> Self {
        panic!("Wrapper cannot be cloned");
    }
}

关键设计思路

  1. 所有权明确化:用*mut c_object替代引用,明确包装器拥有C对象的所有权,有权在Drop中释放它。
  2. 类型安全保障:通过PhantomData<T>关联用户类型,让Rust类型系统跟踪T的生命周期,避免类型错误与悬垂引用。
  3. 资源释放顺序:先释放Rust侧的用户对象T,再调用C的free_obj释放C结构体,符合"谁分配谁释放"的原则。
  4. 安全检查:在关键路径添加断言,debug模式下快速发现无效指针问题,生产环境可根据需求改为返回Result或优雅处理。

适配Varnish回调场景

当Varnish触发清理回调时,你可以从*void指针恢复Wrapper并自动释放资源:

extern "C" fn varnish_cleanup_callback(ctx: *mut std::ffi::c_void) {
    unsafe {
        // 将void指针转为Wrapper<T>的裸指针,用Box获取所有权
        let _wrapper = Box::from_raw(ctx as *mut Wrapper<MyUserType>);
        // Box被drop时,自动触发Wrapper的Drop逻辑,完成资源释放
    }
}

内容的提问来源于stack exchange,提问作者Yuri Astrakhan

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.18 13:58:11