osbytes

Search

Find posts, projects, and members.

← back to blog

Rust 1.96.0's wasm linker now rejects undefined symbols (check your build)

2026-05-29by@osbytes5 min read
#rust #release #webassembly #cargo #tooling #breaking-changes

TL;DR

  • Rust 1.96.0 shipped 2026-05-28. The WebAssembly linker change is the one that breaks builds: it no longer passes --allow-undefined, so an undefined symbol is a linker error instead of being converted to an import from the env module.
  • If you build a wasm target and you have undefined symbols that used to become env imports, your build fails on the bump. Two ways back: RUSTFLAGS=-Clink-arg=--allow-undefined, or annotate the symbol with #[link(wasm_import_module = "env")].
  • Also in: stabilized assert_matches! / debug_assert_matches!, copyable range types in core::range (the std half of RFC 3550), and fixes for two Cargo CVEs that only touch third-party-registry users.

The wasm change, and who it breaks

Before 1.96.0, rustc passed --allow-undefined to the wasm linker. An undefined symbol at link time didn't fail the build — it got quietly turned into a WebAssembly import from the env module, and the host was expected to supply it at instantiation. Convenient, and a good way to ship a binary with a typo'd symbol name that nobody notices until the module fails to instantiate in production.

1.96.0 drops the flag. An undefined symbol is now a hard linker error. The release notes frame it as catching bugs earlier — symbol-naming mistakes and accidental missing definitions surface at build time instead of at instantiation.

Reader-action test: do you build a wasm target with symbols you expect the host to provide? If yes, and you were leaning on the old auto-import behavior, the bump turns those into link errors. You'll see it in CI the moment someone updates the toolchain.

What to change

Two supported paths, per the release notes:

  • Restore the old behavior wholesale: RUSTFLAGS=-Clink-arg=--allow-undefined. This re-adds the flag and you're back to undefined-becomes-env-import. Blunt, fine as a stopgap while you sort out which symbols were intentional.
  • Be explicit per symbol: put #[link(wasm_import_module = "env")] on the extern block declaring the symbol. This says "yes, this one is a host import from env" out loud, so the linker stops treating it as a mistake. This is the version you want to keep — it documents intent instead of disabling the check globally.

If your wasm build has no host imports at all, you likely won't notice anything; this only bites builds that depended on the implicit conversion.

The smaller stable bits

assert_matches! and debug_assert_matches! are stable. They assert a value matches a pattern and, on failure, panic with the Debug value that didn't match — so you get what the value was, not just that the assert blew up. They are not in the prelude, on purpose: the names collide with popular third-party crates, so you import them from core/std yourself.

Copyable range types land in core::range — the standard-library portion of RFC 3550. The motivation: people expect Range to be Copy, but the legacy ranges implement Iterator directly, and Copy + Iterator on one type is a footgun. The new types implement IntoIterator instead, so they can be Copy. Note the catch: 0..1 syntax still produces the legacy types for now; the switch to core::range is deferred to a future edition. So this is opt-in today, not a silent behavior change to existing range code.

The Cargo CVEs (secondary)

1.96.0 also carries fixes for two Cargo vulnerabilities, both scoped to third-party-registry users — crates.io users are unaffected by either:

  • CVE-2026-5223 (medium): a crafted tarball's symlink could escape the crate's cache directory. Covered in an earlier osbytes note.
  • CVE-2026-5222 (low): authentication against normalized URLs.

If you build crates from anything other than crates.io, the bump is also a patch. If you're crates.io-only, these are informational — the wasm change is your real reason to read the changelog before bumping.

What to do on bump

  • Building wasm? Grep your tree for extern blocks that expect host-provided symbols. Decide per symbol: keep it (#[link(wasm_import_module = "env")]) or it was a mistake (fix the definition). Reach for RUSTFLAGS=-Clink-arg=--allow-undefined only as a temporary unblock.
  • Pin the CI toolchain bump in a branch first so the wasm link errors land in review, not on someone's main push.
  • Third-party-registry users get the two Cargo fixes for free; crates.io users lose nothing.

Sources