Reference
/ WeakReference
In some scenarios, you may want to extend the lifetime of the Object
to the Rust
side. You can use Reference
to hold a reference to this object.
Both the Reference
and WeakReference
are not Send
, because of the drop
of the Reference
must be called in the same thread as the Reference
is
created.
Reference
Reference
is a wrapper of the napi_ref
.
NAPI-RS calls the napi_wrap
function to wrap the Rust struct
with the class instance object when creating the class instance. There is a napi_ref
that is created by the napi_wrap
. Reference
holds the napi_ref
so you can always access the underlying struct
reference before the underlying napi_ref
is deleted.
For example:
pub struct Repository {
dir: String,
}
impl Repository {
fn remote(&self) -> Remote {
Remote { inner: self }
}
}
pub struct Remote<'repo> {
inner: &'repo Repository,
}
impl<'repo> Remote<'repo> {
fn name(&self) -> String {
"origin".to_owned()
}
}
The Repository
struct below is easy to create a #[napi]
Class around, because it doesn't contain any lifetime in the definition.
However, the Remote<'repo>
struct cannot have a #[napi]
Class created around it, because it has a 'repo
lifetime.
With the Reference
API, you can create a 'static
lifetime struct, which means the created struct will live as long as you can access it in your Rust
code.
Like the Env
and This
, the Reference
is injected into parameters of #[napi]
functions.
use napi::bindgen_prelude::*;
pub struct Repository {
dir: String,
}
impl Repository {
fn remote(&self) -> Remote {
Remote { inner: self }
}
}
pub struct Remote<'repo> {
inner: &'repo Repository,
}
impl<'repo> Remote<'repo> {
fn name(&self) -> String {
"origin".to_owned()
}
}
#[napi]
pub struct JsRepo {
inner: Repository,
}
#[napi]
impl JsRepo {
#[napi(constructor)]
pub fn new(dir: String) -> Self {
JsRepo {
inner: Repository { dir },
}
}
#[napi]
pub fn remote(&self, reference: Reference<JsRepo>, env: Env) -> Result<JsRemote> {
Ok(JsRemote {
inner: reference.share_with(env, |repo| Ok(repo.inner.remote()))?,
})
}
}
#[napi]
pub struct JsRemote {
inner: SharedReference<JsRepo, Remote<'static>>,
}
#[napi]
impl JsRemote {
#[napi]
pub fn name(&self) -> String {
self.inner.name()
}
}
As you can see, the injected Reference<JsRepo>
has the share_with
fn on it, which can be used to create a 'static
lifetime JsRepo
struct in the closure.
The created Reference
will make Node.js hold the JsRepo
instance until all the references are dropped.
WeakReference
WeakReference
is very useful when you are creating circular references.
use std::{cell::RefCell, rc::Rc};
use napi::bindgen_prelude::*;
use napi_derive::napi;
pub struct OwnedStyleSheet {
rules: Vec<String>,
}
#[napi]
pub struct CSSRuleList {
owned: Rc<RefCell<OwnedStyleSheet>>,
parent: WeakReference<CSSStyleSheet>,
}
#[napi]
impl CSSRuleList {
#[napi]
pub fn get_rules(&self) -> Vec<String> {
self.owned.borrow().rules.to_vec()
}
#[napi(getter)]
pub fn parent_style_sheet(&self) -> WeakReference<CSSStyleSheet> {
self.parent.clone()
}
#[napi(getter)]
pub fn name(&self, env: Env) -> Result<Option<String>> {
Ok(
self
.parent
.upgrade(env)?
.map(|stylesheet| stylesheet.name.clone()),
)
}
}
#[napi]
pub struct CSSStyleSheet {
name: String,
inner: OwnedStyleSheet,
rules: Option<Reference<CSSRuleList>>,
}
#[napi]
impl CSSStyleSheet {
#[napi(constructor)]
pub fn new(name: String, rules: Vec<String>) -> Result<Self> {
let inner = OwnedStyleSheet { rules };
Ok(CSSStyleSheet {
name,
inner,
rules: None,
})
}
#[napi(getter)]
pub fn rules(
&mut self,
env: Env,
reference: Reference<CSSStyleSheet>,
) -> Result<Reference<CSSRuleList>> {
if let Some(rules) = &self.rules {
return rules.clone(env);
}
let rules = CSSRuleList::into_reference(
CSSRuleList {
owned: self.inner.clone(),
parent: reference.downgrade(),
},
env,
)?;
self.rules = Some(rules.clone(env)?);
Ok(rules)
}
}
In the example above, the CSSRuleList
struct is created with a WeakReference<CSSStyleSheet>
as its parent
field. Because the CSSRuleList
is created by the CSSStyleSheet
in this case, the CSSStyleSheet
instance is a circular reference to the CSSRuleList
instance it created.
The WeakReference
will not increase the reference count of the raw Object, so the upgrade
function of WeakReference
may return None
if the raw Object is dropped.
JavaScript Value Reference
ObjectRef
The SymbolRef
must be return back to the JavaScript
side, or call the
unref
on it manually. Otherwise, the Symbol
under the hood will never be
garbage collected.
In the example below, we create the ObjectRef
in the constructor and use it later in the getOptions
method.
use napi::bindgen_prelude::*;
use napi_derive::napi;
#[napi]
pub struct NativeClass {
options: ObjectRef,
}
#[napi]
impl NativeClass {
#[napi]
pub fn new(options: Object) -> Result<Self> {
Ok(Self {
options: options.create_ref()?,
})
}
#[napi]
pub fn get_options(&self, env: &Env) -> Result<Object> {
self.options.get_value(env)
}
}
⬇️ ⬇️ ⬇️ ⬇️ ⬇️ ⬇️ ⬇️ ⬇️ ⬇️
import { NativeClass } from './index.js'
const nativeClass = new NativeClass({
name: 'John',
age: 30,
})
const options = nativeClass.getOptions() // { name: 'John', age: 30 }
SymbolRef
The SymbolRef
must be return back to the JavaScript
side, or call the
unref
on it manually. Otherwise, the Symbol
under the hood will never be
garbage collected.
The SymbolRef
API is basically the same as the ObjectRef
.
use napi::{SymbolRef, bindgen_prelude::*};
use napi_derive::napi;
#[napi]
pub fn create_symbol_ref(env: &Env) -> Result<SymbolRef> {
Symbol::new("NAPI_RS_SYMBOL")
.into_js_symbol(env)?
.create_ref()
}
FunctionRef
The FunctionRef
is not Send
because it needs to be dropped in the same
thread as the FunctionRef
is created.
FunctionRef
can be created on the Function
directly.
In the example below, if you try to call Function
in the Promise.finally
callback, you will encounter a lifetime error:
use napi::bindgen_prelude::*;
use napi_derive::napi;
#[napi]
pub fn promise_finally_callback(
mut promise: PromiseRaw<()>,
on_finally: Function<()>,
) -> Result<()> {
// ❌ compile Error
// borrowed data escapes outside of function
// `on_finally` escapes the function body here
// lib.rs(7, 3): `on_finally` is a reference that is only valid in the function body
// lib.rs(7, 3): has type `napi::bindgen_prelude::Function<'1, ()>`
promise.finally(|env| {
on_finally.call(());
Ok(())
})?;
Ok(())
}
You can create the FunctionRef
and borrow back the Function
from it later:
use napi::bindgen_prelude::*;
use napi_derive::napi;
#[napi]
pub fn promise_finally_callback(
mut promise: PromiseRaw<()>,
on_finally: Function<(), ()>,
) -> Result<()> {
let on_finally_ref = on_finally.create_ref()?;
promise.finally(move |env| {
let on_finally = on_finally_ref.borrow_back(&env)?;
on_finally.call(())?;
Ok(())
})?;
Ok(())
}
⬇️ ⬇️ ⬇️ ⬇️ ⬇️ ⬇️ ⬇️ ⬇️ ⬇️ ⬇️
import { promiseFinallyCallback } from './index.js'
promiseFinallyCallback(Promise.resolve(), () => {
console.log('finally')
})
ExternalRef
The ExternalRef
is not Send
because it needs to be dropped in the same
thread as the ExternalRef
is created.
ExternalRef
holds the napi_ref
to the object thats created by the napi_create_external
function.
It's basically the same as the ObjectRef
API:
use napi::bindgen_prelude::*;
use napi_derive::napi;
#[napi]
pub fn create_external_ref(env: &Env, size: u32) -> Result<ExternalRef<u32>> {
let external = External::new(size).into_js_external(env)?;
external.create_ref()
}