类
TIP
Rust 中没有类的概念。我们使用 struct 来表示 JavaScript 的 Class。
选择正确的 JavaScript 形状
在结构体上使用 #[napi] 会创建具有原生身份和方法的 JavaScript 类。其他结构体属性会创建不同的值形状:
| Rust 声明 | JavaScript 表示 | 适用场景 |
|---|---|---|
#[napi] struct |
由一个 Rust 值支持的类实例 | 有状态的原生对象、方法、身份和引用 |
#[napi(object)] struct |
与拥有所有权的 Rust 结构体相互复制的普通对象 | 记录、选项和配置形状 |
#[napi(transparent)] struct Wrapper(T) |
内部值 T |
不应添加 JavaScript 包装层的 Rust newtype |
#[napi(array)] 元组结构体 |
JavaScript Array / TypeScript 元组 | 固定的位置数据 |
有关方向和所有权规则,请参阅类型转换;有关完整的形状控制,请参阅 #[napi] 属性。
Constructor
默认 constructor
如果一个 Rust 结构体中的所有字段都是 pub,那么你可以使用 #[napi(constructor)] 来使 struct 有一个默认的 constructor。
#[napi(constructor)]
pub struct AnimalWithDefaultConstructor {
pub name: String,
pub kind: u32,
}
export class AnimalWithDefaultConstructor {
name: string
kind: number
constructor(name: string, kind: number)
}
每个公开字段都是 JavaScript API 的一部分:napi-rs 会生成 getter,并且在字段没有 #[napi(readonly)] 时生成 setter。因此,该字段的 Rust 类型必须支持所生成方向的 JavaScript 转换。仅供原生代码使用的状态应保持私有,就像下方自定义构造函数示例中的 count 一样。
自定义 constructor
如果你想定义一个自定义的 constructor,你可以在结构体的 impl 块中的构造函数 fn 上面使用 #[napi(constructor)]。
#[napi(js_name = "QueryEngine")]
pub struct JsQueryEngine {
count: u32,
}
#[napi]
impl JsQueryEngine {
#[napi(constructor)]
pub fn new() -> Self {
JsQueryEngine { count: 0 }
}
}
export class QueryEngine {
constructor()
}
WARNING
NAPI-RS 目前不支持 private constructor,在 Rust
中你的自定义构造函数必须是 pub 的。
工厂
除了 constructor 之外,你还可以使用 #[napi(factory)] 在 Class 上定义工厂方法。
#[napi(js_name = "QueryEngine")]
pub struct JsQueryEngine {
count: u32,
}
#[napi]
impl JsQueryEngine {
#[napi(factory)]
pub fn with_initial_count(count: u32) -> Self {
JsQueryEngine { count }
}
}
export class QueryEngine {
static withInitialCount(count: number): QueryEngine
constructor()
}
WARNING
如果结构体中没有定义 #[napi(constructor)],并且你尝试在 JavaScript
中创建一个 Class 的实例(new),这将会抛出一个错误。
import { QueryEngine } from './index.js'
new QueryEngine() // Error: Class contains no `constructor`, cannot create it!
class method
你可以在 Rust 的结构体方法上使用 #[napi] 定义一个 JavaScript 类方法。
#[napi(js_name = "QueryEngine")]
pub struct JsQueryEngine {
count: u32,
}
#[napi]
impl JsQueryEngine {
#[napi(factory)]
pub fn with_initial_count(count: u32) -> Self {
JsQueryEngine { count }
}
/// 类方法
#[napi]
pub async fn query(&self, query: String) -> napi::Result<String> {
Ok(format!("{query}: {}", self.count))
}
#[napi]
pub fn status(&self) -> napi::Result<u32> {
Ok(self.count)
}
}
export class QueryEngine {
static withInitialCount(count: number): QueryEngine
constructor()
query(query: string): Promise<string>
status(): number
}
WARNING
async fn 需要启用 napi4 和 tokio_rt 特性。
TIP
任何返回 Result<T> 的 Rust fn 在 JavaScript/TypeScript 中都会被视为 T ,
如果 Result<T> 是 Err,则会抛出一个 JavaScript 错误。
Getter
使用 #[napi(getter)] 定义 JavaScript 类的 getter,
Rust 的 fn 必须是一个结构体方法,而不是一个关联函数。
#[napi(js_name = "QueryEngine")]
pub struct JsQueryEngine {
count: u32,
}
#[napi]
impl JsQueryEngine {
#[napi(factory)]
pub fn with_initial_count(count: u32) -> Self {
JsQueryEngine { count }
}
/// 类方法
#[napi]
pub async fn query(&self, query: String) -> napi::Result<String> {
Ok(format!("{query}: {}", self.count))
}
#[napi(getter)]
pub fn status(&self) -> napi::Result<u32> {
Ok(self.count)
}
}
export class QueryEngine {
static withInitialCount(count: number): QueryEngine
constructor()
get status(): number
}
Setter
使用 #[napi(setter)] 定义 JavaScript 类的 setter,
Rust 的 fn 必须是一个结构体方法,而不是一个关联函数。
#[napi(js_name = "QueryEngine")]
pub struct JsQueryEngine {
count: u32,
}
#[napi]
impl JsQueryEngine {
#[napi(factory)]
pub fn with_initial_count(count: u32) -> Self {
JsQueryEngine { count }
}
/// 类方法
#[napi]
pub async fn query(&self, query: String) -> napi::Result<String> {
Ok(format!("{query}: {}", self.count))
}
#[napi(getter)]
pub fn status(&self) -> napi::Result<u32> {
Ok(self.count)
}
#[napi(setter)]
pub fn count(&mut self, count: u32) {
self.count = count;
}
}
export class QueryEngine {
static withInitialCount(count: number): QueryEngine
constructor()
get status(): number
set count(count: number)
}
类作为参数
Class 与 Object 不同。Rust 值由 JavaScript 实例包装,并由该环境的垃圾回收器管理。将实例传回 Rust 时,使用 &T 进行共享访问,或使用 &mut T 进行可变访问;该值不是从普通对象克隆而来的。
只有公开的结构体字段会成为 JavaScript 属性。它们默认可写,因为 napi-rs 会生成两种访问器;#[napi(readonly)] 会禁止生成 setter,#[napi(skip)] 会禁止生成两个访问器。私有字段仍然是原生实现细节。可写字段同时需要 ToNapiValue 和 FromNapiValue,只读字段只需要 ToNapiValue。请参阅字段属性参考,其中也说明了 #[napi(constructor)] 结构体简写的限制。
#[napi]
pub fn accept_class(engine: &QueryEngine) {
// ...
}
#[napi]
pub fn accept_class_mut(engine: &mut QueryEngine) {
// ...
}
export function acceptClass(engine: QueryEngine): void
export function acceptClassMut(engine: QueryEngine): void
有关嵌套类实例、类实例数组和 ClassInstance<T>,请参阅转换参考中的类章节。
属性描述
默认的属性描述是 writable = true 、enumerable = true 和 configurable = true ,你可以通过 #[napi] 宏控制属性描述:
use napi::bindgen_prelude::*;
use napi_derive::napi;
// 一个复杂的结构体,无法直接暴露给 JavaScript。
#[napi]
pub struct QueryEngine {
num: i32,
}
#[napi]
impl QueryEngine {
#[napi(constructor)]
pub fn new() -> Result<Self> {
Ok(Self {
num: 42,
})
}
// writable / enumerable / configurable
#[napi(writable = false)]
pub fn get_num(&self) -> i32 {
self.num
}
}
在这个例子中,QueryEngine 的 getNum 方法是不可写的:
import { QueryEngine } from './index.js'
const qe = new QueryEngine()
qe.getNum = function () {} // TypeError: Cannot assign to read only property 'getNum' of object '#<QueryEngine>'
自定义终结逻辑
当 JavaScript 对象被垃圾回收时,NAPI-RS 会释放 JavaScript 对象中封装的 Rust 结构体,您还可以为 Rust 结构体指定自定义终结逻辑。
use napi::bindgen_prelude::*;
use napi_derive::napi;
#[napi(custom_finalize)]
pub struct CustomFinalize {
width: u32,
height: u32,
inner: Vec<u8>,
}
#[napi]
impl CustomFinalize {
#[napi(constructor)]
pub fn new(mut env: Env, width: u32, height: u32) -> Result<Self> {
let inner_size = u64::from(width)
.checked_mul(u64::from(height))
.and_then(|pixels| pixels.checked_mul(4))
.and_then(|bytes| usize::try_from(bytes).ok())
.ok_or_else(|| Error::new(Status::InvalidArg, "image dimensions are too large"))?;
let external_size = i64::try_from(inner_size)
.map_err(|_| Error::new(Status::InvalidArg, "image dimensions are too large"))?;
let mut inner = Vec::new();
inner.try_reserve_exact(inner_size).map_err(|err| {
Error::new(
Status::GenericFailure,
format!("failed to allocate image buffer: {err}"),
)
})?;
inner.resize(inner_size, 0);
env.adjust_external_memory(external_size)?;
Ok(Self {
width,
height,
inner,
})
}
}
impl ObjectFinalize for CustomFinalize {
fn finalize(self, mut env: Env) -> Result<()> {
let external_size = i64::try_from(self.inner.len())
.map_err(|_| Error::new(Status::InvalidArg, "image buffer is too large"))?;
env.adjust_external_memory(-external_size)?;
Ok(())
}
}
首先,您可以在 #[napi] 宏中设置 custom_finalize 属性,NAPI-RS 将不会为 Rust 结构体生成默认的 ObjectFinalize。
然后,您可以自己为 Rust 结构体实现 ObjectFinalize。
在这个例子中,CustomFinalize 结构体在 构造函数 中增加外部内存,并在 fn finalize 中减少外部内存。
instance of
所有 #[napi] 类都有 fn instance_of:
use napi::bindgen_prelude::*;
use napi_derive::napi;
#[napi(constructor)]
pub struct NativeClass {}
#[napi]
pub fn is_native_class_instance(env: &Env, value: Unknown) -> Result<bool> {
NativeClass::instance_of(env, &value)
}
import { NativeClass, isNativeClassInstance } from './index.js'
const nc = new NativeClass()
console.log(isNativeClassInstance(nc)) // true
console.log(isNativeClassInstance(1)) // false