How to Fix: Duplicate Definition Error in `define_unknown_imports_as_traps` Method

6 min read

Fixing the Duplicate Definition Error in define_unknown_imports_as_traps

This bug appears when a Wasmtime linker is asked to define trap-based fallbacks for unknown imports more than once for the same import space. The result is a duplicate definition error during component preparation or instantiation, even though the code looks harmless at first glance.

In the reported pattern, code similar to this is enough to trigger the issue:

linker.define_unknown_imports_as_traps(&component_a).unwrap();
let ins_a = linker.instantiate_pre(&component_a).unwrap();

The failure typically happens because the linker is being mutated in a way that causes the same unresolved imports to be registered multiple times, or because pre-instantiation and repeated component preparation reuse a linker state that already contains trap definitions.

Understanding the Root Cause

The method define_unknown_imports_as_traps is designed to walk the imports required by a component and automatically register placeholder definitions that trap at runtime when called. This is useful for testing, partial linking, and diagnosing unresolved dependencies without wiring every host function manually.

The problem is that these generated definitions are still real linker entries. If the same import name, namespace, or component dependency path is defined again, the linker correctly rejects the second registration as a duplicate.

Technically, the error usually comes from one of these situations:

  • The same component is passed repeatedly to define_unknown_imports_as_traps on the same linker instance.
  • A linker already contains host definitions, and trap definitions are added for those same imports afterward.
  • Multiple components share overlapping import names, and the trap-generation step defines the same symbols more than once.
  • instantiate_pre is used in a workflow where the linker is reused across setup phases without checking whether definitions were already inserted.

In short, the linker is stateful. Once an import has been defined, calling another API path that tries to define it again will trigger the duplicate definition error.

Step-by-Step Solution

The safest fix is to ensure that define_unknown_imports_as_traps runs exactly once per linker/import set, or that each component gets a fresh linker when trap definitions are generated.

1. Avoid redefining unknown imports on the same linker

If you only need trap-based placeholders once, keep the setup single-pass:

let mut linker = wasmtime::component::Linker::new(&engine);

linker
    .define_unknown_imports_as_traps(&component_a)
    .unwrap();

let pre_a = linker.instantiate_pre(&component_a).unwrap();
let instance_a = pre_a.instantiate(&mut store).unwrap();

This works when the linker has not already defined the same imports elsewhere.

2. Use a fresh linker for separate preparation flows

If you instantiate multiple components or run multiple test stages, create a new linker per stage:

let mut linker_a = wasmtime::component::Linker::new(&engine);
linker_a
    .define_unknown_imports_as_traps(&component_a)
    .unwrap();
let pre_a = linker_a.instantiate_pre(&component_a).unwrap();

let mut linker_b = wasmtime::component::Linker::new(&engine);
linker_b
    .define_unknown_imports_as_traps(&component_b)
    .unwrap();
let pre_b = linker_b.instantiate_pre(&component_b).unwrap();

This isolates linker state and prevents trap definitions for one component from colliding with another.

3. Define real imports before adding trap fallbacks

If some imports are intentionally implemented by the host, register those first and only use traps for the remaining unresolved imports:

let mut linker = wasmtime::component::Linker::new(&engine);

// Define known host imports first.
// Example only; actual API shape depends on your bindings.
// linker.func_wrap("host", "log", host_log)?;

linker
    .define_unknown_imports_as_traps(&component_a)
    .unwrap();

let pre = linker.instantiate_pre(&component_a).unwrap();

This ordering helps because the trap method should only fill genuinely missing imports. If your current flow does the reverse and later adds a real definition for the same import, the second definition will fail.

4. Do not call the trap-definition method twice for the same component on the same linker

A subtle but common anti-pattern looks like this:

let mut linker = wasmtime::component::Linker::new(&engine);

linker.define_unknown_imports_as_traps(&component_a).unwrap();
linker.define_unknown_imports_as_traps(&component_a).unwrap(); // duplicate definition error

Instead, centralize linker setup:

fn build_linker_with_traps(
    engine: &wasmtime::Engine,
    component: &wasmtime::component::Component,
) -> anyhow::Result<wasmtime::component::Linker<MyState>> {
    let mut linker = wasmtime::component::Linker::new(engine);
    linker.define_unknown_imports_as_traps(component)?;
    Ok(linker)
}

5. If needed, separate shared host wiring from per-component trap wiring

In larger systems, the cleanest structure is:

  • one reusable function for known host definitions
  • one fresh linker per component for trap completion
fn configure_known_imports(
    linker: &mut wasmtime::component::Linker<MyState>,
) -> anyhow::Result<()> {
    // linker.func_wrap(...)?;
    Ok(())
}

fn prepare_component(
    engine: &wasmtime::Engine,
    component: &wasmtime::component::Component,
) -> anyhow::Result<wasmtime::component::InstancePre<MyState>> {
    let mut linker = wasmtime::component::Linker::new(engine);
    configure_known_imports(&mut linker)?;
    linker.define_unknown_imports_as_traps(component)?;
    linker.instantiate_pre(component)
}

This pattern avoids hidden linker mutation bugs and makes component instantiation deterministic.

Common Edge Cases

  • Shared import names across components: Two different components may import the same name, causing collisions if they share one mutable linker configured in multiple phases.
  • Mixed real and trap definitions: A host function manually defined after trap generation will conflict with the previously inserted trap entry.
  • Repeated test execution with reused fixtures: Test helpers that cache a linker globally or across test cases can accidentally preserve old definitions.
  • Nested component imports: If a component graph expands to repeated logical paths, generated trap definitions may overlap in ways that are not obvious from the top-level component API.
  • Pre-instantiation caching mistakes: Reusing a linker while assuming instantiate_pre is read-only can lead to confusion about where the duplicate entry originated.

When debugging, inspect your setup sequence and ask one question: Which exact code path defined this import first? The answer usually reveals the duplicate.

FAQ

Why does define_unknown_imports_as_traps fail if the imports are unknown?

Because the method turns unknown imports into concrete linker definitions that trap at runtime. Once inserted, they are no longer unknown to the linker, so redefining them is an error.

Should I reuse the same linker across many components?

You can, but only if their import definitions are intentionally shared and you control definition order carefully. For independent preparation flows, a fresh linker is usually safer.

Is instantiate_pre the real problem here?

Usually no. The underlying issue is duplicate state in the linker. instantiate_pre often just exposes the problem because it validates the final import mapping.

The practical fix is simple: treat define_unknown_imports_as_traps as a one-time linker population step, avoid redefining the same imports, and prefer fresh linker instances when preparing separate components.

Leave a Reply

Your email address will not be published. Required fields are marked *