How to Fix: bindgen! macro WIT Package resolution issue

6 min read

Why bindgen! Fails to Resolve Shared WIT Packages Across Host and Guest

If your Rust host and Wasm guest both reference shared WIT types, but bindgen! refuses to resolve the package correctly, the problem is usually not the type definitions themselves. The real issue is how WIT package resolution works: packages are resolved from a package root, and cross-package references only succeed when the files are laid out and declared exactly the way wit-bindgen and wasmtime expect.

Understanding the Root Cause

This bug appears when you try to define a reusable types package and import it from another WIT package, then generate bindings with Rust’s bindgen! macro. The failure usually happens because WIT package resolution is package-oriented, not file-oriented.

In practice, that means:

  • bindgen! expects a valid WIT package root, not an arbitrary single file.
  • Imported packages must be discoverable from the same WIT dependency layout.
  • The package name, namespace, and path structure must agree.
  • If the host and guest use different local layouts, one side may compile while the other fails.

A common mistake is putting shared type definitions into a separate types.wit file and assuming another package can reference it just because the file exists nearby. That is not enough. WIT imports are resolved through declared packages and worlds/interfaces, not through loose file adjacency.

Another source of confusion is that embedded Wasmtime hosts and Wasm guests may use different generation flows. The host often runs wasmtime::component::bindgen! or related macros against a WIT root, while the guest may use wit-bindgen or a component build process. If both sides are not pointed at the same package graph, resolution breaks.

So the root cause is usually one of these:

  • The shared package is not declared as a real WIT package.
  • The importing package uses the wrong package identifier.
  • bindgen! is pointed at a file instead of the package directory.
  • The dependency package is not vendored or placed where the resolver expects it.
  • The referenced items are defined in an interface/world combination that is not actually exported or imported correctly.

Step-by-Step Solution

The fix is to structure your WIT definitions as proper packages, give the shared types their own package identity, and invoke bindgen! from the package root that can resolve all dependencies.

1. Create a dedicated shared WIT package

Put your shared types in their own package with a clear namespace and package name.

project-root/
  wit/
    deps/
      shared-types/
        types.wit
    app/
      app.wit

Example shared package:

package example:shared-types;

interface types {
  record user {
    id: u64,
    name: string,
  }

  variant error {
    not-found,
    permission-denied,
    internal(string),
  }
}

This creates a real reusable package named example:shared-types.

2. Import that package from your app package correctly

Your application WIT should declare its own package and import the shared interface by package-qualified name.

package example:app;

import example:shared-types/types;

interface api {
  get-user: func(id: u64) -> result<types.user, types.error>;
}

world host {
  export api;
}

The key detail is that example:shared-types/types must match:

  • The package declaration: package example:shared-types;
  • The interface name: interface types

3. Point bindgen! at the package root, not just a loose file

In Rust, configure the macro so it reads the package root that contains the WIT package and its dependencies.

wasmtime::component::bindgen!({
    path: "wit/app",
    world: "host",
});

If your dependencies are under a supported dependency directory, the resolver can walk them. The important part is that the path must represent the package root for example:app, not merely an isolated file copied somewhere else.

4. Keep guest and host on the same WIT package graph

If your guest also generates bindings, make sure it uses the same package layout and package names.

wit/
  deps/
    shared-types/
      types.wit
  app/
    app.wit

Do not duplicate the same types into slightly different local folders with different package declarations. That often causes subtle mismatches where generated bindings compile separately but do not line up as the same component contract.

5. Verify package names and imports carefully

These must align exactly:

// shared package
package example:shared-types;
interface types {
  record user { id: u64, name: string }
}

// consuming package
package example:app;
import example:shared-types/types;

interface api {
  get-user: func(id: u64) -> types.user;
}

If you rename the interface from types to common, your import must also change to:

import example:shared-types/common;

6. Expose the right world

Even when package resolution works, bindgen! can still fail if the requested world name does not exist in that package.

package example:app;

import example:shared-types/types;

interface api {
  get-user: func(id: u64) -> types.user;
}

world host {
  export api;
}

Then in Rust:

wasmtime::component::bindgen!({
    path: "wit/app",
    world: "host",
});

If the world is actually named guest or app, the macro will not find host.

A robust layout for host/guest sharing looks like this:

project-root/
  crates/
    host/
      src/
        main.rs
    guest/
      src/
        lib.rs
  wit/
    deps/
      shared-types/
        types.wit
    app/
      app.wit

Host binding generation:

wasmtime::component::bindgen!({
    path: "../../wit/app",
    world: "host",
});

Guest binding generation should reference the same WIT package graph through its own build configuration or macro settings.

8. If resolution still fails, test the minimal dependency chain

Reduce the WIT setup to one shared interface and one importing package. If that works, the remaining issue is usually a naming mismatch or an unsupported local dependency layout.

package example:shared-types;
interface types {
  record thing { value: string }
}
package example:app;
import example:shared-types/types;

world host {
  export types;
}

Once that resolves, reintroduce your real interfaces incrementally.

Common Edge Cases

  • Importing a file instead of a package: WIT imports target package/interface identities, not ad hoc file paths.
  • Wrong namespace or package name: example:types and example:shared-types are different packages. Even a small rename breaks resolution.
  • Interface name mismatch: If the interface is named common, importing .../types will fail.
  • World not found: bindgen! may report a confusing error when the package resolves but the requested world does not exist.
  • Host and guest using copied WIT trees: Separate copies drift over time, causing incompatible generated bindings.
  • Dependency folder not included: If the shared package is not in the expected dependency layout, the resolver cannot discover it.
  • Mixing old examples with newer tooling: Some older blog posts or examples use layouts that do not match current component model tooling expectations.

FAQ

Can I share types across multiple WIT packages?

Yes. Put the shared definitions in a dedicated package such as example:shared-types, define them inside an interface, and import that interface from each consuming package using the fully qualified package/interface name.

Why does bindgen! work when everything is in one file, but fail after I split out types?

Because a single file avoids cross-package resolution entirely. Once you split shared definitions into another package, the resolver must locate that package through the proper WIT package graph and dependency layout.

Should the host and guest each have their own copy of the same WIT files?

No. The safest approach is a single shared WIT source tree used by both sides. That ensures the world, interfaces, and types stay consistent for both the embedded Wasmtime host and the Wasm guest.

For deeper reference on WIT and component conventions, consult the official Component Model documentation and the Wasmtime documentation to verify the current expected package layout for your toolchain version.

Leave a Reply

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