class

Attribute Macro class 

#[class]
Available on crate feature napi-6 only.
Expand description

Create a Neon class from a Rust datatype

The #[neon::class] attribute can be applied to an impl block to create a JavaScript class that wraps a Rust struct (or enum). The impl block specifies a constructor method named new to create instances of the struct, which Neon automatically attaches to instances of the JavaScript class during object construction.

Typically, Neon classes are exported from their addon, which can be done with the #[neon::export(class)] attribute.

§Example

#[derive(Clone)]
pub struct User {
    username: String,
    first_name: String,
    last_name: String,
}

#[neon::export(class)]
impl User {
    pub fn new(username: String, first_name: String, last_name: String) -> Self {
        Self { username, first_name, last_name }
    }

    pub fn to_string(&self) -> String {
        format!("[object User:{}]", self.username)
    }
}

§Constructor

Classes must have exactly one constructor method named new. The constructor takes the class parameters and returns Self. Constructor arguments can be any type that implements TryFromJs.

pub struct Person {
    name: String,
    age: u32,
}

#[neon::class]
impl Person {
    pub fn new(name: String, age: u32) -> Self {
        Self { name, age }
    }
}

§Methods

Class methods can have either &self or &mut self as their first parameter. Methods can take any type that implements TryFromJs and return any type that implements TryIntoJs.

pub struct Counter {
    value: i32,
}

#[neon::class]
impl Counter {
    pub fn new(initial: i32) -> Self {
        Self { value: initial }
    }

    pub fn increment(&mut self) -> i32 {
        self.value += 1;
        self.value
    }

    pub fn get(&self) -> i32 {
        self.value
    }
}

§Reference Parameters

Methods can accept class instances by reference (&T) or mutable reference (&mut T) to avoid cloning when passing instances between methods. The type must implement TryFromJsRef for immutable references or TryFromJsRefMut for mutable references. The #[neon::class] macro automatically implements these traits for all class types.

#[derive(Clone)]
pub struct Point {
    x: f64,
    y: f64,
}

#[neon::class]
impl Point {
    pub fn new(x: f64, y: f64) -> Self {
        Self { x, y }
    }

    // Accept another Point by immutable reference (no clone)
    pub fn distance(&self, other: &Self) -> f64 {
        let dx = self.x - other.x;
        let dy = self.y - other.y;
        (dx * dx + dy * dy).sqrt()
    }

    // Accept another Point by mutable reference (no clone)
    pub fn swap_coordinates(&mut self, other: &mut Self) {
        std::mem::swap(&mut self.x, &mut other.x);
        std::mem::swap(&mut self.y, &mut other.y);
    }

    // Accept another Point by value (clones the instance)
    pub fn midpoint(&self, other: Self) -> Self {
        Self {
            x: (self.x + other.x) / 2.0,
            y: (self.y + other.y) / 2.0,
        }
    }
}

From JavaScript:

const p1 = new Point(0, 0);
const p2 = new Point(3, 4);

console.log(p1.distance(p2));    // 5 (no cloning)

p1.swapCoordinates(p2);          // Mutates both points
console.log(p1.x);               // 3
console.log(p2.x);               // 0

const mid = p1.midpoint(p2);     // Clones p2

When to use references:

  • Use &T when you only need to read from the instance
  • Use &mut T when you need to mutate the instance
  • Use T (by value) when the semantics require taking ownership

Note that reference parameters still use RefCell internally, so runtime borrow checking applies. Attempting to borrow the same instance both mutably and immutably (or multiple times mutably) will panic.

§Finalizer

Classes can implement a finalize method to perform cleanup when the JavaScript object is garbage collected. The finalize method takes ownership of the class instance and is called when the object is no longer reachable from JavaScript.

pub struct Logger {
    name: String,
}

#[neon::class]
impl Logger {
    pub fn new(name: String) -> Self {
        Self { name }
    }

    pub fn finalize<'cx, C: Context<'cx>>(self, _cx: &mut C) {
        println!("Logger {} is being finalized", self.name);
    }
}

§Mutability and Borrow Checking

Neon classes use RefCell internally to allow mutation through &mut self methods while maintaining JavaScript’s shared ownership semantics. This means that borrow checking happens at runtime, not compile time, and violating Rust’s borrowing rules will cause a panic.

Important: You cannot call a method that requires &mut self while another method is borrowing the instance (even with &self). This includes:

  • Reentrancy from JavaScript callbacks
  • Nested method calls on the same instance

For complex scenarios involving callbacks or shared mutable state across threads, consider using additional interior mutability types like Arc<Mutex<T>> for the specific fields that need it.

§Method Attributes

Methods support the same attributes as #[neon::export] functions, including json, task, async, context, this, and name, and may be fallible by returning Result types.

§JSON Methods
pub struct DataProcessor;

#[neon::class]
impl DataProcessor {
    pub fn new() -> Self {
        Self
    }

    #[neon(json)]
    pub fn process_data(&self, items: Vec<String>) -> Vec<String> {
        items.into_iter().map(|s| s.to_uppercase()).collect()
    }
}
§Async Methods

Methods declared with async fn are automatically detected and exported as async. The macro automatically clones the instance before calling the method. Because Rust’s async fn captures self into the Future (which must be 'static), the method must take self by value (not &self or &mut self) and the struct must implement Clone. Any shared mutable state should use types like Arc<Mutex<T>> for thread-safe interior mutability.

#[neon::class]
impl AsyncWorker {
    pub fn new() -> Self {
        Self {
            counter: Arc::new(Mutex::new(0)),
        }
    }

    // Must take `self` by value; the macro clones the instance automatically
    pub async fn fetch_data(self, url: String) -> String {
        // Simulate async work
        let mut count = self.counter.lock().unwrap();
        *count += 1;
        format!("Data from {} (request #{})", url, count)
    }
}
§Synchronous Setup

For more control over async behavior, use #[neon(async)] with a method that returns a Future. This allows synchronous setup on the JavaScript main thread. With this approach, the method takes &self and you are responsible for cloning any data needed to make the returned Future 'static.

#[neon::class]
impl AsyncWorker {
    #[neon(async)]
    pub fn process_data(&self, data: String) -> impl Future<Output = String> + 'static {
        println!("Setup on main thread");
        // Clone any instance data you need for the Future
        let value = self.value.clone();
        async move {
            format!("{}: {}", value, data.to_uppercase())
        }
    }
}
§Task Methods

Methods can be executed on Node’s worker pool using the task attribute. The macro automatically clones the instance before moving it to the worker thread, so the struct must implement Clone. Like async fn methods, task methods must take self by value (not &self or &mut self) to make it clear that they operate on a clone.

#[derive(Clone)]
pub struct CpuWorker {
    multiplier: u32,
}

#[neon::class]
impl CpuWorker {
    pub fn new(multiplier: u32) -> Self {
        Self { multiplier }
    }

    // Must take `self` by value; the macro clones the instance automatically
    #[neon(task)]
    pub fn heavy_computation(self, iterations: u32) -> u32 {
        (0..iterations).map(|i| i * self.multiplier).sum()
    }
}
§Method Naming

Like #[neon::export] functions, method names are converted from snake_case to camelCase. Custom names can be specified with the name attribute:

pub struct Label {
    data: String,
}

#[neon::class]
impl Label {
    pub fn new() -> Self {
        Self { data: String::new() }
    }

    #[neon(name = "trimStart")]
    pub fn trim_leading(&self) -> String {
        self.data.trim_start().to_string()
    }
}
§Fallible Methods

Methods can return Result types to throw JavaScript exceptions, just like #[neon::export] functions.

pub struct User {
    name: String,
}

#[neon::class]
impl User {
    pub fn new(name: String) -> Self {
        Self { name }
    }

    pub fn get_name(&self) -> String {
        self.name.clone()
    }

    pub fn set_name(&mut self, name: String) -> Result<(), &'static str> {
        if name.is_empty() {
            return Err("Name cannot be empty");
        }

        self.name = name;

        Ok(())
    }
}

§Constructor Attributes

Constructor methods support the json and context attributes and may be fallible as well.

pub struct Argv {
    pub args: Vec<String>,
}

#[neon::class]
impl Argv {
    // context attribute is inferred automatically
    #[neon(json)]
    pub fn new(cx: &mut Cx, args: Option<Vec<String>>) -> NeonResult<Self> {
        let args = if let Some(args) = args { args } else {
            let Json(args): Json<Vec<String>> = cx
                .global::<JsObject>("process")?
                .prop(cx, "argv")
                .get()?;
            args
        };
        Ok(Self { args } )
    }

    pub fn len(&self) -> u32 {
        self.args.len() as u32
    }

    pub fn get(&self, index: u32) -> Option<String> {
        self.args.get(index as usize).cloned()
    }
}

§Const Properties

Classes can expose Rust constants as static, immutable properties on the JavaScript class:

pub struct MathConstants;

#[neon::class]
impl MathConstants {
    const PI: f64 = 3.14159;
    const VERSION: u32 = 1;

    #[neon(name = "maxValue")]
    const MAX_VALUE: f64 = f64::MAX;

    #[neon(json)]
    const DEFAULT_SETTINGS: &'static [&'static str] = &["feature1", "feature2"];

    pub fn new() -> Self {
        Self
    }
}

From JavaScript:

console.log(MathConstants.PI);               // 3.14159
console.log(MathConstants.maxValue);         // 1.7976931348623157e+308
console.log(MathConstants.DEFAULT_SETTINGS); // ["feature1", "feature2"]

Const properties support the same attributes as globals: name for custom naming and json for automatic JSON serialization. Properties are immutable from JavaScript.

§Context and This Parameters

Methods can access the JavaScript runtime context and the JavaScript object wrapper:

pub struct Interactive {
    data: String,
}

#[neon::class]
impl Interactive {
    pub fn new(data: String) -> Self {
        Self { data }
    }

    // Method with context parameter
    pub fn create_object<'cx>(
        &self,
        cx: &mut FunctionContext<'cx>,
    ) -> JsResult<'cx, JsObject> {
        let obj = cx.empty_object();
        let value = cx.string(&self.data);
        obj.set(cx, "data", value)?;
        Ok(obj)
    }

    // Method with this parameter (access to JS object)
    pub fn inspect_this(&self, this: Handle<JsObject>) -> String {
        format!("JS object available: {}", self.data)
    }
}

§Working with Class Instances

Methods can accept and return instances of the same class directly. When a class instance is passed as a parameter or returned from a method, it is automatically cloned from (or into) the internal RefCell storage, so the struct must implement Clone.

#[derive(Clone)]
pub struct Point {
    x: f64,
    y: f64,
}

#[neon::class]
impl Point {
    pub fn new(x: f64, y: f64) -> Self {
        Self { x, y }
    }

    pub fn distance(&self, other: Self) -> f64 {
        let dx = self.x - other.x;
        let dy = self.y - other.y;
        (dx * dx + dy * dy).sqrt()
    }

    pub fn midpoint(&self, other: Self) -> Self {
        Self {
            x: (self.x + other.x) / 2.0,
            y: (self.y + other.y) / 2.0,
        }
    }
}

From JavaScript, you can call these methods with other instances of the same class:

const p1 = new Point(0, 0);
const p2 = new Point(3, 4);
console.log(p1.distance(p2)); // 5
const midpoint = p1.midpoint(p2); // Point { x: 1.5, y: 2 }

§Export Shorthand

Use #[neon::export(class)] to combine class definition with automatic module export:

pub struct AutoExported {
    value: u32,
}

// Combines #[neon::class] with automatic export
#[neon::export(class)]
impl AutoExported {
    pub fn new(value: u32) -> Self {
        Self { value }
    }
}

Like other exports, classes can be exported with custom names:

pub struct InternalPoint {
    x: f64,
    y: f64,
}

// Export as "Point" instead of "InternalPoint"
#[neon::export(class(name = "Point"))]
impl InternalPoint {
    pub fn new(x: f64, y: f64) -> Self {
        Self { x, y }
    }

    pub fn distance_from_origin(&self) -> f64 {
        (self.x * self.x + self.y * self.y).sqrt()
    }
}

It’s also possible to distinguish between the class name and export name:

pub struct RustPoint {
    x: f64,
    y: f64,
}

// Export as "Point" but with Point.name === "NeonPoint"
#[neon::export(class(name = "NeonPoint"), name = "Point")]
impl RustPoint {
    pub fn new(x: f64, y: f64) -> Self {
        Self { x, y }
    }
}

§Error Handling

Methods can return Result types to throw JavaScript exceptions, just like #[neon::export] functions:

pub struct FileReader;

#[neon::class]
impl FileReader {
    pub fn new() -> Self {
        Self
    }

    pub fn read_file(&self, path: String) -> Result<String, Error> {
        std::fs::read_to_string(path).map_err(Error::from)
    }
}

§Class Trait

The #[neon::class] macro automatically implements the Class trait for the struct. This trait can be used to access the constructor function at runtime.

use neon::object::Class;

let constructor = Point::constructor(cx)?;
constructor
    .prop(cx, "ORIGIN")
    .set(Point::new(0.0, 0.0))?;