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