Skip to content

Rust 和 JavaScript 类型之间的转换。

本页介绍常见值。有关完整的源码依据矩阵,包括转换方向、所有权、Cargo 特性、OptionEither、集合、路径、函数、Promise、stream 和 Node-API 级别,请参阅类型转换

Undefined

代表 JavaScript 中的 undefined

lib.rs
rust
#[napi]
fn get_undefined() -> Undefined {
	() 
}

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

Null

代表 JavaScript 中的 null

lib.rs
rust
#[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
ts
export function getNull(): null
export function getEnv(env: string): string | null

Option<T> 作为参数时接受 Tnullundefined,但返回值为 None 时会返回 null。在 #[napi(object)] 字段中,默认表示是可选属性,输出时会省略 None#[napi(use_nullable)] 则会将其变为必需的 T | null 属性。有关随位置变化的完整映射,请参阅 Optionnullundefined

Numbers

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

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

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

String

代表 JavaScript String 类型。

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

Boolean

代表 JavaScript Boolean 类型。

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

Buffer

lib.rs
rust
#[napi]
fn with_buffer(buf: Buffer) {
  let buf: Vec<u8> = buf.into();
  // do something
}

#[napi]
fn read_buffer(file: String) -> Result<Buffer> {
	Ok(std::fs::read(file)?.into())
}
index.d.ts
ts
export function withBuffer(buf: Buffer): void
export function readBuffer(file: string): Buffer

Object

代表 JavaScript 匿名对象值。

WARNING

性能

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

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

lib.rs
rust
#[napi]
pub fn keys(obj: Object) -> Result<Vec<String>> {
	Object::keys(&obj)
}

#[napi]
pub fn log_string_field(obj: Object, field: String) -> Result<()> {
	println!("{}: {:?}", &field, obj.get::<String>(&field)?);
	Ok(())
}

#[napi]
pub fn create_obj(env: &Env) -> Result<Object> {
	let mut obj = Object::new(env)?;
	obj.set("test", 1)?;
	Ok(obj)
}
index.d.ts
ts
export function keys(obj: object): Array<string>
export function logStringField(obj: object, field: string): void
export function createObj(): object

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

lib.rs
rust
use std::collections::HashMap;

/// #[napi(object)] 需要所有的结构体字段都是对外可见的
#[napi(object)]
pub struct PackageJson {
	pub name: String,
	pub version: String,
	pub dependencies: Option<HashMap<String, String>>,
	pub dev_dependencies: Option<HashMap<String, String>>,
}

#[napi]
pub fn log_package_name(package_json: PackageJson) {
	println!("name: {}", package_json.name);
}

#[napi]
pub fn example_package_json() -> PackageJson {
	PackageJson {
		name: "example".to_owned(),
		version: "1.0.0".to_owned(),
		dependencies: None,
		dev_dependencies: None,
	}
}
index.d.ts
ts
export interface PackageJson {
  name: string
  version: string
  dependencies?: Record<string, string>
  devDependencies?: Record<string, string>
}
export function logPackageName(packageJson: PackageJson): void
export function examplePackageJson(): PackageJson

WARNING

深拷贝

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

#[napi(object)] 是拥有所有权的普通对象形状,不是类。需要原生类身份和方法时使用 #[napi] struct;需要使用内部 JavaScript 表示的 Rust newtype 时使用 #[napi(transparent)];需要元组形状的数组时使用 #[napi(array)]。参阅类型转换

lib.rs
rust
/// #[napi(object)] 需要所有的结构体字段都是对外可见的
#[napi(object)]
struct Animal {
	pub name: String,
}

#[napi]
fn change_animal_name(mut animal: Animal) {
  animal.name = "cat".to_string();
}
js
const animal = { name: 'dog' }
changeAnimalName(animal)
console.log(animal.name) // "dog"

Array

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

WARNING

性能

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

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

lib.rs
rust
#[napi]
fn arr_len(arr: Array) -> u32 {
  arr.len()
}

#[napi]
fn get_tuple_array(env: &Env) -> Result<Array> {
  let mut arr = env.create_array(2)?;

  arr.insert(1)?;
  arr.insert("test")?;

  Ok(arr)
}

#[napi]
fn vec_len(nums: Vec<u32>) -> Result<u32> {
  u32::try_from(nums.len())
    .map_err(|_| Error::new(Status::InvalidArg, "Array is too large"))
}

#[napi]
fn get_nums() -> Vec<u32> {
  vec![1, 1, 2, 3, 5, 8]
}
index.d.ts
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 特性。

WARNING

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

TIP

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

lib.rs
rust
/// `get_u128` 的返回值是 (signed: bool, value: u128, lossless: bool)
#[napi]
pub fn bigint_add(a: BigInt, b: BigInt) -> Result<u128> {
  let (a_signed, a_value, a_lossless) = a.get_u128();
  let (b_signed, b_value, b_lossless) = b.get_u128();
  if a_signed || b_signed || !a_lossless || !b_lossless {
    return Err(Error::new(
      Status::InvalidArg,
      "both values must be lossless, non-negative u128 integers",
    ));
  }
  a_value.checked_add(b_value).ok_or_else(|| {
    Error::new(Status::InvalidArg, "u128 addition overflowed")
  })
}

#[napi]
pub fn create_big_int_i128() -> i128 {
  100
}
index.d.ts
ts
export function bigintAdd(a: bigint, b: bigint): bigint
export function createBigIntI128(): bigint

TypedArray

TIP

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

lib.rs
rust
#[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 unsafe { input.as_mut() } {
    *item *= 2.0;
  }
}
index.d.ts
ts
export function convertU32Array(input: Uint32Array): Array<number>
export function createExternalTypedArray(): Uint32Array
export function mutateTypedArray(input: Float32Array): void
test.mjs
js
import { convertU32Array, mutateTypedArray } from './index.js'

convertU32Array(new Uint32Array([1, 2, 3, 4, 5])) // [1, 2, 3, 4, 5]
const values = new Float32Array([1, 2, 3, 4, 5])
mutateTypedArray(values)
console.log(values) // Float32Array(5) [ 2, 4, 6, 8, 10 ]