#java #interop #bridge #extern #java-class #file-path

bin+lib rs4j

A small, automatic, efficient, and easy-to-use Rust to Java bridge

8 unstable releases (3 breaking)

0.4.1 Feb 9, 2024
0.4.0 Feb 9, 2024
0.3.1 Feb 9, 2024
0.2.1 Feb 9, 2024
0.1.1 Feb 9, 2024

#72 in FFI

MIT license

38KB
965 lines

rs4j

Crates.io Version docs.rs

A small, automatic, efficient, and easy-to-use Rust to Java bridge.

Usage

rs4j works by using a custom language syntax to translate into Rust and Java code.

A good way to use this is to make a build script and have it auto-compile your code.

You can add the package to your build script by running:

cargo add rs4j --build

Here's an example build.rs:

use rs4j::build::BindgenConfig;
use anyhow::Result;

fn main() -> Result<()> {
    // Make a new config
    BindgenConfig::new()

        // Set the package for export
        .package("your.package.here")

        // Where to save the Rust bindings
        .bindings(format!("{}/src/bindings.rs", env!("CARGO_MANIFEST_DIR")))

        // Where the input files are
        .glob(format!("{}/bindings/**/*.rs4j", env!("CARGO_MANIFEST_DIR")))?

        // Where to save java classes (is a directory)
        .output(format!("{}/java", env!("CARGO_MANIFEST_DIR")))

        // Enable JetBrains annotations
        .annotations(true)

        // Go!
        .generate()?;

    Ok(())
}

Then, once that's done, use your lib.rs (or other file) to include the generated bindings:

// Put any imports that are needed for the bindings here.
use path::to::Dependency;

// You can even define structs in the file, just before the bindings!

// Include the generated code.
include!("bindings.rs");

Syntax

The syntax of the binding language is fairly simple. You start by defining a class, then filling out its methods and items.

The basic syntax run-down is:

// This class, Thing, takes in one type parameter, `A`.
// You can omit this if it doesn't take any type parameters.
class Thing<A> {
    // This makes it so that Rust knows that the type for `A`
    // will have `Clone + Copy`. This doesn't change anything
    // on the Java side, it's just so that Rust will compile.
    bound A: Clone + Copy;

    // Here, the Rust function's name is `new`, but in Java that's
    // illegal as it is a reserved keyword. To combat this, you can
    // specify a different name for the function in Java and the real
    // one in Rust.
    [new] static fn of(value: A) -> Thing;

    // This gets the value. Since this is in snake_case, rs4j will
    // automatically convert it into camelCase, renaming this to
    // `get_value` on the Java side.
    fn get_value() -> A;

    // This marks this function as mutable, meaning in Rust it will
    // mutate the struct, as if it took a `&mut self` as an argument.
    mut fn set_value(value: A);

    // You can even include trait methods, as long as Rust can find the
    // trait it belongs to!
    fn clone() -> A;
};

Support

The following primitive* types are supported:

Rust Java
String String
str String
bool Boolean
u8 Byte
u16 Short
u32 Integer
u64 Long
u128 BigInteger
i8 Byte
i16 Short
i32 Integer
i64 Long
i128 BigInteger
f32 Float
f64 Double
() Void

* Not all are primitive, but I consider them to be.

Dependencies

~3–15MB
~130K SLoC