---
title: Foreign Function & Memory (FFM) APIでRustの関数をJavaで呼び出すメモ
summary: この記事では、Java 25の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で実装することにしました。

**目次**
<!-- toc -->

### 検証環境

以下の環境で動作確認を行いました：

```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
<?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`:

```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<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`:

```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 です。
