neon-bindings/neon · error

Only one `new` constructor is allowed in a class.

Error message

Only one `new` constructor is allowed in a class.

What it means

group_class_items in crates/neon-macros/src/class/mod.rs walks the items of a `#[impl]` class block and collects the constructor. Because a JavaScript class can only have one `new`, Neon permits at most one function named `new` per class; encountering a second one while a constructor was already recorded produces this error at the duplicate item's span.

Solutions

  1. Keep exactly one `new` constructor in the class impl
  2. Move extra logic into private helper functions with different names and call them from the single constructor
  3. If you need multiple construction paths, accept different argument shapes in the one constructor (e.g. an options object) or provide named static factory methods marked `#[method]`
  4. Rename any non-constructor helper that happens to be called `new`

Example fix

// before
impl MyCoord {
    #[constructor]
    fn new(cx: &mut FunctionContext) -> JsResult<JsMyCoord> { ... }
    #[constructor]
    fn new_from_xy(cx: &mut FunctionContext) -> JsResult<JsMyCoord> { ... }
}
// after
impl MyCoord {
    #[constructor]
    fn new(cx: &mut FunctionContext) -> JsResult<JsMyCoord> { ... }
    #[method]
    fn from_xy(cx: &mut FunctionContext) -> JsResult<JsMyCoord> { ... }
}
Defensive patterns

Strategy: validation

Validate before calling

fn ensure_single_constructor(items: &[syn::ImplItem]) -> Result<(), String> {
    let count = items.iter().filter(|i| matches!(i, syn::ImplItem::Fn(f) if f.sig.ident == "new")).count();
    if count > 1 { Err(format!("{} constructors named `new` found", count)) } else { Ok(()) }
}

Prevention

When it happens

Trigger: An `impl` block for a Neon class contains two or more methods named `new` (e.g. overloads or a private helper named `new`) — Rust has no overloading, so both are seen as constructors.

Common situations: Trying to emulate constructor overloading with multiple `new` fns of different parameter lists; a helper function accidentally named `new`; merge conflicts that duplicate the constructor block.

Related errors


AI-assisted analysis of neon-bindings/neon@38960e4381 (2026-09-13). Data as JSON: /api/errors/19d69cea095cdda0. Report an issue: GitHub.

Appendix: source

Thrown at crates/neon-macros/src/class/mod.rs:790

    })
}

fn group_class_items(items: Vec<syn::ImplItem>) -> Result<ClassItems, syn::Error> {
    let mut consts = Vec::new();
    let mut fns = Vec::new();
    let mut constructor = None;
    let mut has_finalizer = false;

    for item in items {
        match item {
            syn::ImplItem::Const(c) => consts.push(c),
            syn::ImplItem::Fn(f) => {
                // Check if the function is a constructor
                if f.sig.ident == "new" {
                    if constructor.is_some() {
                        let span = syn::spanned::Spanned::span(&f);
                        let msg = "Only one `new` constructor is allowed in a class.";
                        return Err(syn::Error::new(span, msg));
                    }
                    constructor = Some(f);
                    continue; // Skip adding to fns
                } else if f.sig.ident == "finalize" {
                    if has_finalizer {
                        let span = syn::spanned::Spanned::span(&f);
                        let msg = "Only one `finalize` method is allowed in a class.";
                        return Err(syn::Error::new(span, msg));
                    }
                    has_finalizer = true;
                    continue; // Skip adding to fns
                }
                fns.push(f)
            }
            _ => {
                let span = syn::spanned::Spanned::span(&item);
                let msg = "`neon::class` can only contain `const` and `fn` items.";
                return Err(syn::Error::new(span, msg));

View on GitHub (pinned to 38960e4381)