---
title: Foreign Function & Memory (FFM) APIでRustの関数をJavaで呼び出すメモ
tags: ["Java", "FFM", "Rust"]
categories: ["Programming", "Java", "java", "lang", "foreign"]
date: 2025-11-18T03:51:15Z
updated: 2025-11-18T04:00:41Z
---
Java 22で正式版となったForeign Function & Memory(FFM)APIをJava 25で使用して、Rustで実装した関数をJavaから呼び出すサンプルを試しました。
FFM APIはJNIを使わずにネイティブライブラリを呼び出すための新しいAPIです。
シンプルな題材として[竹内関数](https://ja.wikipedia.org/wiki/%E7%AB%B9%E5%86%85%E9%96%A2%E6%95%B0)をRustで実装することにしました。
**目次**
### 検証環境
以下の環境で動作確認を行いました:
```bash
$ 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`:
```toml
[package]
name = "tak"
version = "0.1.0"
edition = "2021"
[lib]
crate-type = ["cdylib"]
```
`rust/src/lib.rs`:
```rust
#[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ライブラリのビルド:
```bash
cd rust
cargo test
cargo build --release
cd -
```
### JavaでのFFM API実装
`pom.xml`:
```xml
4.0.0
com.example
java-rust-ffm
1.0.0-SNAPSHOT
25
25
UTF-8
org.junit.jupiter
junit-jupiter
6.0.1
test
org.assertj
assertj-core
3.27.6
test
org.apache.maven.plugins
maven-compiler-plugin
3.14.1
org.apache.maven.plugins
maven-surefire-plugin
3.5.4
-Djava.library.path=${project.basedir}/rust/target/release
io.spring.javaformat
spring-javaformat-maven-plugin
0.0.47
validate
true
validate
```
FFM APIを使用してRustライブラリを呼び出すJavaクラスを作成します:
`src/main/java/com/example/ffm/TakeuchiFunction.java`:
```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`:
```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`:
```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 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`:
```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");
}
}
```
### プロジェクトのビルド
```bash
./mvnw clean compile
```
テスト実行
```bash
./mvnw test
```
### アプリケーション実行
Rust FFM実装の実行:
```bash
java -cp target/classes -Djava.library.path=rust/target/release com.example.ffm.Main
```
Java実装の実行(比較用):
```bash
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実装:
```bash
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実装:
```bash
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 |
### クリーンアップ
```bash
./mvnw clean
cd rust && cargo clean && cd -
```
---
FFM APIを使用することで、JNIを使わずに簡潔にネイティブライブラリを呼び出すことができました。
ケースバイケースではありますが、今回はFFM API経由でRustライブラリを実行した方がパフォーマンスが良好でした。
検証に使ったソースコードは https://github.com/making/java-rust-ffm です。