Programming > Java > java > lang > foreign
Nov 18, 2025
Nov 18, 2025
N/A Views
MD

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

Found a mistake? Update the entry.
Share this article: