Rust Wasm imports should declare their host contract
Rust’s WebAssembly linker change separates missing definitions from deliberate host imports. Understand the April plan, May release and compatibility limits.
A missing library function and a deliberate function supplied by a WebAssembly host should be distinguishable before an artifact reaches that host. Rust’s change to undefined-symbol handling argues for making that distinction explicit. A successful link should mean that the build has accounted for its dependencies, including the dependencies intentionally left to the embedding environment.
The chronology matters. The April 4, 2026 announcement described removing the default --allow-undefined linker flag as an upcoming change scheduled for Rust 1.96 on May 28. It was not already stable in April. The May 28 release announcement subsequently confirmed that the change took effect. Looking back from November, those are announcement and delivery, respectively.
An unresolved symbol leaves a question open
The April explanation separates an undefined linker symbol from an intentional WebAssembly import. With the old permissive default, a reference lacking a linked definition could become an import from env. A build mistake could therefore produce a module whose host requirements differed from the requirements the developer intended. The artifact’s existence would not settle whether those requirements were sensible.
That is a useful distinction for reviewing a pipeline. A linker error points toward the missing definition while the build context is still close. A later import-resolution or instantiation failure appears after the artifact has crossed another boundary. It may draw attention to host configuration even when the underlying mistake was a misspelled function or a library omitted from linking. These are different places to discover the same unresolved design question.
An early error cannot establish that the whole application is correct. It can establish that one class of unresolved dependency has not silently become an external obligation. That narrower guarantee is valuable because it gives successful linking a clearer meaning. The argument concerns diagnostic responsibility, rather than a measured improvement in build speed or reliability.
A host import is a contract
A function intentionally supplied by the embedding host belongs in the interface between module and host. Naming the import module makes that intention visible in the source. Rust’s announcement describes the wasm_import_module link attribute as the way to retain deliberate imports while avoiding the accidental-undefined-symbol behavior. It also describes compatibility across the transition. This is source-level intent rather than permission for every unresolved reference.
For an engineering team, that declaration should lead to a corresponding explanation of the host obligation. Which embedding environment supplies the function? Who owns the agreement about its name and behavior? What changes if the module is used by another consumer? Those are design questions to answer around an intentional import. A permissive flag alone answers none of them.
Keeping imports explicit also improves a review’s scope. A reviewer can consider whether an external dependency is intended without having to infer intent from the absence of a link error. The declaration is not proof that every host supplies the right function. It identifies the boundary where that separate proof belongs. Module construction and successful instantiation remain different claims.
That separation matters during handover. The person maintaining the module may differ from the person maintaining its host. A short, concrete interface description gives both people the same object to discuss. Otherwise a host can inherit an obligation that was created accidentally by the build, and the module’s maintainer may not recognize it as part of the public contract.
Compatibility can justify a temporary exception
The strongest counterargument is operational. Existing projects may deliberately rely on the permissive behavior, and forcing every declaration to change immediately can interrupt a working delivery process. Both official posts describe restoring --allow-undefined as an available compatibility option. A temporary exception can be reasonable while the team identifies which imports are intentional.
The cost of that exception is the return of ambiguity. It permits the build to accept missing definitions as well as intended imports. A team choosing it should be able to say why the exception exists and what evidence will let it narrow or remove that permission. Otherwise “temporary” describes a hope rather than a decision with an owner.
There is no need to turn compatibility into a moral failure. A working host contract may predate the new default. Preserving that service while clarifying its declarations can be a sound sequence. The relevant objection is to treating the restored flag as evidence that all undefined symbols are correct. Compatibility and correctness answer different questions.
Scope the conclusion to the actual artifact
The official release establishes what Rust 1.96 changed in this toolchain behavior. It does not demonstrate the installed compiler version, downstream flags or host obligations of an unfamiliar project. Nor does it justify a claim that every WebAssembly target and embedding has identical runtime behavior. The April post itself discusses different ways an unexpected import can surface.
A useful review therefore records the compiler and linking assumptions for the artifact under discussion, then separates linked definitions from intended host imports. Any remaining exception should have an explanation. This can begin as a source and pipeline review; the public announcements are not a substitute for evidence from a project’s own environment when making release decisions.
For custom software development, make the module-to-host agreement part of what is handed over. The next maintainer should know which dependencies are resolved while linking and which are supplied externally. Rust’s stricter default gives the build a better place to reject accidental omissions. Explicit imports give intentional external dependencies a place to be reviewed on their own terms.
Paul