结论:FFI 边界天然是 unsafe,核心规则四条:1)数据布局用 #[repr(C)] 保证与 C 一致;2)跨越边界的所有权必须清晰——谁分配谁释放,Rust 的 Box 不能交给 C 的 free;3)所有传入的指针都要校验(非空、对齐、生命周期覆盖整个调用);4)panic 绝不能跨 FFI 边界,用 catch_unwind 兜住并转成错误码。

展开:常见翻车点逐条对应:字符串要转 CString(含 NUL 结尾),返回时 CStr::from_ptr 借用而非接管;回调里拿到裸指针用 Box::from_raw 接管所有权前必须确认对方不再使用,且与 Box::into_raw 严格配对;枚举默认布局与 C enum 不兼容,需 #[repr(C)]#[repr(u8)];结构体里的 bool 与 C 的 _Bool 大小对齐陷阱要用 u8 显式化。工具侧:bindgen 从 C 头生成绑定避免手写错误,cbindgen 反向生成头文件;用 Miri(有限支持)和 ASan 交叉验证。易错点:extern "C" 函数签名写错类型不会编译报错(链接时只看符号名),错了就是 UB。

#[repr(C)]
pub struct Point { x: f64, y: f64 }

#[no_mangle]
pub extern "C" fn point_norm(p: *const Point) -> f64 {
    let p = match unsafe { p.as_ref() } {
        Some(p) => p,
        None => return f64::NAN, // 空指针防御
    };
    (p.x * p.x + p.y * p.y).sqrt()
}

追问方向:所有权跨边界的三种移交模式(借用/移交/复制)、unwinding 与 extern "C-unwind" 的区别。