Java 22で正式版となったForeign Function & Memory(FFM)APIをJava 25で使用して、Rustで実装した関数をJavaから呼び出すサンプルを試しました。
FFM APIはJNIを使わずにネイティブライブラリを呼び出すための新しいAPIです。
シンプルな題材として竹内関数をRustで実装することにしました。
目次
検証環境
以下の環境で動作確認を行いました:
$ java -version
openjdk version "25.0.1" 2025-10-21
OpenJDK Runtime Environment GraalVM CE 25.0.1+8.1 (build 25.0.1+8-jvmci-b01)
OpenJDK 64-Bit Server VM GraalVM CE 25.0.1+8.1 (build 25.0.1+8-jvmci-b01, mixed mode, sharing)
$ cargo version
cargo 1.89.0 (Homebrew)
プロジェクト構成
今回のサンプルプロジェクトの構成は以下の通りです:
.
├── rust/
│ ├── Cargo.toml
│ └── src/
│ └── lib.rs
├── src/
│ ├── main/java/com/example/ffm/
│ │ ├── Main.java
│ │ ├── TakeuchiFunction.java
│ │ └── TakeuchiFunctionJ.java
│ └── test/
│ └── TakeuchiFunctionTest.java
└── pom.xml
Rustライブラリの実装
まず、Rustで竹内関数を実装します。竹内関数は計算量の多い再帰関数として知られ、パフォーマンス比較の題材として適しています。
rust/Cargo.toml:
[package]
name = "tak"
version = "0.1.0"
edition = "2021"
[lib]
crate-type = ["cdylib"]
rust/src/lib.rs:
#[no_mangle]
pub extern "C" fn tak(x: i32, y: i32, z: i32) -> i32 {
if y < x {
tak(tak(x - 1, y, z), tak(y - 1, z, x), tak(z - 1, x, y))
} else {
y
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_tak_base_case() {
// When y >= x, should return y
assert_eq!(tak(5, 10, 0), 10);
assert_eq!(tak(5, 5, 3), 5);
assert_eq!(tak(0, 10, 20), 10);
}
#[test]
fn test_tak_recursive_case() {
// Classic test cases for Takeuchi function
assert_eq!(tak(6, 2, 1), 6);
assert_eq!(tak(10, 5, 0), 10);
assert_eq!(tak(12, 6, 0), 12);
}
}
Rustライブラリのビルド:
cd rust
cargo test
cargo build --release
cd -
JavaでのFFM API実装
pom.xml:
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.example</groupId>
<artifactId>java-rust-ffm</artifactId>
<version>1.0.0-SNAPSHOT</version>
<properties>
<maven.compiler.source>25</maven.compiler.source>
<maven.compiler.target>25</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<dependencies>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter</artifactId>
<version>6.0.1</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.assertj</groupId>
<artifactId>assertj-core</artifactId>
<version>3.27.6</version>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.14.1</version>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<version>3.5.4</version>
<configuration>
<argLine>-Djava.library.path=${project.basedir}/rust/target/release</argLine>
</configuration>
</plugin>
<plugin>
<groupId>io.spring.javaformat</groupId>
<artifactId>spring-javaformat-maven-plugin</artifactId>
<version>0.0.47</version>
<executions>
<execution>
<phase>validate</phase>
<inherited>true</inherited>
<goals>
<goal>validate</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>
FFM APIを使用してRustライブラリを呼び出すJavaクラスを作成します:
src/main/java/com/example/ffm/TakeuchiFunction.java:
package com.example.ffm;
import java.lang.foreign.*;
import java.lang.invoke.MethodHandle;
import java.nio.file.Path;
public class TakeuchiFunction {
private static final SymbolLookup LIBRARY_LOOKUP;
private static final MethodHandle TAK_HANDLE;
static {
try {
// Load the Rust library
System.loadLibrary("tak");
LIBRARY_LOOKUP = SymbolLookup.loaderLookup();
// Create function descriptor for tak(int, int, int) -> int
FunctionDescriptor takDescriptor = FunctionDescriptor.of(ValueLayout.JAVA_INT, // return
// type
ValueLayout.JAVA_INT, // x parameter
ValueLayout.JAVA_INT, // y parameter
ValueLayout.JAVA_INT // z parameter
);
// Find the tak function
TAK_HANDLE = LIBRARY_LOOKUP.find("tak")
.map(symbol -> Linker.nativeLinker().downcallHandle(symbol, takDescriptor))
.orElseThrow(() -> new RuntimeException("Failed to find tak function"));
}
catch (Exception e) {
throw new RuntimeException("Failed to load native library", e);
}
}
public static int tak(int x, int y, int z) {
try {
return (int) TAK_HANDLE.invokeExact(x, y, z);
}
catch (Throwable t) {
throw new RuntimeException("Failed to invoke tak function", t);
}
}
}
パフォーマンス比較のため、同じ竹内関数をJavaでも実装:
src/main/java/com/example/ffm/TakeuchiFunctionJ.java:
package com.example.ffm;
public class TakeuchiFunctionJ {
public static int tak(int x, int y, int z) {
if (y < x) {
return tak(tak(x - 1, y, z), tak(y - 1, z, x), tak(z - 1, x, y));
}
else {
return y;
}
}
}
テストクラス
src/test/java/com/example/ffm/TakeuchiFunctionTest.java:
package com.example.ffm;
import java.util.stream.Stream;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.MethodSource;
import static org.assertj.core.api.Assertions.assertThat;
class TakeuchiFunctionTest {
@FunctionalInterface
interface TakFunction {
int tak(int x, int y, int z);
}
static Stream<TakFunction> takFunctionProvider() {
return Stream.of(TakeuchiFunction::tak, TakeuchiFunctionJ::tak);
}
@ParameterizedTest
@MethodSource("takFunctionProvider")
void testBaseCases(TakFunction takFunction) {
// When y >= x, should return y
assertThat(takFunction.tak(5, 10, 0)).isEqualTo(10);
assertThat(takFunction.tak(5, 5, 3)).isEqualTo(5);
assertThat(takFunction.tak(0, 10, 20)).isEqualTo(10);
}
@ParameterizedTest
@MethodSource("takFunctionProvider")
void testRecursiveCases(TakFunction takFunction) {
// Classic test cases for Takeuchi function
assertThat(takFunction.tak(6, 2, 1)).isEqualTo(6);
assertThat(takFunction.tak(10, 5, 0)).isEqualTo(10);
assertThat(takFunction.tak(12, 6, 0)).isEqualTo(12);
}
}
動作確認用のメインクラス
src/main/java/com/example/ffm/Main.java:
package com.example.ffm;
import java.util.Scanner;
public class Main {
public static void main(String[] args) {
// Check for options
boolean useJavaImpl = false;
boolean doWarmup = false;
for (String arg : args) {
if ("--java".equals(arg)) {
useJavaImpl = true;
}
if ("--warmup".equals(arg)) {
doWarmup = true;
}
}
// Perform warmup if requested
if (doWarmup) {
performWarmup(useJavaImpl);
}
Scanner scanner = new Scanner(System.in);
System.out.println("Takeuchi Function Calculator");
System.out.println("Implementation: " + (useJavaImpl ? "Java" : "Rust (FFM)"));
System.out.println("Enter 'quit' or 'q' to exit");
while (true) {
System.out.print("\nEnter x y z (space separated): ");
String input = scanner.nextLine().trim();
if (input.equalsIgnoreCase("quit") || input.equalsIgnoreCase("q")) {
System.out.println("Goodbye!");
break;
}
String[] parts = input.split("\\s+");
if (parts.length != 3) {
System.out.println("Error: Please enter exactly 3 integers");
continue;
}
try {
int x = Integer.parseInt(parts[0]);
int y = Integer.parseInt(parts[1]);
int z = Integer.parseInt(parts[2]);
long startTime = System.currentTimeMillis();
int result = useJavaImpl ? TakeuchiFunctionJ.tak(x, y, z) : TakeuchiFunction.tak(x, y, z);
long endTime = System.currentTimeMillis();
System.out.println("tak(" + x + ", " + y + ", " + z + ") = " + result);
System.out.println("Time: " + (endTime - startTime) + " ms");
} catch (NumberFormatException e) {
System.out.println("Error: Please enter valid integers");
} catch (Exception e) {
System.out.println("Error: " + e.getMessage());
}
}
scanner.close();
}
private static void performWarmup(boolean useJavaImpl) {
System.out.println("Warming up implementation...");
if (useJavaImpl) {
System.out.print("Java warmup: ");
for (int i = 0; i < 50; i++) {
TakeuchiFunctionJ.tak(12, 6, 0);
if ((i + 1) % 10 == 0)
System.out.print(".");
}
System.out.println(" done");
} else {
System.out.print("Rust warmup: ");
for (int i = 0; i < 50; i++) {
TakeuchiFunction.tak(12, 6, 0);
if ((i + 1) % 10 == 0)
System.out.print(".");
}
System.out.println(" done");
}
System.out.println("Warmup completed!\n");
}
}
プロジェクトのビルド
./mvnw clean compile
テスト実行
./mvnw test
アプリケーション実行
Rust FFM実装の実行:
java -cp target/classes -Djava.library.path=rust/target/release com.example.ffm.Main
Java実装の実行(比較用):
java -cp target/classes com.example.ffm.Main --java
実行結果例
Takeuchi Function Calculator
Implementation: Rust (FFM)
Enter 'quit' or 'q' to exit
Enter x y z (space separated): 12 6 0
tak(12, 6, 0) = 12
Time: 50 ms
Enter x y z (space separated): 10 5 0
tak(10, 5, 0) = 10
Time: 1 ms
パフォーマンス比較
tak(12, 6, 0)、tak(14, 7, 0)、tak(15, 5, 0)、tak(15, 7, 0)の実行をRust FFM実装とJava実装で比較してみました。
Rust FFM実装:
echo -e "12 6 0\n14 7 0\n15 5 0\n15 7 0\nq" | java -cp target/classes -Djava.library.path=rust/target/release com.example.ffm.Main --warmup
Java実装:
echo -e "12 6 0\n14 7 0\n15 5 0\n15 7 0\nq" | java -cp target/classes com.example.ffm.Main --java --warmup
結果は次の通りでした。FFMの方がオーバーヘッドが大きくて遅くなるかなと予想していましたが、今回の竹内関数の例ではどのケースでもRust FFM実装の方が約1.4倍高速でした。
| Test Case | Rust FFM (ms) | Java (ms) | Rust優位率 | 差分 (ms) |
|---|---|---|---|---|
tak(12, 6, 0) |
7 | 10 | 1.43x | -3 |
tak(14, 7, 0) |
349 | 496 | 1.42x | -147 |
tak(15, 5, 0) |
1,710 | 2,486 | 1.45x | -776 |
tak(15, 7, 0) |
2,413 | 3,403 | 1.41x | -990 |
クリーンアップ
./mvnw clean
cd rust && cargo clean && cd -
FFM APIを使用することで、JNIを使わずに簡潔にネイティブライブラリを呼び出すことができました。
ケースバイケースではありますが、今回はFFM API経由でRustライブラリを実行した方がパフォーマンスが良好でした。
検証に使ったソースコードは https://github.com/making/java-rust-ffm です。