Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Unsafe Rust and FFI
Unsafe Rust 与 FFI

What you’ll learn: What unsafe actually means in Rust, when Java teams typically need it, and how JNI, JNA, or Panama map onto Rust FFI.
本章将学习: Rust 里的 unsafe 到底意味着什么、Java 团队通常会在什么场景遇到它,以及 JNI、JNA、Panama 与 Rust FFI 的对应关系。

Difficulty: 🔴 Advanced
难度: 🔴 高级

unsafe does not turn Rust into chaos mode. It marks code where the compiler can no longer verify every safety invariant. The job of the programmer becomes narrower and more explicit: document the invariant, confine the dangerous operation, and expose a safe API whenever possible.
unsafe 不是把 Rust 一键切成失控模式。它只是标记出“编译器已经无法替代开发者验证全部安全约束”的代码区块。此时开发者的职责会变得更窄、更明确:写清约束、把危险操作关进小范围、尽可能向外暴露安全 API。

When Java Developers Usually Meet unsafe
Java 开发者通常会在什么时候碰到 unsafe

  • wrapping a C library for use inside Rust
    给 C 库写 Rust 包装层。
  • exporting a Rust library so Java can call it
    把 Rust 库导出给 Java 调用。
  • working with raw buffers, shared memory, or kernel interfaces
    处理原始缓冲区、共享内存或内核接口。
  • implementing performance-sensitive data structures that cannot be expressed in fully safe code
    实现一些性能敏感、无法完全用安全 Rust 表达的数据结构。

What unsafe Allows
unsafe 允许做什么

  • dereferencing raw pointers
    解引用裸指针。
  • calling unsafe functions
    调用 unsafe 函数。
  • accessing mutable statics
    访问可变静态变量。
  • implementing unsafe traits
    实现 unsafe trait。

Most real projects should keep unsafe in a tiny number of modules.
真实项目通常都应该把 unsafe 收敛在极少数模块里。

FFI Boundary: Java and Rust
FFI 边界:Java 与 Rust

The cleanest mental model is:
最容易记的心智模型是下面这张表:

Java side
Java 侧
Rust side
Rust 侧
JNI, JNA, or Panama bindingextern "C" functions
ByteBuffer or native memory segment
ByteBuffer 或原生内存段
raw pointer or slice
裸指针或切片
Java object lifetime
Java 对象生命周期
explicit Rust ownership rules
显式 Rust 所有权规则
exception and null conventions
异常与空值约定
explicit return value or error code
显式返回值或错误码

Minimal Rust Export
最小 Rust 导出示例

#![allow(unused)]
fn main() {
#[no_mangle]
pub extern "C" fn add_numbers(a: i32, b: i32) -> i32 {
    a + b
}
}

That symbol can then be called through a native interface on the Java side.
这样导出的符号就可以通过 Java 侧的原生互操作层调用。

Practical FFI Rules
FFI 实战规则

  1. Use a stable ABI such as extern "C".
    使用稳定 ABI,比如 extern "C"
  2. Do not let panics cross the FFI boundary.
    不要让 panic 穿过 FFI 边界。
  3. Prefer plain integers, floats, pointers, and opaque handles at the boundary.
    边界上优先使用整数、浮点、指针和 opaque handle 这类朴素类型。
  4. Convert strings and collections at the edge instead of trying to share high-level representations.
    字符串和集合在边界处转换,别试图共享两边各自的高级表示。
  5. Free memory on the same side that allocated it.
    谁分配内存,最好就由谁释放。

Opaque Handle Pattern
opaque handle 模式

#![allow(unused)]
fn main() {
pub struct Engine {
    counter: u64,
}

#[no_mangle]
pub extern "C" fn engine_new() -> *mut Engine {
    Box::into_raw(Box::new(Engine { counter: 0 }))
}

#[no_mangle]
pub extern "C" fn engine_increment(ptr: *mut Engine) -> u64 {
    let engine = unsafe { ptr.as_mut() }.expect("null engine pointer");
    engine.counter += 1;
    engine.counter
}

#[no_mangle]
pub extern "C" fn engine_free(ptr: *mut Engine) {
    if !ptr.is_null() {
        unsafe { drop(Box::from_raw(ptr)); }
    }
}
}

This pattern is far easier to reason about than trying to expose Rust structs field-by-field to Java code.
和把 Rust 结构体字段逐个暴露给 Java 相比,这种 opaque handle 模式要好理解得多,也更稳妥。

JNI, JNA, or Panama?
JNI、JNA、Panama 怎么选

  • JNI offers full control, but the API is verbose.
    JNI 控制力最强,但 API 很啰嗦。
  • JNA is easier for quick integration, but adds overhead.
    JNA 集成更快,但会带来额外开销。
  • Panama is the long-term modern direction for native interop on newer JDKs.
    Panama 则是较新 JDK 上更现代、也更值得关注的长期方向。

The Rust side stays mostly the same in all three cases. The biggest difference is how the Java layer loads symbols and marshals data.
对 Rust 侧来说,这三种方案的大体写法差不多。真正差异主要落在 Java 侧如何装载符号和封送数据。

Advice
建议

  • Write the safe Rust API first.
    先把安全 Rust API 设计好。
  • Add the FFI layer second.
    再加 FFI 包装层。
  • Audit every pointer assumption.
    把每一条指针假设都审一遍。
  • Keep the boundary narrow and boring.
    让边界尽量窄、尽量朴素。

That discipline is what turns unsafe from a liability into an implementation detail.
真正能把 unsafe 从负担变成实现细节的,靠的就是这套纪律。