MK
摩柯社区 - 一个极简的技术知识社区
AI 面试

Rust与其他语言互操作实现

2023-05-253.7k 阅读

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.somylib.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_functiongreet 函数添加到 Python 模块中。

接下来,我们需要将这个 Rust 代码编译成 Python 扩展模块。在 Cargo.toml 中添加以下内容:

[lib]
name = "my_module"
crate-type = ["cdylib"]

然后执行:

cargo build --release

编译完成后,在 target/release 目录下会生成一个共享库文件(如 my_module.somy_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 加载动态库,通过 argtypesrestype 定义函数的参数类型和返回类型,然后就可以调用 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.jsmy_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 的库,就能直接调用,通用性较强。
    • 但需要手动处理类型转换等操作,相对较为繁琐。
  • 使用 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 间接调用,可能存在一定的性能损耗。
  • 使用 cgo 的适用场景
    • 当 Go 项目中希望复用 Rust 实现的功能,且对性能要求不是极致苛刻时。
    • 例如在一些小型的工具类项目中,结合 Go 的开发效率和 Rust 的性能优势。
  • 使用 gollvm 的特点
    • 提供了更高效的直接调用方式,避免了通过 C ABI 的间接调用带来的性能损耗。
    • 但目前处于实验阶段,稳定性和成熟度相对较低。
    • 对开发人员的要求较高,需要了解 LLVM 相关知识。
  • 使用 gollvm 的适用场景
    • 对性能要求极高,且对稳定性要求相对较低,愿意尝试新技术的场景。
    • 例如在一些对性能敏感的科研项目或者性能优化实验中。