Rust和JVM一起使用 - itnext
我已經使用 JVM 二十年了,主要是在 Java 中。JVM 是一項了不起的技術。恕我直言,它最大的好處是它能夠使本機程式碼適應當前的工作負載;如果工作負載發生變化並且本機程式碼不是最佳的,它將相應地重新編譯位元組碼。
另一方面,當不再需要物件時,JVM 會自動從記憶體中釋放它們。這個過程被稱為垃圾收集。在沒有 GC 的語言中,開發人員必須負責釋放物件。對於遺留語言和大型程式碼庫,釋出應用不一致,並且在生產中發現了錯誤。
雖然 GC 演算法隨著時間的推移有所改進,但 GC 本身仍然是一個大型複雜機器。微調 GC 很複雜,並且在很大程度上取決於上下文。昨天有效的方法今天可能無效。總而言之,在您的上下文中配置 JVM 以最好地處理 GC 就像魔術一樣。
由於圍繞 JVM 的生態系統非常發達,因此使用 JVM 開發應用程式並將需要可預測性的部分委託給 Rust 是有意義的。
透過 JNI 整合 Java 和 Rust
整合Java和Rust需要以下幾步:
- 在 Java 中建立“skeleton”方法
- 從它們生成 C 標頭檔案
- 在 Rust 中實現它們
- 編譯 Rust 生成系統庫
- 從 Java 程式載入庫
- 呼叫第一步中定義的方法。此時,庫包含實現,並且整合已完成。
老手會意識到這些步驟與您需要與 C 或 C++ 整合時的步驟相同。因為他們也可以生成系統庫。讓我們詳細看看每個步驟。
先需要建立 Java skeleton方法。native方法將其實現委託給庫。
public native int doubleRust(int input); |
接下來,我們需要生成相應的C標頭檔案。為了自動生成,我們可以利用 Maven 編譯器外掛:
<plugin> <artifactId>maven-compiler-plugin</artifactId> <version>3.8.1</version> <configuration> <compilerArgs> <arg>-h</arg> <!--1--> <arg >target/headers</arg> <!--2--> </compilerArgs> </configuration> </plugin> |
上述 Java 片段的生成頭應如下所示:
include ifndef _Included_ch_frankel_blog_rust_Main define _Included_ch_frankel_blog_rust_Main ifdef __cplusplus extern "C" { endif /* * Class: ch_frankel_blog_rust_Main * Method: doubleRust * Signature: (I)I */ JNIEXPORT jint JNICALL Java_ch_frankel_blog_rust_Main_doubleRust (JNIEnv *, jobject, jint); ifdef __cplusplus } endif endif |
Rust 實現
現在,我們可以開始 Rust 實現。讓我們建立一個新專案:
cargo new lib-rust <p class="indent">[package] name = "dummymath" version = "0.1.0" authors = ["Nicolas Frankel "] edition = "2018" <p class="indent">[dependencies] jni = "0.19.0" <p class="indent">[lib] crate_type = ["cdylib"] |
有幾種 crate 型別可用:cdylib用於動態系統庫,您可以從其他語言載入。您可以在文件中檢視所有其他可用型別。
API 一對一地對映到生成的 C 程式碼。我們可以相應地使用它:
#[no_mangle] pub extern "system" fn Java_ch_frankel_blog_rust_Main_doubleRust(_env: JNIEnv, _obj: JObject, x: jint) -> jint { x * 2 } |
在上面的程式碼中發生了很多事情。讓我們詳細說明一下。
- 該no_mangle宏告訴編譯器在編譯後的程式碼中保留相同的函式簽名。這很重要,因為 JVM 將使用此簽名。
- 大多數時候,我們extern在 Rust 函式中使用將實現委託給其他語言:這稱為 FFI。這與我們在 Java 中使用native. 然而,Rust 也extern用於相反的情況,即,使函式可以從其他語言呼叫。
- 簽名本身應該精確地模仿 C 標頭檔案中的程式碼,因此這個名字看起來很有趣
- 最後,x是一個jint,是i32的別名。
現在構建: cargo build |
構建會生成一個系統相關的庫。例如,在 OSX 上,工件有一個dylib副檔名;在 Linux 上,它將有一個so。
使用Java端的庫
最後一部分是在Java端使用生成的庫。它需要首先載入它。有兩種方法可用於此目的,System.load(filename)以及System.loadLibrary(libname)。
load()需要庫的絕對路徑,包括其副檔名,例如, /path/to/lib.so. 對於需要跨系統工作的應用程式,這是不切實際的。loadLibrary()允許您只傳遞庫的名稱 - 沒有副檔名。請注意,庫是在java.library.pathSystem 屬性指示的位置載入的。
public class Main {
static {
System.loadLibrary("dummymath");
}
}
請注意,在 Mac OS 上,lib字首不是庫名稱的一部分。
處理物件
上面的程式碼非常簡單:它涉及一個純函式,根據定義,它僅取決於其輸入引數。假設我們想要更多的東西。我們提出了一種新方法,將引數與物件狀態中的另一個引數相乘:
public class Main { private int state; public Main(int state) { this.state = state; } public static void main(String[] args) { try { var arg1 = Integer.parseInt(args[1]); var arg2 = Integer.parseInt(args[2]); var result = new Main(arg1).timesRust(arg2); // 1 System.out.println(arg1 + "x" + arg2 + " = " + result); } catch (NumberFormatException e) { throw new IllegalArgumentException("Arguments must be ints"); } } public native int timesRust(int input); } |
native方法看起來與上面完全相同,但名稱不同。因此,生成的 C 標頭檔案看起來也一樣。魔法需要發生在 Rust 方面。
在純函式中,我們沒有使用JNIEnv和JObject引數:JObject表示 Java 物件,即,Main和JNIEnv允許訪問其資料(或行為)。
#[no_mangle] pub extern "system" fn Java_ch_frankel_blog_rust_Main_timesRust(env: JNIEnv, obj: JObject, x: jint) -> jint { // 1 let state = env.get_field(obj, "state", "I"); // 2 state.unwrap().i().unwrap() * x // 3 } |
第二行:傳遞物件的引用、Java 中的欄位名稱及其型別。型別是指正確的JVM 型別簽名,例如 "I"代表 int。
第三行:state是一個Result<JValue>。我們需要將它解包到一個 JValue,然後將它“投射”到一個Result<jint>viai()
這篇文章的完整原始碼可以在Github上找到。
相關文章
- 一起學習RustRust
- 使用Rust的幾點理由,加入我們,一起學習!Rust
- Jenkins已經老了 - ITNEXTJenkins
- 使用Docker桌面開發環境功能開發SpringBoot - itnextDocker開發環境Spring Boot
- 一起來梳理JVM知識點JVM
- 使用 Rust、OpenAI 和 Qdrant 構建 Agentic RAGRustOpenAI
- JVM調優之JConsole和JVisualVM工具使用JVMLVM
- 使用 Rust 和 OpenCV 進行物體檢測RustOpenCV
- 使用Rust和WebAssembly構建Web應用程式RustWeb
- API優先方法的完整指南 - ITNEXTAPI
- 文盤rust--使用 Rust 構建RAGRust
- Rust match 和 if letRust
- JVM dump和分析JVM
- [JVM]<clinit>和<init>JVM
- JDK,JRE和JVMJDKJVM
- JDK、JRE和JVMJDKJVM
- 使用Rust和Elixir實現高效的下發好友列表Rust
- 使用Rust的Tauri和Yew建立桌面應用程式 - DEVRustdev
- Rust Cargo使用總結RustCargo
- Cargo(Rust) 使用映象源CargoRust
- VSCode 使用 rust-analyzerVSCodeRust
- rust druid 之 Lens使用RustUI
- IPv6 和 RustRust
- RUST Some None 和OKRustNone
- Rust 所有權和借用Rust
- Go 和 Rust 我都要!GoRust
- 使用Rust的ripunzip和rayon並行解壓縮檔案Rust並行
- JDK.JRE和JVMJDKJVM
- 深入研究自定義Apache Nifi處理器 - itnextApacheNifi
- rust-unofficial/awesome-rust:Rust開原始碼和資源的精選列表。Rust原始碼
- 和 transformjs 一起搖擺ORMJS
- 使用IntelliJ做為Rust IDEIntelliJRustIDE
- Rust中的Copy和CloneRust
- 為什麼Go不再需要Java風格的GC?- itnextGoJavaGC
- JVM筆記 -- Java跨平臺和JVM跨語言JVM筆記Java
- JVM 調優命令&工具使用JVM
- JVM 調優示例和配置JVM
- 2018年的Docker和JVMDockerJVM