node.jsrustbcryptnode-native-addon

Type Mismatch Error When Wrapping Bcrypt Hash Function in Rust with N-API


I'm working on a project where I need to wrap the bcrypt hash function using N-API in Rust. I've written the following code, but I'm encountering type mismatch errors during compilation. I'd appreciate any guidance on how to fix these issues or improve my approach.

use bcrypt::hash;
use napi::{bindgen_prelude::*, Env, JsObject, JsString, Result, Task};
use napi_derive::napi;

struct BcryptTask {
    password: String,
    cost: u32,
}

impl Task for BcryptTask {
    type Output = String;
    type JsValue = JsString;

    fn compute(&mut self) -> Result<Self::Output> {
        hash(&self.password, self.cost)
            .map_err(|e| Error::new(Status::GenericFailure, format!("Bcrypt error: {:?}", e)))
    }

    fn resolve(&mut self, env: Env, output: Self::Output) -> Result<JsString> {
        env.create_string(&output)
    }
}

#[napi]
fn init(mut exports: JsObject) -> Result<()> {
    exports.create_named_method("hashPassword", hash_password_wrapper)?;
    Ok(())
}

#[napi]
fn hash_password_wrapper(env: Env, password: String, cost: u32) -> Result<JsObject> {
    let task = BcryptTask { password, cost };
    env.spawn(task)
}

I get the following errors :

> cargo build
   Compiling rust_for_node v0.1.0 (C:\rust_for_node)
error[E0308]: mismatched types
   --> src\lib.rs:26:49
    |
26  |     exports.create_named_method("hashPassword", hash_password_wrapper)?;
    |             -------------------                 ^^^^^^^^^^^^^^^^^^^^^ expected "C" fn, found "Rust" fn
    |             |
    |             arguments to this method are incorrect
    |
    = note: expected fn pointer `unsafe extern "C" fn(*mut napi_env__, *mut napi_callback_info__) -> *mut napi_value__`
                  found fn item `fn(Env, std::string::String, u32) -> Result<JsObject, napi::Error> {hash_password_wrapper}`
note: method defined here
   --> C:\Users\xxx\.cargo\registry\src\index.crates.io-6f17d22bba15001f\napi-2.16.2\src\js_values\mod.rs:667:1
    |
667 | impl_object_methods!(JsObject);
    | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
    = note: this error originates in the macro `impl_object_methods` (in Nightly builds, run with -Z macro-backtrace for more info)

error[E0308]: mismatched types
  --> src\lib.rs:33:5
   |
31 | fn hash_password_wrapper(env: Env, password: String, cost: u32) -> Result<JsObject> {
   |                                                                    ---------------- expected `Result<JsObject, napi::Error>` because of return type
32 |     let task = BcryptTask { password, cost };
33 |     env.spawn(task)
   |     ^^^^^^^^^^^^^^^ expected `Result<JsObject, Error>`, found `Result<AsyncWorkPromise, ...>`
   |
   = note: expected enum `Result<JsObject, _>`
              found enum `Result<AsyncWorkPromise, _>`

For more information about this error, try `rustc --explain E0308`.
error: could not compile `rust_for_node` (lib) due to 2 previous errors

Questions:

How can I resolve the mismatched types error when using exports.create_named_method? It seems to expect an "extern C" function. For the env.spawn(task) call, it returns an AsyncWorkPromise instead of JsObject. How should I handle this to meet the function's expected return type?

Any help or suggestions on how to correctly implement this in Rust would be greatly appreciated!


Solution

  • You need to call get_c_callback() with the function generated by napi:

    #[napi]
    fn init(mut exports: JsObject) -> Result<()> {
        exports.create_named_method("hashPassword", get_c_callback(hash_password_wrapper)?)?;
        Ok(())
    }
    

    However, note that napi automatically exports functions annotated with #[napi], and so you don't need to add a function for that (you can also change its name with #[napi(js_name = "hashPassword")]).