inline_java 0.1.0

Embed Java directly in Rust — evaluated at program runtime or at compile time
Documentation

inline java

Embed Java directly in Rust — evaluated at program runtime (java!, java_fn!) or at compile time (ct_java!).

Prerequisites

Java 8+ with javac and java on PATH.

Quick start

# Cargo.toml
[dependencies]
inline_java = "0.1.0"

java! — runtime, no parameters

Compiles and runs Java each time the surrounding Rust code executes. Expands to Result<T, inline_java::JavaError>.

use inline_java::java;

// No type annotation needed — the macro infers `i32` from `static int run()`
let x = java! {
    static int run() {
        return 42;
    }
}.unwrap();

java_fn! — runtime, with parameters

Like java!, but run(...) may declare parameters. Expands to a Rust function value fn(P1, P2, …) -> Result<T, JavaError>. Parameters are serialised by Rust and piped to the Java process over stdin.

use inline_java::java_fn;

// Single parameter — return type inferred from `static int run()`
let doubled = java_fn! {
    static int run(int n) {
        return n * 2;
    }
}(21).unwrap();

// Multiple parameters
let msg: String = java_fn! {
    static String run(String greeting, String target) {
        return greeting + ", " + target + "!";
    }
}("Hello", "World").unwrap();

// Optional parameter
let result: Option<i32> = java_fn! {
    import java.util.Optional;
    static Optional<Integer> run(Optional<Integer> val) {
        return val.map(x -> x * 2);
    }
}(Some(21)).unwrap();

ct_java! — compile time

Runs Java during rustc macro expansion and splices the result as a Rust literal at the call site. No parameters are allowed (values must be compile-time constants).

use inline_java::ct_java;

const PI: f64 = ct_java! {
    static double run() {
        return Math.PI;
    }
};

// Arrays work too — result is a Rust array literal baked into the binary
const PRIMES: [i32; 5] = ct_java! {
    static int[] run() {
        return new int[]{2, 3, 5, 7, 11};
    }
};

Supported parameter types (java_fn!)

Declare parameters in the Java run(...) signature; Rust receives them with the mapped types below.

Java parameter type Rust parameter type
byte i8
short i16
int i32
long i64
float f32
double f64
boolean bool
char char
String &str
T[] / List<BoxedT> &[T]
Optional<BoxedT> Option<T>

Supported return types

Java return type Rust return type
byte i8
short i16
int i32
long i64
float f32
double f64
boolean bool
char char
String String
T[] / List<BoxedT> Vec<T>
Optional<BoxedT> Option<T>

Types can be nested arbitrarily: Optional<List<Integer>>Option<Vec<i32>>, List<String[]>Vec<Vec<String>>, etc.

Options

The following optional key = "value" pairs may appear before the Java body, separated by commas:

  • javac = "<args>" — extra arguments for javac (shell-quoted).
  • java = "<args>" — extra arguments for java (shell-quoted).
use inline_java::java;

let result: String = java! {
    javac = "-cp ./my.jar",
    java  = "-cp ./my.jar",
    import com.example.MyClass;
    static String run() {
        return new MyClass().greet();
    }
}.unwrap();

Cache directory

Compiled .class files are cached so that unchanged Java code is not recompiled on every run. The cache root is resolved in this order:

Priority Location
1 INLINE_JAVA_CACHE_DIR environment variable (if set and non-empty)
2 Platform cache directory — ~/.cache/inline_java on Linux, ~/Library/Caches/inline_java on macOS, %LOCALAPPDATA%\inline_java on Windows
3 <system temp>/inline_java (fallback if the platform cache dir is unavailable)

Each compiled class gets its own subdirectory named <ClassName>_<hash>/, where the hash covers the Java source, the expanded javac flags, the current working directory, and the raw java flags. This means changing any of those inputs automatically triggers a fresh compilation.

Using project Java source files

Use import or package directives together with javac = "-sourcepath <path>" (or -classpath) to call into your own Java code:

use inline_java::java;

// import style
let s: String = java! {
    javac = "-sourcepath .",
    import com.example.demo.*;
    static String run() {
        return new HelloWorld().greet();
    }
}.unwrap();

// package style — the generated class becomes part of the named package
let s: String = java! {
    javac = "-sourcepath .",
    package com.example.demo;
    static String run() {
        return new HelloWorld().greet();
    }
}.unwrap();

Refactoring use case

inline_java is particularly well-suited for incremental Java → Rust migrations. The typical workflow is:

  1. Keep the original Java logic intact.
  2. Write the replacement in Rust.
  3. Use java_fn! to call the original Java with the same inputs and assert that both implementations produce identical outputs.
use inline_java::java_fn;

fn my_rust_impl(n: i32) -> i32 {
    // … new Rust code …
    n * 2
}

fn parity_with_java() {
    let java_impl = java_fn! {
        static int run(int n) {
            // original Java logic, verbatim
            return n * 2;
        }
    };

    for n in [0, 1, -1, 42, i32::MAX / 2] {
        let expected = java_impl(n).unwrap();
        assert_eq!(my_rust_impl(n), expected, "diverged for n={n}");
    }
}

parity_with_java();

Crate layout

Crate Purpose
inline_java Public API — re-exports macros and core types
inline_java_macros Proc-macro implementation (java!, java_fn!, ct_java!)
inline_java_core Runtime helpers (run_java, JavaError)
inline_java_demo Demo binary