Rust与Deno的桥梁:deno_napi插件库的使用,实现高性能Node-API与Deno的交互

Rust与Deno的桥梁:deno_napi插件库的使用,实现高性能Node-API与Deno的交互

概述

deno_napi是一个用于连接Rust与Deno的插件库,它通过Node-API标准实现了两者之间的高性能交互。该实现保持了与Node.js相似的文件组织方式,确保良好的兼容性。

添加新函数

1. 声明符号

在symbol_exports.json中添加需要导出的函数符号:

{
  "symbols": [
    "napi_get_undefined",
    "napi_get_null",
    "napi_get_boolean"
  ]
}

2. 实现函数

在适当的Rust文件中(如js_native_api.rs)编写实现:

#[napi_sym::napi_sym]
fn napi_get_boolean(
  env: *mut Env,       // 环境指针
  value: bool,         // 输入布尔值
  result: *mut napi_value,  // 输出结果指针
) -> Result {
  // 实现逻辑...
  Ok(())
}

3. 更新符号列表

运行生成脚本更新符号列表:

deno run --allow-write tools/napi/generate_symbols_lists.js

测试示例

JavaScript测试代码

// 测试napi_get_boolean功能
import { assertEquals, loadTestLibrary } from "./common.js";
const lib = loadTestLibrary();

Deno.test("napi get boolean", function () {
  assertEquals(lib.test_get_boolean(true), true);
  assertEquals(lib.test_get_boolean(false), false);
});

Rust测试实现

use napi_sys::Status::napi_ok;
use napi_sys::ValueType::napi_boolean;
use napi_sys::*;

extern "C" fn test_boolean(
  env: napi_env,
  info: napi_callback_info,
) -> napi_value {
  // 获取回调信息
  let (args, argc, _) = crate::get_callback_info!(env, info, 1);
  assert_eq!(argc, 1);

  // 验证参数类型
  let mut ty = -1;
  assert!(unsafe { napi_typeof(env, args[0], &mut ty) } == napi_ok);
  assert_eq!(ty, napi_boolean);

  // 返回原始值
  value
}

模块注册

在lib.rs中注册测试模块:

mod boolean;

#[no_mangle]
unsafe extern "C" fn napi_register_module_v1(
  env: napi_env,
  exports: napi_value,
) -> napi_value {
  boolean::init(env, exports);
  exports
}

运行测试:

cargo test -p tests/napi

完整开发示例

1. 配置Cargo.toml

[dependencies]
deno_napi = "0.143.0"  # Deno的Node-API支持
napi-sys = "0.5"       # Node-API系统绑定

2. Rust模块实现

use napi_sys::*;
use std::os::raw::c_void;

// 模块注册函数
#[no_mangle]
pub unsafe extern "C" fn napi_register_module_v1(
    env: napi_env,
    exports: napi_value,
) -> napi_value {
    // 定义要导出的hello函数
    let desc = napi_property_descriptor {
        utf8name: "hello\0".as_ptr() as *const _,
        name: std::ptr::null_mut(),
        method: Some(hello),
        getter: None,
        setter: None,
        value: std::ptr::null_mut(),
        attributes: napi_default,
        data: std::ptr::null_mut(),
    };
    
    // 将函数添加到exports对象
    napi_define_properties(env, exports, 1, &desc);
    exports
}

// hello函数实现
unsafe extern "C" fn hello(
    env: napi_env,
    info: napi_callback_info,
) -> napi_value {
    let mut result: napi_value = std::ptr::null_mut();
    
    // 创建返回字符串
    napi_create_string_utf8(
        env,
        "Hello from Rust!\0".as_ptr() as *const _,
        15,
        &mut result,
    );
    
    result
}

3. Deno调用代码

// 加载编译好的Rust库
const lib = Deno.dlopen("./target/debug/libexample.dylib", {
  napi_register_module_v1: { 
    parameters: ["pointer", "pointer"], 
    result: "pointer" 
  },
});

// 初始化模块
const exports = new Deno.UnsafePointerObject(
  lib.symbols.napi_register_module_v1(
    new Deno.UnsafePointer(0n),
    new Deno.UnsafePointer(0n)
  )
);

// 调用Rust函数
const result = Deno.core.unescapePointer(expts.hello());
console.log(result); // 输出: "Hello from Rust!"

4. 构建和运行

# 编译Rust库
cargo build

# 运行Deno测试(需要启用不稳定特性)
deno run --allow-ffi --unstable test.ts

平台注意事项

根据操作系统不同,生成的库文件扩展名也不同:

  • macOS: .dylib
  • Linux: .so
  • Windows: .dll

在Deno代码中需要根据实际平台调整库文件路径。


1 回复

Rust与Deno的桥梁:deno_napi插件库的使用

介绍

deno_napi是一个允许Rust代码通过Node-API(N-API)与Deno交互的插件库。它提供了一种高性能的方式来扩展Deno的功能,利用Rust的性能优势和Deno的JavaScript运行时能力。

主要特点

  • 在Deno环境中使用Rust编写的原生模块
  • 通过Node-API标准接口实现互操作
  • 高性能的跨语言调用
  • 支持异步操作
  • 类型安全的接口绑定

完整示例demo

Rust部分 (src/lib.rs)

use napi_derive::napi;

// 简单的加法函数
#[napi]
pub fn sum(a: i32, b: i32) -> i32 {
    a + b
}

// 异步乘法函数
#[napi]
pub async fn async_multiply(a: i32, b: i32) -> i32 {
    tokio::time::sleep(std::time::Duration::from_secs(1)).await;
    a * b
}

// 带错误处理的除法函数
#[napi]
pub fn divide(a: i32, b: i32) -> napi::Result<i32> {
    if b == 0 {
        return Err(napi::Error::new(
            napi::Status::GenericFailure,
            "Cannot divide by zero".to_string(),
        ));
    }
    Ok(a / b)
}

// 数组映射函数,接受JavaScript回调
#[napi]
pub fn map_array(arr: Vec<i32>, callback: napi::JsFunction) -> napi::Result<Vec<i32>> {
    let mut result = Vec::new();
    for item in arr {
        let new_item: i32 = callback.call(None, &[item.into()])?.try_into()?;
        result.push(new_item);
    }
    Ok(result)
}

// Person结构体定义
#[napi]
pub struct Person {
    pub name: String,
    pub age: i32,
}

#[napi]
impl Person {
    #[napi(constructor)]
    pub fn new(name: String, age: i32) -> Self {
        Self { name, age }
    }

    #[napi]
    pub fn introduce(&self) -> String {
        format!("Hi, I'm {}, {} years old", self.name, self.age)
    }
    
    #[napi]
    pub fn birthday(&mut self) {
        self.age += 1;
    }
}

Deno部分 (example.js)

import { load } from "https://deno.land/x/deno_napi/mod.ts";

// 加载编译好的Rust模块
const {
  sum,
  asyncMultiply,
  divide,
  mapArray,
  Person
} = await load("./target/release/libdeno_napi_example.so");

// 测试同步函数
console.log("1 + 2 =", sum(1, 2)); // 输出: 1 + 2 = 3

// 测试异步函数
(async () => {
  console.log("3 * 4 =", await asyncMultiply(3, 4)); // 1秒后输出: 3 * 4 = 12
})();

// 测试错误处理
try {
  console.log("10 / 0 =", divide(10, 0));
} catch (e) {
  console.error("Error:", e.message); // 输出: Error: Cannot divide by zero
}

// 测试回调函数
const squared = mapArray([1, 2, 3, 4], (x) => x * x);
console.log("Squared array:", squared); // 输出: Squared array: [1, 4, 9, 16]

// 测试类和方法
const alice = new Person("Alice", 30);
console.log(alice.introduce()); // 输出: Hi, I'm Alice, 30 years old

alice.birthday();
console.log("After birthday:", alice.introduce()); // 输出: After birthday: Hi, I'm Alice, 31 years old

构建和运行步骤

  1. 创建Rust项目并添加依赖
cargo new --lib deno_napi_example
cd deno_napi_example
  1. 编辑Cargo.toml
[lib]
crate-type = ["cdylib"]

[dependencies]
napi = { version = "2", default-features = false }
napi-derive = "2"
deno_napi = "0.1"
tokio = { version = "1", features = ["rt"] }
  1. 构建发布版本
cargo build --release
  1. 运行Deno测试脚本
deno run --allow-read example.js

性能提示

  1. 尽量减少跨语言调用的次数,批量处理数据
  2. 对于复杂数据结构,考虑使用ArrayBuffer或SharedArrayBuffer传输
  3. 长时间运行的任务应该在Rust端使用异步API

注意事项

  • 确保Deno版本与deno_napi兼容
  • 跨平台时注意库文件扩展名(.so, .dll, .dylib)
  • 发布时需要包含编译好的二进制文件
回到顶部