neon-bindings/neon · error

Expected an owned `Channel` instead of a context reference.

Error message

Expected an owned `Channel` instead of a context reference.

What it means

This is a compile-time error from Neon's `#[neon]` class macro. When an `async fn` method (or one forced into channel mode by `#[neon(context)]`) declares its context-like first argument as a reference to a context type (e.g. `&mut Cx` or `&mut FunctionContext`), the macro rejects it: async methods must take an owned `Channel`, not a borrowed context, because the context cannot live across an await point. The macro checks the second argument (after `&self`) in `check_channel` and produces this error when that argument is a `Type::Reference` whose element is a context type.

Solutions

  1. Change the parameter to an owned `Channel`: `async fn foo(&self, mut cx: Channel)`.
  2. If you only need synchronous access to the context, make the method non-async and keep `&mut FunctionContext`.
  3. Use `cx.channel()` from a synchronous method to schedule async work instead of holding a context in an async fn.

Example fix

// before
#[neon]
impl Greeter {
    async fn greet(&self, cx: &mut FunctionContext) -> JsResult<JsString> {
        ...
    }
}

// after
#[neon]
impl Greeter {
    async fn greet(&self, mut cx: Channel) -> JsResult<JsString> {
        ...
    }
}
Defensive patterns

Strategy: validation

Validate before calling

// Before compiling, audit exported Neon class methods:
// every `async fn` method must take an owned `Channel`, not `&mut FunctionContext`/`&mut Cx`.
fn validate_async_method_sig(is_async: bool, second_arg_ty: &str) -> Result<(), String> {
    if is_async && (second_arg_ty.starts_with("&mut FunctionContext")
        || second_arg_ty.starts_with("&mut Cx")) {
        return Err(format!(
            "async method must take an owned `Channel`, found `{}`", second_arg_ty
        ));
    }
    Ok(())
}

Type guard

fn is_owned_channel(ty: &str) -> bool {
    ty == "Channel" || ty.ends_with("::Channel")
}

Prevention

When it happens

Trigger: Declaring an exported Neon class method as `async fn` (or under `#[neon(context)]`) whose first non-self parameter is a context reference such as `&mut FunctionContext` or `&mut Cx`, e.g. `async fn foo(&self, cx: &mut FunctionContext)`.

Common situations: Porting a synchronous Neon method to `async fn` and leaving the existing `cx: &mut FunctionContext` parameter in place; copy-pasting a sync method signature into an async one; migrating code after upgrading Neon where async methods require `Channel`.

Understand the failure class

Background: Type mismatch errors: IllegalArgumentException, TypeError and type guards across 150 open-source libraries — this error's family across 150 libraries.

Related errors


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

Appendix: source

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

fn check_channel(opts: &meta::Meta, sig: &syn::Signature) -> syn::Result<bool> {
    // Extract the first argument (after &self)
    let ty = match first_arg(opts, sig)? {
        Some(arg) => arg,
        None => return Ok(false),
    };

    // Check the type
    match &*ty.ty {
        // Provided `&mut Channel` instead of `Channel`
        syn::Type::Reference(ty) if opts.context || is_channel_type(&ty.elem) => {
            Err(syn::Error::new(
                ty.span(),
                "Expected an owned `Channel` instead of a reference.",
            ))
        }

        // Provided a `&mut Cx` instead of a `Channel`
        syn::Type::Reference(ty) if is_context_type(&ty.elem) => Err(syn::Error::new(
            ty.elem.span(),
            "Expected an owned `Channel` instead of a context reference.",
        )),

        // Found a `Channel`
        _ if opts.context || is_channel_type(&ty.ty) => Ok(true),

        // Tried to use an owned `Cx`
        _ if is_context_type(&ty.ty) => Err(syn::Error::new(
            ty.ty.span(),
            "Context is not available in async functions. Try a `Channel` instead.",
        )),

        _ => Ok(false),
    }
}

// Extract the first argument (after &self) from a method signature

View on GitHub (pinned to 38960e4381)