Docs
Compat mode
Concepts
Env

Env

From Node.js documents

napi_env is used to represent a context that the underlying N-API implementation can use to persist VM-specific state. This structure is passed to native functions when they're invoked, and it must be passed back when making N-API calls. Specifically, the same napi_env that was passed in when the initial native function was called must be passed to any subsequent nested N-API calls. Caching the napi_env for the purpose of general reuse, and passing the napi_env between instances of the same addon running on different Worker threads is not allowed. The napi_env becomes invalid when an instance of a native addon is unloaded. Notification of this event is delivered through the callbacks given to napi_add_env_cleanup_hook and napi_set_instance_data.

Env is a Rust layer abstraction over napi_env. When writing Node.js Add-ons in C/C++, any N-API call goes through napi_env, and similarly in Rust, any API call goes through the Env data structure. In napi-rs, there are four ways to interact with Env.

1. CallContext in js_function

js_function is a way to define a JavaScript function in Rust:

#[js_function(1)]
fn hello(ctx: CallContext) -> Result<JsString> {
  let argument_one = ctx.get::<JsString>(0)?.into_utf8()?;
  ctx.env.create_string_from_std(format!("{} world!", argument_one.as_str()?))
}

There is env field in CallContext struct.

Env functionality can be found in the Env documentation (opens in a new tab).

2. contextless_function

contextless_function is very similar to js_function, except that contextless_function will not get the Context of this JavaScript function. Which means in contextless_function you can not get arguments, this or any other function context relational data. But it will more perform more efficiently so it can be used in performance sensitive scenarios.

#[contextless_function]
fn just_return_hello(env: Env) -> ContextlessResult<JsString> {
  env.create_string("hello").map(Some)
}

3. resolve/reject method in Task trait

The Task trait is a little complicated, see it's docs for a dive deep into it.

Env will be passed into your resolve and reject methods. The two methods will be called in main thread when async task in the thread pool was completed.

struct PlusOneAsync(u32);
 
impl Task for PlusOneAsync {
  type Output = u32;
  type JsValue = JsNumber;
 
  fn compute(&mut self) -> Result<Self::Output> {
    Ok(self.0 + 1)
  }
 
  fn resolve(self, env: Env, output: Self::Output) -> Result<Self::JsValue> {
    env.create_uint32(output)
  }
}
 
#[js_function(1)]
fn async_plus_one(ctx: CallContext) -> Result<JsNumber> {
  let input_number: u32 = ctx.get::<JsNumber>(0)?.try_into()?;
  let task = PlusOneAsync(input_number);
  ctx.env.spawn(task).map(|t| t.promise_object())
}
asyncPlusOne(1).then((result) => {
  console.log(result) // 2
})

4. ThreadSafeCallContext in Thread safe function

#[js_function(1)]
fn thread_safe_function(ctx: CallContext) -> Result<JsUndefined> {
  let callback: JsFunction = ctx.get(0)?;
  let tsfn = ctx.create_threadsafe_function(&callback, 0, |ctx: ThreadSafeCallContext<u32>| {
    ctx.env.create_uint32(ctx.value).map(|js_value| vec![js_value])
  })?;
 
  std::thread::spawn(move || {
    tsfn.call(Ok(1), ThreadsafeFunctionCallMode::NonBlocking);
  });
 
  ctx.env.get_undefined()
}
threadSafeFunction((err, value) => {
  if (err) {
    console.error(err)
    process.exit(1)
  }
  console.log(value) // 1
})