Rust与其他语言互操作实现
Rust 与 C 语言的互操作
Rust 调用 C 函数
在很多项目中,我们可能已经有了一些成熟的 C 代码库,希望能在 Rust 项目中复用这些代码。要实现 Rust 调用 C 函数,主要通过 extern "C"
声明来完成。
首先,我们需要编写一个简单的 C 函数。假设我们有一个 add.c
文件,内容如下:
#include <stdio.h>
int add(int a, int b) {
return a + b;
}
接下来,我们在 Rust 项目中调用这个函数。创建一个新的 Rust 项目,并在 src/lib.rs
中编写如下代码:
// 使用 `extern "C"` 块来声明外部函数
extern "C" {
fn add(a: i32, b: i32) -> i32;
}
pub fn call_add() {
// 安全调用外部函数,因为这里的函数签名简单且没有复杂的状态
unsafe {
let result = add(2, 3);
println!("The result of add(2, 3) is: {}", result);
}
}
这里,extern "C"
块告诉 Rust 编译器这是一个符合 C 语言 ABI(应用二进制接口)的外部函数。由于调用外部函数绕过了 Rust 的安全检查,所以需要在 unsafe
块中进行调用。
C 调用 Rust 函数
让 Rust 函数可以被 C 调用,需要使用 #[no_mangle]
注解,并指定正确的 ABI。
编写一个 Rust 函数库。在 src/lib.rs
中:
#[no_mangle]
pub extern "C" fn multiply(a: i32, b: i32) -> i32 {
a * b
}
这里 #[no_mangle]
阻止 Rust 对函数名进行重整(name mangling),这样 C 代码就能以我们定义的名字找到这个函数。extern "C"
声明函数使用 C ABI。
然后,我们编写 C 代码来调用这个 Rust 函数。假设我们有一个 main.c
文件:
#include <stdio.h>
// 声明 Rust 函数
extern int multiply(int a, int b);
int main() {
int result = multiply(4, 5);
printf("The result of multiply(4, 5) is: %d\n", result);
return 0;
}
要让 C 代码能够链接到 Rust 函数,我们需要将 Rust 代码编译成动态库。在 Rust 项目根目录下执行:
cargo build --release --lib
编译完成后,在 target/release
目录下会生成动态库文件(如 libmylib.so
或 mylib.dll
)。在 C 项目中,将这个动态库链接进去,就可以调用 Rust 函数了。
Rust 与 Python 的互操作
使用 PyO3 进行 Rust 与 Python 的交互
PyO3 是一个用于在 Rust 和 Python 之间进行交互的库。它允许我们在 Rust 中编写 Python 扩展模块。
首先,创建一个新的 Rust 项目并在 Cargo.toml
中添加 pyo3
依赖:
[dependencies]
pyo3 = "0.18"
然后在 src/lib.rs
中编写如下代码:
use pyo3::prelude::*;
// 定义一个 Rust 函数
fn greet(name: &str) -> String {
format!("Hello, {}!", name)
}
// 将 Rust 函数暴露为 Python 函数
#[pymodule]
fn my_module(_py: Python, m: &PyModule) -> PyResult<()> {
m.add_function(wrap_pyfunction!(greet, m)?)?;
Ok(())
}
这里,use pyo3::prelude::*
引入了 PyO3 的常用类型和宏。greet
是一个普通的 Rust 函数,#[pymodule]
注解将 my_module
函数标记为 Python 模块初始化函数。在 my_module
中,使用 add_function
将 greet
函数添加到 Python 模块中。
接下来,我们需要将这个 Rust 代码编译成 Python 扩展模块。在 Cargo.toml
中添加以下内容:
[lib]
name = "my_module"
crate-type = ["cdylib"]
然后执行:
cargo build --release
编译完成后,在 target/release
目录下会生成一个共享库文件(如 my_module.so
或 my_module.pyd
)。将这个文件复制到 Python 项目的目录中,就可以在 Python 中导入并使用了:
import my_module
print(my_module.greet("World"))
使用 ctypes 在 Python 中调用 Rust 生成的 C 兼容库
如果我们之前将 Rust 函数编译成了符合 C ABI 的库,也可以使用 Python 的 ctypes
模块来调用。
假设我们有一个 Rust 函数库 libmylib.so
,其中有一个 add_numbers
函数:
#[no_mangle]
pub extern "C" fn add_numbers(a: i32, b: i32) -> i32 {
a + b
}
在 Python 中调用:
import ctypes
# 加载动态库
lib = ctypes.CDLL('./libmylib.so')
# 定义函数参数和返回类型
lib.add_numbers.argtypes = [ctypes.c_int, ctypes.c_int]
lib.add_numbers.restype = ctypes.c_int
# 调用函数
result = lib.add_numbers(3, 4)
print(result)
这里,ctypes.CDLL
加载动态库,通过 argtypes
和 restype
定义函数的参数类型和返回类型,然后就可以调用 Rust 函数了。
Rust 与 Java 的互操作
使用 JNI 实现 Rust 与 Java 的交互
Java 本地接口(JNI)允许 Java 代码调用本地代码(如 C、C++、Rust 等)。要在 Rust 中使用 JNI,我们需要一些额外的工具和库。
首先,在 Cargo.toml
中添加 jni
依赖:
[dependencies]
jni = "0.19"
编写一个简单的 Rust 函数来实现加法操作:
use jni::JNIEnv;
use jni::objects::{JClass, JInt};
#[no_mangle]
pub extern "system" fn Java_com_example_Main_add(
env: JNIEnv,
_class: JClass,
a: JInt,
b: JInt,
) -> JInt {
let a = env.get_value(a);
let b = env.get_value(b);
(a + b).into()
}
这里,Java_com_example_Main_add
函数名遵循 JNI 的命名规范,其中 com_example_Main
是 Java 类的全限定名(包名 + 类名),add
是要调用的方法名。
接下来,我们需要将 Rust 代码编译成共享库。在 Cargo.toml
中:
[lib]
name = "mylib"
crate-type = ["cdylib"]
编译:
cargo build --release
在 Java 中,我们创建一个 Main.java
文件:
class Main {
// 加载本地库
static {
System.loadLibrary("mylib");
}
// 声明本地方法
public native static int add(int a, int b);
public static void main(String[] args) {
int result = add(2, 3);
System.out.println("The result of add(2, 3) is: " + result);
}
}
这里,通过 System.loadLibrary("mylib")
加载 Rust 生成的共享库,add
方法声明为 native
,表示这是一个本地方法,实际实现由 Rust 代码提供。
使用 GraalVM 实现更紧密的 Rust 与 Java 集成
GraalVM 是一个高性能的多语言运行时。它允许我们在同一个运行时环境中运行不同语言的代码,包括 Java 和 Rust。
要在 GraalVM 中使用 Rust,我们需要使用 GraalVM 的本地镜像功能。首先,将 Rust 代码编译成静态库。假设我们有一个简单的 Rust 函数:
#[no_mangle]
pub extern "C" fn multiply_numbers(a: i32, b: i32) -> i32 {
a * b
}
编译成静态库:
cargo build --release --lib --target=x86_64-unknown-linux-gnu
然后,我们使用 GraalVM 的 native-image
工具将 Java 代码和 Rust 静态库集成。假设我们有一个 Main.java
文件:
class Main {
// 加载本地库
static {
System.loadLibrary("mylib");
}
// 声明本地方法
public native static int multiply(int a, int b);
public static void main(String[] args) {
int result = multiply(4, 5);
System.out.println("The result of multiply(4, 5) is: " + result);
}
}
使用 native-image
工具生成本地镜像:
native-image --no-fallback -H:Name=myapp -H:IncludeResources='libmylib.so' -cp target/classes Main
这里,--no-fallback
表示不使用解释执行模式,-H:Name=myapp
指定生成的可执行文件名为 myapp
,-H:IncludeResources='libmylib.so'
将 Rust 生成的静态库包含到本地镜像中,-cp target/classes
指定 Java 类文件的路径。
生成的 myapp
可执行文件可以直接运行,实现了 Java 与 Rust 的紧密集成。
Rust 与 JavaScript 的互操作
使用 wasm - bindgen 实现 Rust 与 JavaScript 交互
WebAssembly(Wasm)是一种可以在现代 Web 浏览器中高效运行的二进制指令格式。wasm - bindgen
是一个工具,用于在 Rust 和 JavaScript 之间生成绑定代码。
首先,创建一个新的 Rust 项目并在 Cargo.toml
中添加依赖:
[dependencies]
wasm - bindgen = "0.2"
在 src/lib.rs
中编写 Rust 代码:
use wasm_bindgen::prelude::*;
// 将 Rust 函数暴露给 JavaScript
#[wasm_bindgen]
pub fn greet(name: &str) -> String {
format!("Hello, {}!", name)
}
这里,#[wasm_bindgen]
注解将 greet
函数标记为可以暴露给 JavaScript 的函数。
然后,编译 Rust 代码为 WebAssembly:
cargo build --target=wasm32 - unknown - unknown --release
编译完成后,使用 wasm - bindgen
工具生成 JavaScript 绑定代码:
wasm - bindgen target/wasm32 - unknown - unknown/release/my_project.wasm --out - dir=www
这会在 www
目录下生成 my_project.js
和 my_project_bg.wasm
文件。
在 JavaScript 中,可以这样使用:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
</head>
<body>
<script type="module">
import init, { greet } from './www/my_project.js';
async function run() {
await init();
const result = greet('World');
console.log(result);
}
run();
</script>
</body>
</html>
这里,通过 import
引入生成的 JavaScript 绑定代码,调用 init
初始化 WebAssembly 模块,然后调用 greet
函数。
使用 Node - Rust 实现 Node.js 与 Rust 的交互
Node - Rust 是一个用于在 Node.js 中调用 Rust 代码的库。它基于 N-API,提供了一个稳定的 API 用于编写 Node.js 原生插件。
在 Cargo.toml
中添加依赖:
[dependencies]
node - rust = "0.13"
在 src/lib.rs
中编写 Rust 函数:
use node_rust::prelude::*;
// 定义一个 Rust 函数
fn add(a: i32, b: i32) -> i32 {
a + b
}
// 将 Rust 函数暴露为 Node.js 函数
#[node_export]
fn my_add(env: &mut Environment, args: &mut Arguments) -> Result<Value> {
let a: i32 = args.get(0)?.try_into()?;
let b: i32 = args.get(1)?.try_into()?;
let result = add(a, b);
Ok(env.number(result))
}
这里,#[node_export]
注解将 my_add
函数标记为可以在 Node.js 中调用的函数。
编译 Rust 代码:
cargo build --release
在 Node.js 中使用:
const addon = require('./target/release/my_addon');
const result = addon.myAdd(2, 3);
console.log(result);
这里,通过 require
引入 Rust 生成的 Node.js 插件,调用 myAdd
函数。
Rust 与 Go 的互操作
使用 cgo 实现 Go 调用 Rust 生成的 C 兼容库
Go 语言提供了 cgo
工具来调用 C 代码,因此我们可以通过将 Rust 代码编译成符合 C ABI 的库,然后在 Go 中使用 cgo
调用。
首先,编写 Rust 函数库:
#[no_mangle]
pub extern "C" fn divide_numbers(a: i32, b: i32) -> i32 {
if b != 0 {
a / b
} else {
0
}
}
编译成共享库:
cargo build --release --lib
在 Go 项目中,创建一个 main.go
文件:
package main
/*
#cgo LDFLAGS: -L. -lmylib
#include "stdlib.h"
extern int divide_numbers(int a, int b);
*/
import "C"
import "fmt"
func main() {
a := 10
b := 2
result := int(C.divide_numbers(C.int(a), C.int(b)))
fmt.Printf("The result of divide(%d, %d) is: %d\n", a, b, result)
}
这里,通过 #cgo LDFLAGS
指定共享库的路径和名称,通过 extern
声明 Rust 函数。在 main
函数中,调用 Rust 函数并打印结果。
使用 gollvm 实现更高效的 Go 与 Rust 集成
gollvm 是一个实验性的项目,它允许在 Go 代码中直接调用 Rust 函数,而无需通过 C ABI 进行间接调用。
首先,安装 gollvm
:
go get -u github.com/llir/gollvm
假设我们有一个 Rust 函数库 libmylib.rlib
:
#[no_mangle]
pub extern "system" fn subtract_numbers(a: i32, b: i32) -> i32 {
a - b
}
在 Go 中使用 gollvm
:
package main
import (
"fmt"
"github.com/llir/gollvm/llvm"
)
func main() {
llvm.InitializeAllTargetInfos()
llvm.InitializeAllTargets()
llvm.InitializeAllTargetMCs()
llvm.InitializeAllAsmPrinters()
engine, err := llvm.NewEngine("")
if err != nil {
panic(err)
}
defer engine.Dispose()
mod, err := llvm.ParseIR(`
; ModuleID = 'rust_module'
target datalayout = "e - p:64:64:64 - i1:8:8 - i8:8:8 - i16:16:16 - i32:32:32 - i64:64:64 - f32:32:32 - f64:64:64"
target triple = "x86_64 - unknown - linux - gnu"
declare i32 @subtract_numbers(i32, i32)
define i32 @call_subtract_numbers(i32 %a, i32 %b) {
%result = call i32 @subtract_numbers(i32 %a, i32 %b)
ret i32 %result
}
`)
if err != nil {
panic(err)
}
defer mod.Dispose()
err = engine.AddModule(mod)
if err != nil {
panic(err)
}
sym, err := engine.GetFunction("call_subtract_numbers")
if err != nil {
panic(err)
}
a := int32(5)
b := int32(3)
result, err := engine.RunFunction(sym, []llvm.Value{llvm.NewInt(a), llvm.NewInt(b)})
if err != nil {
panic(err)
}
fmt.Printf("The result of subtract(%d, %d) is: %d\n", a, b, result.Int())
}
这里,通过 llvm
相关的库加载 Rust 函数并调用。gollvm
虽然提供了更直接的集成方式,但目前还处于实验阶段,使用时需要注意其稳定性。
总结不同互操作方式的特点和适用场景
Rust 与 C 语言互操作的特点和适用场景
- 特点:
- 直接利用 C 的 ABI,实现简单直接。
- 由于 C 语言的广泛使用,已有大量的 C 代码库可复用。
- Rust 调用 C 函数需要在
unsafe
块中进行,绕过了 Rust 的安全检查。
- 适用场景:
- 当项目中已有成熟的 C 代码库,希望在 Rust 项目中复用这些代码时,如一些底层系统库的调用。
- 对性能要求极高,且 C 代码在特定平台上已进行高度优化的场景。
Rust 与 Python 互操作的特点和适用场景
- 使用 PyO3 的特点:
- 提供了一种 Rust 编写 Python 扩展模块的便捷方式,能充分利用 Rust 的性能优势和 Python 的生态。
- 可以方便地将 Rust 函数暴露为 Python 函数,并且支持 Python 的类型系统。
- 开发体验较好,通过宏和类型系统简化了很多操作。
- 使用 PyO3 的适用场景:
- 当需要为 Python 项目开发高性能的扩展模块时,如数据处理、科学计算等领域。
- 希望在 Python 环境中利用 Rust 的安全和性能特性,同时又能享受 Python 丰富的库和易用性。
- 使用 ctypes 的特点:
- 基于 Python 的标准库
ctypes
,不需要额外安装复杂的工具链。 - 只要 Rust 代码编译成符合 C ABI 的库,就能直接调用,通用性较强。
- 但需要手动处理类型转换等操作,相对较为繁琐。
- 基于 Python 的标准库
- 使用 ctypes 的适用场景:
- 对 Python 环境的侵入性要求较低,只需要简单地调用 Rust 实现的功能时。
- 快速验证想法,不想引入过多新工具和依赖的场景。
Rust 与 Java 互操作的特点和适用场景
- 使用 JNI 的特点:
- 是 Java 与本地代码交互的标准方式,在 Java 生态中广泛应用。
- 可以实现 Java 与 Rust 的双向调用,但开发过程相对复杂,需要遵循 JNI 的规范。
- 对不同平台的兼容性较好。
- 使用 JNI 的适用场景:
- 当 Java 项目需要调用 Rust 实现的高性能功能,或者 Rust 项目需要与已有的 Java 生态系统集成时。
- 例如在大型企业级应用中,结合 Rust 的性能优势和 Java 的企业级开发优势。
- 使用 GraalVM 的特点:
- 提供了更紧密的集成方式,能在同一个运行时环境中运行 Java 和 Rust 代码。
- 可以生成本地镜像,提高启动速度和性能。
- 但 GraalVM 相对较新,对开发人员的要求可能较高,且一些功能可能还在完善中。
- 使用 GraalVM 的适用场景:
- 对启动性能和运行性能都有较高要求,且希望在一个统一的运行时环境中管理多种语言代码的场景。
- 例如一些微服务架构中,不同服务可能用不同语言实现,使用 GraalVM 可以实现更高效的整合。
Rust 与 JavaScript 互操作的特点和适用场景
- 使用 wasm - bindgen 的特点:
- 专门为 WebAssembly 设计,能很好地在浏览器环境中实现 Rust 与 JavaScript 的交互。
- 生成的代码易于在前端项目中集成,支持现代 JavaScript 的模块系统。
- 利用 WebAssembly 的性能优势,提升前端应用的性能。
- 使用 wasm - bindgen 的适用场景:
- 前端开发中,当需要处理一些高性能的计算任务,如游戏开发、图形处理等,而 JavaScript 的性能无法满足需求时。
- 希望在前端项目中复用 Rust 的安全和高性能特性,同时保持与现有 JavaScript 生态的兼容性。
- 使用 Node - Rust 的特点:
- 专注于 Node.js 与 Rust 的集成,基于 N - API 提供了稳定的接口。
- 可以将 Rust 代码封装成 Node.js 的原生插件,方便在 Node.js 项目中使用。
- 开发过程相对简单,对 Node.js 开发者友好。
- 使用 Node - Rust 的适用场景:
- 在 Node.js 项目中,当需要提升某些模块的性能,或者复用 Rust 编写的库时。
- 例如在后端服务开发中,对一些计算密集型的任务使用 Rust 实现,以提高整体性能。
Rust 与 Go 互操作的特点和适用场景
- 使用 cgo 的特点:
- 借助 Go 的
cgo
工具,实现方式较为直接,利用 C ABI 进行交互。 - 对 Go 和 Rust 开发者来说,不需要引入过多全新的概念,容易理解和上手。
- 但由于通过 C ABI 间接调用,可能存在一定的性能损耗。
- 借助 Go 的
- 使用 cgo 的适用场景:
- 当 Go 项目中希望复用 Rust 实现的功能,且对性能要求不是极致苛刻时。
- 例如在一些小型的工具类项目中,结合 Go 的开发效率和 Rust 的性能优势。
- 使用 gollvm 的特点:
- 提供了更高效的直接调用方式,避免了通过 C ABI 的间接调用带来的性能损耗。
- 但目前处于实验阶段,稳定性和成熟度相对较低。
- 对开发人员的要求较高,需要了解 LLVM 相关知识。
- 使用 gollvm 的适用场景:
- 对性能要求极高,且对稳定性要求相对较低,愿意尝试新技术的场景。
- 例如在一些对性能敏感的科研项目或者性能优化实验中。