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

Rust与C FFI中Arc的惯用安全用法技术问询

Rust FFI中Arc与C交互的引用计数安全问题

问题背景

需要实现一套供C调用的Rust FFI,核心需求如下:

  • Rust侧以Arc<T>形式返回对象引用,T为存储数据的自定义结构体;
  • C侧需完成对象创建、数据读取、内存释放三个操作;
  • 当前实现将Arc<T>转换为*const T裸指针传递给C,C将其视为void*使用,代码如下:
use std::sync::Arc;

pub struct my_struct {
    // some fields
}

pub fn init_my_struct(/*some parameters*/) -> Arc<my_struct> {
    // do stuff
    new_struct // new struct is of type Arc<my_struct> with field values initialized
}

pub extern "C" fn c_wrap_init_my_struct(/*some parameters*/) -> *const my_struct {
    // cast to raw pointer, hold by C
    Arc::into_raw(init_my_struct(/*some parameters*/))
}

pub extern "C" fn c_wrap_get_data_from_struct(my_struct_ptr: *const my_struct) -> /*some data type*/
{
    // reconstruct the rust object to extract a value
    // increment strong counter before re-constructing the object from pointer
    unsafe { Arc::increment_strong_count(my_struct_ptr) };
    // mys has ref count two due to increment above
    let mys = unsafe { Arc::from_raw(my_struct_ptr) };
    // do stuff to extract value from mys
    value // return value
} // mys goes out of scope and ref count of my_struct_ptr is decremented to one (2-1=1).

pub extern "C" fn c_wrap_drop_my_struct(my_struct_ptr: *const my_struct) {
    unsafe {
        assert!(!my_struct_ptr.is_null());
        drop(Arc::from_raw(my_struct_ptr)); // ref count drops to zero --> object is discarded.
    }
}

疑问:手动增减裸指针的引用计数是否安全?是否符合Rust惯用写法?若不增加计数,getter函数中引用计数会降至0导致对象被销毁,这不符合预期。


问题分析与修正方案

你的核心思路(用Arc管理跨语言对象的生命周期)是正确的,但当前实现的细节存在不安全的用法,主要问题在于c_wrap_get_data_from_struct中对Arc::from_raw的误用:

Arc::from_raw的语义是接管裸指针的所有权,要求该指针必须是由Arc::into_raw生成,且此后裸指针不能再被其他代码使用(除非再次通过Arc::into_raw转换回去)。你的代码中,C仍持有该裸指针的情况下调用Arc::from_raw,会造成双重所有权问题——Rust的Arc和C侧同时认为自己拥有该指针的管理权,这违反了Rust的内存安全约定,即使当前逻辑看似能运行,也容易引入难以追踪的bug。

以下是两种符合Rust惯用写法的安全实现方案:

方案一:临时增减引用计数,直接通过裸指针访问数据

这种方式无需构造Arc实例,仅临时调整引用计数确保访问期间对象不被销毁,是最简洁的实现:

pub extern "C" fn c_wrap_get_data_from_struct(my_struct_ptr: *const my_struct) -> i32 {
    unsafe {
        // 先检查指针有效性,避免空指针访问
        if my_struct_ptr.is_null() {
            // 可返回错误码,FFI中避免panic
            return -1;
        }
        // 增加强引用计数,确保访问期间对象不会被C侧提前释放
        Arc::increment_strong_count(my_struct_ptr);
        // 将裸指针转换为不可变引用,安全访问数据
        let mys = &*my_struct_ptr;
        // 提取数据(假设结构体有一个i32类型的data字段)
        let value = mys.data;
        // 访问完成后减少引用计数,恢复C侧持有的计数状态
        Arc::decrement_strong_count(my_struct_ptr);
        value
    }
}

方案二:恢复Arc并克隆,再将原指针交还给C

这种方式通过克隆Arc获得临时访问权,确保原指针的所有权始终归C侧持有:

pub extern "C" fn c_wrap_get_data_from_struct(my_struct_ptr: *const my_struct) -> i32 {
    unsafe {
        if my_struct_ptr.is_null() {
            return -1;
        }
        // 从裸指针恢复Arc,此时接管指针所有权
        let original_arc = Arc::from_raw(my_struct_ptr);
        // 克隆一个新的Arc,强引用计数+1
        let cloned_arc = Arc::clone(&original_arc);
        // 将原Arc转换回裸指针,交还给C侧持有
        let _ = Arc::into_raw(original_arc);
        // 使用克隆的Arc安全访问数据
        let value = cloned_arc.data;
        value
    }
}

额外注意事项

  1. 空指针检查:所有接收裸指针的FFI函数都必须先检查指针是否为null,避免未定义行为;
  2. 结构体内存布局:如果C侧需要直接访问my_struct的字段,必须给结构体加上#[repr(C)]属性,确保Rust和C的内存布局一致:
    #[repr(C)]
    pub struct my_struct {
        // fields
    }
    
  3. 线程安全:如果对象会在多线程环境中使用,my_struct需要实现Send和Sync trait(自动实现的前提是所有字段都满足Send+Sync);
  4. 错误处理:FFI中应避免panic,建议用错误码替代panic,确保C侧能正确处理异常情况。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.30 18:07:46