neon-bindings/neon · error

Cannot combine `async fn` with `#[neon(async)]` attribute

Error message

Cannot combine `async fn` with `#[neon(async)]` attribute

What it means

Compile-time error from Neon's `#[neon]` class macro in `validate_method_attributes`. It fires when a method is declared `async fn` (macro kind `AsyncFn`) and is also marked with the `#[neon(async)]` attribute (kind `Async`). Both spellings request the same async treatment, so combining them is redundant and ambiguous — the macro rejects the declaration instead of guessing.

Solutions

  1. Remove the `#[neon(async)]` attribute and keep `async fn`.
  2. Alternatively, keep `#[neon(async)]` and drop the `async` keyword if the intended style is attribute-driven (verify against your Neon version's supported forms).
  3. Check the Neon docs for the canonical way to declare async methods in your version and use only one mechanism.

Example fix

// before
#[neon(async)]
async fn run(&self, mut cx: Channel) -> JsResult<JsUndefined> {
    ...
}

// after
async fn run(&self, mut cx: Channel) -> JsResult<JsUndefined> {
    ...
}
Defensive patterns

Strategy: validation

Validate before calling

// Reject declarations that both use `async fn` and #[neon(async)].
fn validate_async_spelling(has_async_fn: bool, has_neon_async_attr: bool) -> Result<(), String> {
    if has_async_fn && has_neon_async_attr {
        return Err("use either `async fn` or `#[neon(async)]`, not both".into());
    }
    Ok(())
}

Type guard

fn is_unambiguous_async(has_async_fn: bool, has_neon_async_attr: bool) -> bool {
    has_async_fn ^ has_neon_async_attr
}

Prevention

When it happens

Trigger: Annotating a method that is already declared `async fn` with `#[neon(async)]`, e.g. `#[neon(async)] async fn run(&self, ...)`. (Note: with this macro implementation both `Kind::AsyncFn` and `Kind::Async` must be present simultaneously to trigger the error.)

Common situations: Adding `#[neon(async)]` for explicitness on an already-`async fn` method; copy-pasting attributes from a non-async method when converting it to `async fn`; mixing examples from different Neon versions that use different async spellings.

Related errors


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

Appendix: source

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

    ident == "Channel"
}

// Extract the identifier from the last segment of a type's path
fn type_path_ident(ty: &syn::Type) -> Option<&syn::Ident> {
    let segment = match ty {
        syn::Type::Path(ty) => ty.path.segments.last()?,
        _ => return None,
    };

    Some(&segment.ident)
}

// Validate method attributes for common errors and conflicts
fn validate_method_attributes(meta: &meta::Meta, sig: &syn::Signature) -> syn::Result<()> {
    // Check for conflicting async attributes
    if matches!(meta.kind, meta::Kind::AsyncFn) && matches!(meta.kind, meta::Kind::Async) {
        return Err(syn::Error::new(
            sig.span(),
            "Cannot combine `async fn` with `#[neon(async)]` attribute",
        ));
    }

    // Check for async + task conflict
    if matches!(meta.kind, meta::Kind::AsyncFn | meta::Kind::Async)
        && matches!(meta.kind, meta::Kind::Task)
    {
        return Err(syn::Error::new(
            sig.span(),
            "Cannot combine async method with `#[neon(task)]` attribute",
        ));
    }

    // Validate that async fn and task methods take self by value
    if matches!(meta.kind, meta::Kind::AsyncFn | meta::Kind::Task) {
        if let Some(syn::FnArg::Receiver(receiver)) = sig.inputs.first() {

View on GitHub (pinned to 38960e4381)