• Develop application logic in Rust – Develop e.g. UI in Gtk/C or Qt/C++ • Incrementally modernize a codebase • Priority queue (aka std::collections::BinaryHeap) – Put elements out of order – Pop elements in sorted order • Build a foreign function interface (FFI) module
guarantees do not hold across FFI boundaries • Rust mangles function names – #[no_mangle] • Rust does not follow the C (or platform) calling convention – extern “C” or extern “system” • Some Rust types are not “ffi-safe” – Slices & tagged enums don’t have a C equivalent • Structs & enums with the Rust repr don’t have specified layouts – #[repr(C)]
the header file at build time • Based on the provided Rust FFI # Cargo.toml … [lib] crate-type = ["staticlib", "cdylib"] … [build-dependencies] cbindgen = "0.23" + build.rs
from Rust code using the cxx crate • Without maintaining C++ wrappers around a C API • Supports integration with build systems like CMake # Cargo.toml … [lib] crate-type = ["staticlib", "cdylib"] … [dependencies] cxx = "1.0" … [build-dependencies] cxx-build = "1.0" … + build.rs
mut pqueue = PQueueU8(std::collections::BinaryHeap::new()); for &element in elements { pqueue.0.push(element); } Box::new(pqueue) } impl PQueueU8 { fn pop(&mut self) -> Result<u8, &'static str> { self.0.pop().ok_or("queue is empty") } } C++ bindings using cxx – Implementation The error type
build $ ls target/cxxbridge/pqueue/src lib.rs.cc lib.rs.h $ c++ -Wall -Werror target/release/libpqueue.a -o pqueue_test $ valgrind ./pqueue_test … 5 4 2 2 1 pop error: queue is empty pop error: queue is empty pop error: queue is empty pop error: queue is empty pop error: queue is empty … ==16054== ==16054== HEAP SUMMARY: ==16054== in use at exit: 0 bytes in 0 blocks ==16054== total heap usage: 20 allocs, 20 frees, 74,596 bytes allocated ==16054== ==16054== All heap blocks were freed -- no leaks are possible ==16054== ==16054== For lists of detected and suppressed errors, rerun with: -s ==16054== ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 0 from 0)
- RFC 2603 – Documentation on extern • Types that can and cannot be shared – Documentation on type layouts • Use high-level Rust constructs to represent lower-level C constructs – Option<&T> and Option<Box<T: Sized>> vs. raw pointers – Nomicon on FFI • Automate – cbindgen – cxx
not copy the array memory! BUT might block Java GC until the AutoPrimitiveArray is destroyed https://www.ibm.com/docs/en/sdk-java-technology/8?topic=jni-copying-pinning
jlong) -> jbyte { let pq = unsafe { &mut *(native_ptr as *mut PQueueI8) }; if let Some(val) = pq.0.pop() { return val; } else { let ex_class = env.find_class( "java/lang/ArrayIndexOutOfBoundsException"); env.throw_new(ex_class, "Queue is empty."); // Won't be consumed on the Java side because of the exception above. return i8::MIN; } } Again insert NULL check (native_ptr == 0) before!
allocation’s sandbox. Spatial: A range of bytes that the pointer is allowed to access. Temporal: The lifetime (of the allocation) that access to these bytes is tied to. https://doc.rust-lang.org/nightly/core/ptr/index.html#provenance
The unsafe Rust person. Pointers Are Complicated III, or: Pointer-integer casts exposed: https://www.ralfj.de/blog/2022/04/11/provenance-exposed.html by Ralf Jung
or pinning • Pinning is faster, but might block GC • Memory “leaked” to Java needs to be cleaned up • Freeing resources from finalize() is not guaranteed to work
but slow (interesting if speed is not important) • Java Native Runtime (JNR-FFI) – Comparable performance to JNI without the hand-written glue code • Project Panama (still experimental)