文档
概念

Rust 和 JavaScript 类型之间的转换。

Undefined

代表 JavaScript 中的 undefined

lib.rs
#[napi]
fn get_undefined() -> Undefined {
	()
}
 
// 默认返回值或空元组 `()` 在转换为 JS 值后都是 `undefined`。
#[napi]
fn log(n: u32) {
	println!("{}", n);
}
index.d.ts
export function getUndefined(): undefined
export function log(n: number): void

Null

代表 JavaScript 中的 null

lib.rs
#[napi]
fn get_null() -> Null {
	Null
}
 
#[napi]
fn get_env(env: String) -> Option<String> {
	match std::env::var(env) {
		Ok(val) => Some(val),
		Err(e) => None,
	}
}
index.d.ts
export function getNull(): null
export function getEnv(env: string): string | null

Numbers

JavaScript Number 等同于这些 Rust 整数/浮点数 类型: u32, i32, i64, f64

如需了解 u64、u128、i128 等 Rust 类型,请查看 BigInt 部分。

lib.rs
#[napi]
fn sum(a: u32, b: i32) -> i64 {
	(b + a as i32).into()
}
index.d.ts
export function sum(a: number, b: number): number

String

代表 JavaScript String 类型。

lib.rs
#[napi]
fn greet(name: String) -> String {
	format!("greeting, {}", name)
}
index.d.ts
export function greet(name: string): string

Boolean

代表 JavaScript Boolean 类型。

lib.rs
#[napi]
fn is_good() -> bool {
	true
}
index.d.ts
export function isGood(): boolean

Buffer

lib.rs
#[napi]
fn with_buffer(buf: Buffer) {
  let buf: Vec<u8> = buf.into();
  // do something
}
 
#[napi]
fn read_buffer(file: String) -> Buffer {
	Buffer::from(std::fs::read(file).unwrap())
}
index.d.ts
export function withBuffer(buf: Buffer): void
export function readBuffer(file: string): Buffer

Object

代表 JavaScript 匿名对象值。

⚠️

性能

在 JavaScript 和 Rust 之间转换 Object 的成本比其他原始类型高。

每次对 Object.get("key") 的调用实际上都会被调度到 node 端,包括两个步骤:获取值,将 JS 转换为 rust 值, Object.set("key", v) 也是如此。

lib.rs
#[napi]
fn keys(obj: Object) -> Vec<String> {
	Object::keys(&obj).unwrap()
}
 
#[napi]
fn log_string_field(obj: Object, field: String) {
	println!("{}: {:?}", &field, obj.get::<String>::(field.as_ref()));
}
 
#[napi]
fn create_obj(env: Env) -> Object {
	let mut obj = env.create_object().unwrap();
	obj.set("test", 1).unwrap();
	obj
}
index.d.ts
export function keys(obj: object): Array<string>
export function logStringField(obj: object): void
export function createObj(): object

如果你想要 NAPI-RS 用 Rust 中定义的结构来转换 JavaScript 中的对象,你可以使用 #[napi] 宏里面的 object 属性。

lib.rs
/// #[napi(object)] 需要所有的结构体字段都是对外可见的
#[napi(object)]
struct PackageJson {
	pub name: String,
	pub version: String,
	pub dependencies: Option<HashMap<String, String>>,
	pub dev_dependencies: Option<HashMap<String, String>>,
}
 
#[napi]
fn log_package_name(package_json: PackageJson) {
	println!("name: {}", package_json.name);
}
 
#[napi]
fn read_package_json() -> PackageJson {
	// ...
}
index.d.ts
export interface PackageJson {
  name: string
  version: string
  dependencies: Record<string, string> | null
  devDependencies: Record<string, string> | null
}
export function logPackageName(packageJson: PackageJson): void
export function readPackageJson(): PackageJson
⚠️

深拷贝

Rust fn 中传入的 #[napi(object)] 结构体是从 JavaScript Object 克隆的, 对其的任何更改都不会影响到原始的 JavaScript 对象。

lib.rs
/// #[napi(object)] 需要所有的结构体字段都是对外可见的
#[napi(object)]
struct Animal {
	pub name: String,
}
 
#[napi]
fn change_animal_name(mut animal: Animal) {
  animal.name = "cat".to_string();
}
const animal = { name: 'dog' }
changeAnimalName(animal)
console.log(animal.name) // "dog"

Array

因为在 JavaScript 中,Array 可以包含不同类型的元素,但是 Rust 的 Vec<T> 只能包含相同类型的元素,所以有两种不同的方式来处理数组类型。

⚠️

性能

因为 JavaScript 的 Array 类型实际上是一种特殊的 Object ,所以操作 Arrays 的性能与操作 Objects 的性能相同。

ArrayVec<T> 之间的转换更为繁重,复杂度为 O(2n)

lib.rs
#[napi]
fn arr_len(arr: Array) -> u32 {
  arr.len()
}
 
#[napi]
fn get_tuple_array(env: Env) -> Array {
  let mut arr = env.create_array(2).unwrap();
 
  arr.insert(1).unwrap();
  arr.insert("test").unwrap();
 
  arr
}
 
#[napi]
fn vec_len(nums: Vec<u32>) -> u32 {
  u32::try_from(nums.len()).unwrap()
}
 
#[napi]
fn get_nums() -> Vec<u32> {
  vec![1, 1, 2, 3, 5, 8]
}
index.d.ts
export function arrLen(arr: unknown[]): number
export function getTupleArray(): unknown[]
export function vecLen(nums: Array<number>): number
export function getNums(): Array<number>

BigInt

这需要 napi6 特性。

⚠️

Rust 中传递 BigInt 的唯一方法是使用 BigInt 类型,但是你可以返回 BigInti64u64i128u128, 返回 i64 将被视为 JavaScript 数字,而不是 BigInt

💡

Rust fn 不能接收 i128 u128 u64 i64n 作为参数的原因是,将 JavaScript BigInt 转换为它们时可能会丢失精度。 您可以使用 BigInt::get_u128BigInt::get_i128 ... 来获取 BigInt 中的值。这些方法的返回值还表明是否丢失了精度。

lib.rs
/// `get_u128` 的返回值是 (signed: bool, value: u128, lossless: bool)
#[napi]
fn bigint_add(a: BigInt, b: BigInt) -> u128 {
  a.get_u128().1 + b.get_u128().1
}
 
#[napi]
fn create_big_int_i128() -> i128 {
  100
}
index.d.ts
export function bigintAdd(a: BigInt, b: BigInt): BigInt
export function createBigIntI128(): BigInt

TypedArray

💡

与 JavaScript 对象不同,传递给 Rust fn 的 TypedArray 是一个 引用, 不会执行任何数据 CopyClone,对 TypedArray 的每次更改都会反映到原始的 JavaScript TypedArray

lib.rs
#[napi]
fn convert_u32_array(input: Uint32Array) -> Vec<u32> {
  input.to_vec()
}
 
#[napi]
fn create_external_typed_array() -> Uint32Array {
  Uint32Array::new(vec![1, 2, 3, 4, 5])
}
 
#[napi]
fn mutate_typed_array(mut input: Float32Array) {
  for item in input.as_mut() {
    *item *= 2.0;
  }
}
index.d.ts
export function convertU32Array(input: Uint32Array): Array<number>
export function createExternalTypedArray(): Uint32Array
export function mutateTypedArray(input: Float32Array): void
test.mjs
import { convertU32Array, mutateTypedArray } from './index.js'
 
convertU32Array(new Uint32Array([1, 2, 3, 4, 5])) // [1, 2, 3, 4, 5]
mutateTypedArray(new Float32Array([1, 2, 3, 4, 5])) // Float32Array(5) [ 2, 4, 6, 8, 10 ]