---
title: '类'
description: 使用 NAPI-RS 将 Rust 结构体定义并导出为 JavaScript 类。
---

# 类

::: 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 元组           | 固定的位置数据                            |

有关方向和所有权规则，请参阅[类型转换](/cn/docs/concepts/type-conversions)；有关完整的形状控制，请参阅 [`#[napi]` 属性](/cn/docs/concepts/napi-attributes)。

## `Constructor`

### 默认 `constructor`

如果一个 `Rust` 结构体中的所有字段都是 `pub`，那么你可以使用 `#[napi(constructor)]` 来使 `struct` 有一个默认的 `constructor`。

**lib.rs**

```rust
#[napi(constructor)]
pub struct AnimalWithDefaultConstructor {
  pub name: String,
  pub kind: u32,
}
```

**index.d.ts**

```ts
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)]`。

**lib.rs**

```rust
#[napi(js_name = "QueryEngine")]
pub struct JsQueryEngine {
  count: u32,
}

#[napi]
impl JsQueryEngine {
  #[napi(constructor)]
  pub fn new() -> Self {
    JsQueryEngine { count: 0 }
  }
}
```

**index.d.ts**

```ts
export class QueryEngine {
  constructor()
}
```

::: warning
**NAPI-RS** 目前不支持 `private constructor`，在 Rust
中你的自定义构造函数必须是 `pub` 的。

:::

## 工厂

除了 `constructor` 之外，你还可以使用 `#[napi(factory)]` 在 `Class` 上定义工厂方法。

**lib.rs**

```rust
#[napi(js_name = "QueryEngine")]
pub struct JsQueryEngine {
  count: u32,
}

#[napi]
impl JsQueryEngine {
  #[napi(factory)]
  pub fn with_initial_count(count: u32) -> Self {
    JsQueryEngine { count }
  }
}
```

**index.d.ts**

```ts
export class QueryEngine {
  static withInitialCount(count: number): QueryEngine
  constructor()
}
```

::: warning
如果结构体中没有定义 `#[napi(constructor)]`，并且你尝试在 JavaScript
中创建一个 `Class` 的实例（`new`），这将会抛出一个错误。

:::

**test.mjs**

```js {3}
import { QueryEngine } from './index.js'

new QueryEngine() // Error: Class contains no `constructor`, cannot create it!
```

## `class method`

你可以在 **Rust** 的结构体方法上使用 `#[napi]` 定义一个 `JavaScript` 类方法。

**lib.rs**

```rust
#[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)
  }
}
```

**index.d.ts**

```ts
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`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions/get)，
Rust 的 `fn` 必须是一个结构体方法，而不是一个关联函数。

**lib.rs**

```rust
#[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)
  }
}
```

**index.d.ts**

```ts {4}
export class QueryEngine {
  static withInitialCount(count: number): QueryEngine
  constructor()
  get status(): number
}
```

## `Setter`

使用 `#[napi(setter)]` 定义 [JavaScript 类的 `setter`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions/set)，
Rust 的 `fn` 必须是一个结构体方法，而不是一个关联函数。

**lib.rs**

```rust
#[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;
  }
}
```

**index.d.ts**

```ts {5}
export class QueryEngine {
  static withInitialCount(count: number): QueryEngine
  constructor()
  get status(): number
  set count(count: number)
}
```

## 类作为参数

`Class` 与 [`Object`](./object) 不同。Rust 值由 JavaScript 实例包装，并由该环境的垃圾回收器管理。将实例传回 Rust 时，使用 `&T` 进行共享访问，或使用 `&mut T` 进行可变访问；该值不是从普通对象克隆而来的。

只有公开的结构体字段会成为 JavaScript 属性。它们默认可写，因为 napi-rs 会生成两种访问器；`#[napi(readonly)]` 会禁止生成 setter，`#[napi(skip)]` 会禁止生成两个访问器。私有字段仍然是原生实现细节。可写字段同时需要 `ToNapiValue` 和 `FromNapiValue`，只读字段只需要 `ToNapiValue`。请参阅[字段属性参考](/cn/docs/concepts/napi-attributes#fields)，其中也说明了 `#[napi(constructor)]` 结构体简写的限制。

**lib.rs**

```rust {1,5}
#[napi]
pub fn accept_class(engine: &QueryEngine) {
  // ...
}

#[napi]
pub fn accept_class_mut(engine: &mut QueryEngine) {
  // ...
}
```

**index.d.ts**

```ts
export function acceptClass(engine: QueryEngine): void
export function acceptClassMut(engine: QueryEngine): void
```

有关嵌套类实例、类实例数组和 `ClassInstance<T>`，请参阅[转换参考中的类章节](/cn/docs/concepts/type-conversions#objects-classes-and-custom-shapes)。

## 属性描述

默认的属性描述是 `writable = true` 、`enumerable = true` 和 `configurable = true` ，你可以通过 `#[napi]` 宏控制属性描述：

**lib.rs**

```rust {20}
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` 方法是不可写的：

**main.mjs**

```js {4}
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 结构体指定自定义终结逻辑。

**lib.rs**

```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`：

**lib.rs**

```rust {9}
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)
}
```

**main.mjs**

```js
import { NativeClass, isNativeClassInstance } from './index.js'

const nc = new NativeClass()
console.log(isNativeClassInstance(nc)) // true
console.log(isNativeClassInstance(1)) // false
```
