clockworklabs/SpacetimeDB · critical

{CALL_VIEW_DUNDER} export is not a function

Error message

{CALL_VIEW_DUNDER} export is not a function

What it means

When hooking up a module instance, get_call_view resolves the `__call_view__` export (the generated entry point for SQL views over the module). `Export::into_func()` returns None when the export is a memory, global, or table instead of a function, and the host panics immediately. A non-function export carrying that reserved name is treated as a malformed module. The export itself is optional (get_export returning None yields None from the function), so the panic only fires when something occupies the name with the wrong kind.

Source

Thrown at crates/core/src/host/wasmtime/wasmtime_module.rs:469

    Some(
        export
            .into_func()
            .unwrap_or_else(|| panic!("{CALL_PROCEDURE_DUNDER} export is not a function"))
            .typed(store)
            .unwrap_or_else(|err| panic!("{CALL_PROCEDURE_DUNDER} export is a function with incorrect type: {err}")),
    )
}

/// Look up the `instance`'s export named by [`CALL_VIEW_DUNDER`].
///
/// Similar to [`get_call_procedure`], but for views.
fn get_call_view(store: &mut Store<WasmInstanceEnv>, instance: &Instance) -> Option<CallViewType> {
    let export = instance.get_export(store.as_context_mut(), CALL_VIEW_DUNDER)?;
    Some(
        export
            .into_func()
            .unwrap_or_else(|| panic!("{CALL_VIEW_DUNDER} export is not a function"))
            .typed(store)
            .unwrap_or_else(|err| panic!("{CALL_VIEW_DUNDER} export is a function with incorrect type: {err}")),
    )
}

/// Look up the `instance`'s export named by [`CALL_VIEW_ANON_DUNDER`].
///
/// Similar to [`get_call_procedure`], but for anonymous views.
fn get_call_view_anon(store: &mut Store<WasmInstanceEnv>, instance: &Instance) -> Option<CallViewAnonType> {
    let export = instance.get_export(store.as_context_mut(), CALL_VIEW_ANON_DUNDER)?;
    Some(
        export
            .into_func()
            .unwrap_or_else(|| panic!("{CALL_VIEW_ANON_DUNDER} export is not a function"))
            .typed(store)
            .unwrap_or_else(|err| panic!("{CALL_VIEW_ANON_DUNDER} export is a function with incorrect type: {err}")),
    )
}

View on GitHub (pinned to 6dee26c6ef)

Solutions

  1. Find the offending export: `wasm-objdump -x module.wasm | grep -A2 __call_view__` shows whether it is a global/memory/table.
  2. Rename or remove the conflicting export in module source (check any #[export_name] attributes) and rebuild.
  3. Rebuild with the SDK matching the server so codegen emits the function export correctly.
  4. Reserve the `__call_*__` dunder names for generated code only.

Example fix

// before: accidental export-kind collision with the reserved name
#[export_name = "__call_view__"]
static VIEW_FLAG: AtomicBool = AtomicBool::new(false);

// after: do not shadow the reserved dunder export name
static VIEW_FLAG: AtomicBool = AtomicBool::new(false);
Defensive patterns

Strategy: type-guard

Validate before calling

use wasmtime::{Engine, Module, ExternType};

fn no_dunder_name_collisions(engine: &Engine, wasm: &[u8]) -> Result<(), String> {
    let module = Module::new(engine, wasm).map_err(|e| e.to_string())?;
    for name in ["__call_procedure__", "__call_view__", "__call_view_anon__", "__call_http_handler__"] {
        if let Some(exp) = module.get_export(name) {
            match exp.ty() {
                ExternType::Func(_) => {}
                other => return Err(format!("{name} exported as {other:?}, expected a function")),
            }
        }
    }
    Ok(())
}

Type guard

fn is_func_export(ty: &ExternType) -> bool { matches!(ty, ExternType::Func(_)) }

Try / catch

let r = std::panic::catch_unwind(|| host.instantiate(module));
if let Err(p) = r {
    if panic_message(&p).contains("__call_view__ export is not a function") {
        // a non-function item holds the reserved name: fix module exports and rebuild
    }
}

Prevention

When it happens

Trigger: Instantiating a module that exports `__call_view__` as a global, memory, or table rather than a wasm function; hit inside get_call_view's `into_func().unwrap_or_else(panic!)` during publish or database start.

Common situations: Name collision: a static, memory, or custom export accidentally named `__call_view__` (e.g. via #[export_name]); hand-written WAT exporting a global with the reserved name; codegen changes between SDK versions exporting the wrong kind.

Related errors


AI-assisted analysis of clockworklabs/SpacetimeDB@6dee26c6ef (2026-08-20). Data as JSON: /api/errors/88d4ca8c20bf1ec6. Report an issue: GitHub.