How to Fix: Function not found when linking two components together

6 min read

“Function not found” when linking WebAssembly components usually means the import and export do not have the same component model shape.

If gcd2.wasm exports gcd2: func(a: s64, b: s64) -> s64 and gcd4.wasm expects to call it, the linker will fail unless both components agree on the exact function name, the exact interface, and the exact component import/export path. In practice, this error often appears when a function was exported from a core module, but never re-exported correctly from the final component, or when the consuming component imports a differently named function than the producer actually exposes.

Understanding the Root Cause

In the WebAssembly Component Model, linking does not happen by fuzzy matching. The linker resolves imports against exports using a precise contract:

  • The exported item must exist at the expected component level.
  • The exported item name must match the imported name exactly.
  • The function signature must match exactly, including scalar types such as s64.
  • If the function is part of a named interface, the consuming side must import that same interface path, not just a similarly shaped function.

This is why a component can appear to “have” a function, while the linker still says it cannot find it. A few common technical reasons:

  • Core module vs component export mismatch: the function exists in the underlying module, but the component wrapper does not export it.
  • Name mismatch: one side uses gcd2, the other imports gcd or expects it under an interface namespace.
  • WIT mismatch: both components were generated from different WIT definitions, so the import/export contracts differ.
  • Wrong instantiation target: you may be trying to link a component against another artifact that is not exposing the expected world or interface.

When linking gcd2.wasm and gcd4.wasm, the fix is usually to make both components share the same WIT contract and ensure that the producer explicitly exports the callable function in the form the consumer imports.

Step-by-Step Solution

The most reliable solution is to define a shared WIT interface, export gcd2 from one component through that interface, and import the same interface in the second component.

1. Define a shared WIT contract

Create a WIT package that describes the function once.

package example:math;
interface gcd-api {
  gcd2: func(a: s64, b: s64) -> s64
}
world gcd2-world {
  export gcd-api;
}
world gcd4-world {
  import gcd-api;
  export run: func(a: s64, b: s64, c: s64, d: s64) -> s64
}

This ensures both sides agree on the exact shape of the contract.

2. Make sure gcd2.wasm exports the interface, not just a raw function

If your source language generates a component, verify that it exports gcd-api from the component world. The important detail is that the final component export must match the WIT world, not only the internal core wasm symbol table.

// Pseudocode shape
export interface gcd-api {
  gcd2(a: s64, b: s64) -> s64
}

Then build the producer component so the generated component exposes that interface.

3. Make sure gcd4.wasm imports the same interface

The consumer should import gcd-api and call gcd2 through that import.

// Pseudocode shape
import gcd-api;
run(a: s64, b: s64, c: s64, d: s64) -> s64 {
  let x = gcd-api.gcd2(a, b)
  let y = gcd-api.gcd2(c, d)
  gcd-api.gcd2(x, y)
}

If the consumer instead imports a top-level function named gcd2 while the producer exports gcd-api/gcd2, linking will fail with a function-not-found style error.

4. Inspect the actual imports and exports before linking

Before changing code blindly, inspect both component files with your component tooling. You want to verify whether:

  • gcd2.wasm exports exactly gcd-api with function gcd2
  • gcd4.wasm imports exactly gcd-api with function gcd2
# Example workflow using component tooling
# Inspect producer exports
wasm-tools component wit gcd2.wasm
# Inspect consumer imports
wasm-tools component wit gcd4.wasm

If the printed WIT does not line up, the linker error is expected.

Once both components agree, link or compose them using your component composition tool.

# Example composition flow
wac plug gcd2.wasm --into gcd4.wasm -o linked.wasm

If your toolchain differs, the same principle applies: plug the exporting component into the importing component only after verifying the interface contract.

6. If the function exists only in a core wasm module, wrap it into a component export

This is a frequent source of confusion. A raw core wasm export named gcd2 is not automatically a valid component import target. You may need an adapter or a generated component wrapper.

# Example idea: convert a core wasm module into a component
wasm-tools component new gcd2-core.wasm -o gcd2.wasm

Then ensure the wrapper exposes the WIT-defined interface expected by the consumer.

7. Rebuild both sides from the same WIT package

The safest long-term fix is to generate both components from the same WIT source, rather than maintaining separate copies. That eliminates subtle drift in names, worlds, and signatures.

# Rebuild both components after updating shared WIT
# producer build
# consumer build

If you do only one thing, do this: use one shared WIT definition and inspect both compiled components before linking.

Common Edge Cases

  • Signed vs unsigned mismatch: s64 and u64 are different. A near-identical signature still fails to link.
  • Top-level export vs interface export: exporting gcd2 directly is not the same as exporting it under gcd-api.
  • Different world names: even if the function matches, your toolchain may compose based on the selected world, and the wrong world can hide the expected export.
  • Component generated from old WIT: stale build artifacts often cause confusing link failures after interface changes.
  • Language binding differences: some language toolchains rename or namespace imports/exports based on packages or interfaces. Inspect the generated component, not just the source code.
  • Linking a core module to a component: component tooling expects component-level imports and exports, not plain wasm module symbols.

FAQ

Why does the linker say the function is missing even though I can see gcd2 in the wasm file?

Because you may be looking at a core wasm export, while the linker is resolving component model imports. The symbol can exist internally but still not be exported in the component interface the consumer expects.

Do both components need the exact same WIT file?

They do not need to be the same physical file, but they must compile to the exact same import/export contract. In practice, using one shared WIT package is the safest way to avoid drift.

Not directly. Component linking is name- and interface-based, not just signature-based. You must either rename one side, adapt it with a wrapper, or export/import the same interface path.

The short version: this bug is almost never about the GCD implementation itself. It is about the contract boundary between two WebAssembly components. Once gcd2.wasm exports the same interface that gcd4.wasm imports, the function-not-found error disappears.

Leave a Reply

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