如何跨FFI边界传递指针源信息?
嘿,我完全懂你现在的困扰——在Rust和Go、C#这类语言做跨语言调用(FFI)传递复杂数据结构时,你用Box::into_raw把数据装箱成裸指针传出去,再用Box::from_raw回收内存,逻辑上明明能跑通,结果MIRI却跳出“integer-to-pointer cast”和缺失指针源信息的警告,属实头疼。
先给你说清楚MIRI为啥揪着这个不放:指针的“源信息(provenance)”是Rust用来追踪内存块归属、有效性的关键标记,如果你把指针直接转成整数(比如usize)在FFI边界传递,MIRI就会丢失这个标记,它没法确认你后来转回来的指针确实指向的是当初Rust分配的那块合法内存,所以会判定这个操作有潜在的内存不安全风险。
那怎么解决呢?给你几个实用的方案,按稳妥程度排序:
优先直接传递裸指针,别转整数
你可以直接把Rust的*mut Data类型作为FFI函数的返回值和参数,这样指针的源信息能被MIRI追踪到。比如把你的导出函数改成这样:#[no_mangle] pub extern "C" fn create_my_data() -> *mut Data { // 把数据装箱成裸指针,直接返回给其他语言 Box::into_raw(Box::new(Data { /* 这里填你的数据初始化逻辑 */ })) } #[no_mangle] pub extern "C" fn free_my_data(ptr: *mut Data) { // 先判空,避免空指针操作 if !ptr.is_null() { unsafe { // 把裸指针转成Box,drop时自动回收内存 Box::from_raw(ptr); } } }其他语言(比如Go、C#)只需要持有这个裸指针的副本,绝对不要在Rust之外释放它,所有内存回收都通过你提供的
free_my_data函数来做——这样MIRI就不会再跳警告了。如果必须转整数,一定要保留源信息
要是你用的语言对裸指针的FFI支持不太友好,非得把指针转成整数类型传递,那得用Rust标准库的工具保留指针的源信息。从Rust 1.65版本开始,你可以用std::ptr::with_exposed_provenance和std::ptr::from_exposed_provenance_mut这两个函数来处理:#[no_mangle] pub extern "C" fn create_my_data() -> usize { let raw_ptr = Box::into_raw(Box::new(Data {})); // 把指针转成usize,但保留源信息 unsafe { std::ptr::with_exposed_provenance(raw_ptr) as usize } } #[no_mangle] pub extern "C" fn free_my_data(raw_usize: usize) { // 把usize转回裸指针,恢复源信息 let raw_ptr = unsafe { std::ptr::from_exposed_provenance_mut(raw_usize) }; if !raw_ptr.is_null() { unsafe { Box::from_raw(raw_ptr); } } }这个操作相当于告诉MIRI:“这个整数其实是从某个合法指针转来的,你要记得它的源信息哦”,这样就能绕过那个警告,同时也不会引入内存不安全的风险。
最后再提一句:MIRI的警告可不是没事找事,它是帮你提前排查未定义行为的利器,跟着它的提示调整代码,能让你的FFI逻辑更健壮。
备注:内容来源于stack exchange,提问作者Jury

