Documentação
Conceitos
Class

Classe

💡

Não há o conceito de classe em Rust. Utilizamos struct para representar uma Class JavaScript.

Constructor

constructor padrão

Se todos os campos em uma struct Rust forem pub, então você pode usar #[napi(constructor)] para fazer com que a struct tenha um constructor padrão.

lib.rs
#[napi(constructor)]
pub struct AnimalWithDefaultConstructor {
  pub name: String,
  pub kind: u32,
}
index.d.ts
export class AnimalWithDefaultConstructor {
  name: string
  kind: number
  constructor(name: string, kind: number)
}

constructor personalizado

Se você quiser definir um constructor personalizado, pode usar #[napi(constructor)] na sua constructor fn no bloco impl da struct.

lib.rs
// Uma estrutura complexa que não pode ser exposta diretamente ao JavaScript.
pub struct QueryEngine {}
 
#[napi(js_name = "QueryEngine")]
pub struct JsQueryEngine {
  engine: QueryEngine,
}
 
#[napi]
impl JsQueryEngine {
  #[napi(constructor)]
  pub fn new() -> Self {
    JsQueryEngine { engine: QueryEngine::new() }
  }
}
index.d.ts
export class QueryEngine {
  constructor()
}
⚠️

NAPI-RS atualmente não suporta private constructor. Seu construtor personalizado deve ser pub em Rust.

Factory

Além do constructor, você também pode definir métodos de fábrica na Class usando #[napi(factory)].

lib.rs
// Uma estrutura complexa que não pode ser exposta diretamente ao JavaScript.
pub struct QueryEngine {}
 
#[napi(js_name = "QueryEngine")]
pub struct JsQueryEngine {
  engine: QueryEngine,
}
 
#[napi]
impl JsQueryEngine {
  #[napi(factory)]
  pub fn with_initial_count(count: u32) -> Self {
    JsQueryEngine { engine: QueryEngine::with_initial_count(count) }
  }
}
index.d.ts
export class QueryEngine {
  static withInitialCount(count: number): QueryEngine
  constructor()
}
⚠️

Se nenhum #[napi(constructor)] for definido na struct, e você tentar criar uma instância (new) da Class em JavaScript, um erro será lançado.

test.mjs
import { QueryEngine } from './index.js'
 
new QueryEngine() // Error: Class contains no `constructor`, cannot create it!

class method

Você pode definir um método de classe JavaScript com #[napi] em um método de struct em Rust.

lib.rs
// Uma estrutura complexa que não pode ser exposta diretamente ao JavaScript.
pub struct QueryEngine {}
 
#[napi(js_name = "QueryEngine")]
pub struct JsQueryEngine {
  engine: QueryEngine,
}
 
#[napi]
impl JsQueryEngine {
  #[napi(factory)]
  pub fn with_initial_count(count: u32) -> Self {
    JsQueryEngine { engine: QueryEngine::with_initial_count(count) }
  }
 
  /// Class method (Método de classe)
  #[napi]
  pub async fn query(&self, query: String) -> napi::Result<String> {
    self.engine.query(query).await
  }
 
  #[napi]
  pub fn status(&self) -> napi::Result<u32> {
    self.engine.status()
  }
}
index.d.ts
export class QueryEngine {
  static withInitialCount(count: number): QueryEngine
  constructor()
  query(query: string) => Promise<string>
  status() => number
}
⚠️

async fn precisa dos recursos napi4 e tokio_rt habilitados.

💡

Qualquer fn em Rust que retorne Result<T> será tratado como T em JavaScript/TypeScript. Se o Result<T> for Err, um erro JavaScript será lançado.

Getter

Defina um getter de classe JavaScript (opens in a new tab) usando #[napi(getter)]. A fn Rust deve ser um método de struct, não uma função associada.

lib.rs
// Uma estrutura complexa que não pode ser exposta diretamente ao JavaScript.
pub struct QueryEngine {}
 
#[napi(js_name = "QueryEngine")]
pub struct JsQueryEngine {
  engine: QueryEngine,
}
 
#[napi]
impl JsQueryEngine {
  #[napi(factory)]
  pub fn with_initial_count(count: u32) -> Self {
    JsQueryEngine { engine: QueryEngine::with_initial_count(count) }
  }
 
  /// Método de classe
  #[napi]
  pub async fn query(&self, query: String) -> napi::Result<String> {
    self.engine.query(query).await
  }
 
  #[napi(getter)]
  pub fn status(&self) -> napi::Result<u32> {
    self.engine.status()
  }
}
index.d.ts
export class QueryEngine {
  static withInitialCount(count: number): QueryEngine
  constructor()
  get status(): number
}

Setter

Defina setter de classe JavaScript (opens in a new tab) usando #[napi(setter)]. A fn Rust deve ser um método de struct, não uma função associada.

lib.rs
// Uma estrutura complexa que não pode ser exposta diretamente ao JavaScript.
pub struct QueryEngine {}
 
#[napi(js_name = "QueryEngine")]
pub struct JsQueryEngine {
  engine: QueryEngine,
}
 
#[napi]
impl JsQueryEngine {
  #[napi(factory)]
  pub fn with_initial_count(count: u32) -> Self {
    JsQueryEngine { engine: QueryEngine::with_initial_count(count) }
  }
 
  /// Método de classe
  #[napi]
  pub async fn query(&self, query: String) -> napi::Result<String> {
    self.engine.query(query).await
  }
 
  #[napi(getter)]
  pub fn status(&self) -> napi::Result<u32> {
    self.engine.status()
  }
 
  #[napi(setter)]
  pub fn count(&mut self, count: u32) {
    self.engine.count = count;
  }
}
index.d.ts
export class QueryEngine {
  static withInitialCount(count: number): QueryEngine
  constructor()
  get status(): number
  set count(count: number)
}

Class como argumento

Class é diferente de Object. Class pode ter métodos Rust e funções associadas nele. Cada campo em Class pode ser mutado em JavaScript.

Portanto a ownership(propriedade) da Class é realmente transferida para o lado do JavaScript enquanto você a está criando. Ela é gerenciada pelo GC do JavaScript e você só pode passá-la de volta passando sua reference.

lib.rs
pub fn accept_class(engine: &QueryEngine) {
  // ...
}
 
pub fn accept_class_mut(engine: &mut QueryEngine) {
  // ...
}
index.d.ts
export function acceptClass(engine: QueryEngine): void
export function acceptClassMut(engine: QueryEngine): void

Atributos de propriedade

Os atributos padrão da propriedade são writable = true, enumerable = true e configurable = true. Você pode controlar os atributos da propriedade através do macro #[napi]:

lib.rs
use napi::bindgen_prelude::*;
use napi_derive::napi;
 
// Uma estrutura complexa que não pode ser exposta diretamente ao 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
  }
}

Neste caso, o método getNum de QueryEngine não é gravável:

main.mjs
import { QueryEngine } from './index.js'
 
const qe = new QueryEngine()
qe.getNum = function () {} // TypeError: Cannot assign to read only property 'getNum' of object '#<QueryEngine>'

Lógica de Finalização personalizada

O NAPI-RS descartará a struct Rust envolvida no objeto JavaScript quando o objeto JavaScript for "garbage collected". Você também pode especificar uma lógica de finalização personalizada para a struct Rust.

lib.rs
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 = vec![0; (width * height * 4) as usize];
    let inner_size = inner.len();
    env.adjust_external_memory(inner_size as i64)?;
    Ok(Self {
      width,
      height,
      inner,
    })
  }
}
 
impl ObjectFinalize for CustomFinalize {
  fn finalize(self, mut env: Env) -> Result<()> {
    env.adjust_external_memory(-(self.inner.len() as i64))?;
    Ok(())
  }
}

Primeiro, você pode definir o atributo custom_finalize no macro #[napi], e o NAPI-RS não gerará o ObjectFinalize padrão para a estrutura Rust.

Então, você pode implementar o ObjectFinalize você mesmo para a Rust struct.

Neste caso, a estrutura CustomFinalize aumenta a memória externa no constructor e a diminui em fn finalize.

instance of

Há um fn instance_of em todas as classes #[napi]:

lib.rs
use napi::bindgen_prelude::*;
use napi_derive::napi;
 
#[napi]
pub struct NativeClass {}
 
#[napi]
pub fn is_native_class_instance(env: Env, value: Unknown) -> Result<bool> {
  NativeClass::instance_of(env, value)
}
main.mjs
import { NativeClass, isNativeClassInstance } from './index.js'
 
const nc = new NativeClass()
console.log(isNativeClassInstance(nc)) // true
console.log(isNativeClassInstance(1)) // false