Monday, July 11, 2022

Java's Project Panama and Rust - Simple example

There are a couple good guides to getting started with Foreign Function Interfaces (FFI) in Java 19, but since it's a feature preview, everything is subject to change and the ones I found were already out of date. 

 Here are my notes for getting a trivial Java program to call a Rust library:
  •   Install Java 19 Early Access (recommended: sdk install java 19.ea.29-open)
  •  Install jextract (it's not part of openJDK, instead look here: https://github.com/openjdk/jextract)
  •  Create the Rust program (TODO details, cargo init --lib) 
Cargo.toml
[package]
name ="myrustlibrary"
version = "0.1.0"
edition = "2021"

[dependencies]

[lib]
crate_type = ["cdylib"]

[build-dependencies]
cbindgen = "0.20.0"

Add a build.rs file, we want to use cbindgen to create the lib.h file we'll use with jextract.
extern crate cbindgen;

use std::env;

fn main() {
    let crate_dir = env::var("CARGO_MANIFEST_DIR").unwrap();

    cbindgen::Builder::new()
        .with_crate(crate_dir)
        .with_language(cbindgen::Language::C)
        .generate()
        .expect("Unable to generate bindings")
        .write_to_file("lib.h");
}

Add the src/lib.rs contents, for simplicity we'll just echo the PID

use std::process;

#[no_mangle]
pub extern "C" fn rust_get_pid() -> u32 {
    return process::id();
}

Now build it: cargo build

Important, keep track of where Cargo built your lib. 'lib.h' will be in the base folder, and the lib itself will be in the 'target/debug' folder (libmyrustlibrary.d libmyrustlibrary.dylib if you're on a Mac)

Run jextract on the lib.h file

  ./jextract  -t org.rust -l myrustlibrary --output classes ./lib.h
  

Now there will be a bunch of class files in the classes/org/rust dir

Write a Java program to make use of the header file we created from rust (lib.h)

import static org.rust.lib_h.*;	// notice this is the target package we specified when running jextract

public class Main {
  public static void main(String[] args){
    System.out.println("🦀 process id = " + rust_get_pid());
  }
}

And finally, tie it all together

  ./java --enable-preview --source 19 --enable-native-access=ALL-UNNAMED  -Djava.library.path=./target/debug -cp classes Main.java

🦀 process id = 5526