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 } }
额外注意事项
- 空指针检查:所有接收裸指针的FFI函数都必须先检查指针是否为null,避免未定义行为;
- 结构体内存布局:如果C侧需要直接访问
my_struct的字段,必须给结构体加上#[repr(C)]属性,确保Rust和C的内存布局一致:#[repr(C)] pub struct my_struct { // fields } - 线程安全:如果对象会在多线程环境中使用,
my_struct需要实现Send和Synctrait(自动实现的前提是所有字段都满足Send+Sync); - 错误处理:FFI中应避免panic,建议用错误码替代panic,确保C侧能正确处理异常情况。
内容的提问来源于stack exchange,提问作者user578943
相关产品推荐
相关产品推荐

