Rust中Send闭包调用C回调时*const c_void未实现Send问题解决
问题根因
报错和c_void类型本身无关,核心原因是:Rust 中所有裸指针(*const T/*mut T)无论指向什么类型,默认都不会自动实现Send和Sync trait。
官方文档提到的c_void实现Send/Sync,是指c_void类型值本身的trait约束,和指向它的裸指针没有关系——举个最简单的例子:u8完全满足Send+Sync,但*const u8依然不会自动实现Send,因为裸指针不受Rust借用规则保护,编译器无法自动判断它跨线程传递的安全性,因此统一要求开发者手动做安全担保。
此前尝试用单元结构体包装未生效,核心原因是闭包依然直接捕获了外层的裸指针变量,没有把指针完全收进自定义结构体中,包装逻辑没有实际覆盖被捕获的对象。
解决方案
可以通过新类型包装裸指针、手动标记实现Send的方式解决,前提是必须自行担保:FFI传入的上下文指针确实可以安全跨线程传递,对应的C回调跨线程调用不会触发数据竞争、不会访问线程局部的无效内存。
具体实现代码如下:
use std::ffi::c_void; // 包装FFI传入的上下文裸指针 struct FfiContext(*const c_void); // SAFETY: 需调用方保证上下文指针可安全跨线程转移、回调线程安全 unsafe impl Send for FfiContext {} // 如果闭包需要跨线程不可变共享调用,还要同步实现Sync unsafe impl Sync for FfiContext {} // 创建闭包时,先把指针包装进结构体再move进闭包 let ctx = FfiContext(obj); let rust_closure = move |data: u32| { // 调用时取出内部指针传给C回调即可 callback(ctx.0, data); };
如果不想单独定义结构体,也可以用更轻量的写法:把裸指针转成usize类型存入闭包,usize本身已经实现Send,调用时再转回指针即可,本质和新类型包装的安全要求一致:
let ctx_ptr = obj as usize; let rust_closure = move |data: u32| { let ctx = ctx_ptr as *const c_void; callback(ctx, data); };
注意事项
- 不要为了消除编译错误随便给不满足线程安全要求的指针实现
Send,否则会触发跨线程数据竞争、悬垂指针等未定义行为,这个安全约束必须由FFI调用方根据C侧的实际逻辑确认。 - 如果C回调要求上下文只能在创建线程调用,那不能给指针实现
Send,这种场景应该用线程本地存储或者对应运行时的线程绑定能力包裹闭包,不能直接转成带Send约束的回调对象。
内容的提问来源于stack exchange,提问作者Tim
相关产品推荐
相关产品推荐

