Upcoming Changes to Rust's WebAssembly Linking: The End of --allow-undefined
By • min read
<p>Rust's WebAssembly (Wasm) targets are undergoing a significant change that may break existing projects. For years, the <code>--allow-undefined</code> flag was automatically passed to <code>wasm-ld</code> during linking, allowing undefined symbols to be silently imported. This flag is now being removed to align Wasm behavior with native platforms and catch errors earlier. This article explains the change, its implications, and how to adapt your code.</p>
<h2 id="what-is-changing"><a href="#what-is-changing">#</a> What is changing in Rust's WebAssembly targets?</h2>
<p>Starting soon, Rust's WebAssembly targets will no longer pass the <code>--allow-undefined</code> flag to <code>wasm-ld</code> during linkage. This flag previously permitted undefined symbols—like functions declared in <code>extern "C"</code> blocks—to be silently turned into WebAssembly imports. Its removal means that any undefined symbol encountered at link time will now cause a compilation error instead of being allowed. This shift aims to make Rust's WebAssembly behavior consistent with native targets, where such errors are caught immediately. Projects relying on this flag to link against external C libraries or other Wasm modules will need to adapt by explicitly defining or importing those symbols.</p><figure style="margin:20px 0"><img src="https://www.rust-lang.org/static/images/rust-social-wide.jpg" alt="Upcoming Changes to Rust's WebAssembly Linking: The End of --allow-undefined" style="width:100%;height:auto;border-radius:8px" loading="lazy"><figcaption style="font-size:12px;color:#666;margin-top:5px">Source: blog.rust-lang.org</figcaption></figure>
<h2 id="what-is-allow-undefined"><a href="#what-is-allow-undefined">#</a> What exactly does the --allow-undefined flag do?</h2>
<p>The <code>--allow-undefined</code> flag, when passed to <code>wasm-ld</code>, tells the linker to ignore unresolved symbols. In WebAssembly, symbols correspond to functions or globals used in the code. If a Rust program calls a function declared via <code>extern "C" { fn my_func(); }</code>, the linker normally expects to find a definition for <code>my_func</code>. With <code>--allow-undefined</code>, the linker instead creates an <em>import</em> for that symbol from the WebAssembly environment (e.g., <code>(import "env" "my_func")</code>). This effectively punts the responsibility of supplying the function to runtime or the embedding host. While useful in early days, it masks errors like typos or missing libraries, leading to broken modules that fail at runtime.</p>
<h2 id="why-was-it-used"><a href="#why-was-it-used">#</a> Why was --allow-undefined used historically?</h2>
<p>The exact origins are unclear, but <code>--allow-undefined</code> was likely a practical necessity when WebAssembly support first landed in Rust. In the early days, the toolchains and linkers were immature, and many dependencies (like <code>libc</code> or platform-specific functions) were not yet compiled to WebAssembly. Allowing undefined symbols enabled projects to get off the ground by deferring resolution to the runtime. Over time, this workaround became default behavior, even as the ecosystem matured and native WebAssembly libraries became available. However, this default now diverges from how other platforms work, where undefined symbols are caught at compile time. The upcoming removal restores consistency and catches mistakes earlier.</p>
<h2 id="problems-with-allow-undefined"><a href="#problems-with-allow-undefined">#</a> What are the problems with using --allow-undefined?</h2>
<p>The main issue is that <code>--allow-undefined</code> suppresses errors during compilation, pushing them to runtime. For example, if you typo a function name—writing <code>mylibraryinit</code> instead of <code>mylibrary_init</code>—the linker will create an import for the misspelled name, and the actual function will never be called. Similarly, if you forget to link a required C library, the resulting Wasm module will import nonexistent symbols, causing failures only when executed. This lengthens the feedback loop between introducing a bug and discovering it. On native platforms, such mistakes produce immediate linker errors. Removal of the flag makes Wasm behavior consistent, reducing silent failures and improving development reliability.</p>
<h2 id="how-to-handle"><a href="#how-to-handle">#</a> How can Rust developers handle this change?</h2>
<p>To adapt, you need to ensure all symbols referenced in your WebAssembly code are properly resolved at link time. For external functions from C libraries or host environments, use the <code>#[link(wasm_import_module = "module_name")]</code> attribute and define the import explicitly. For example:</p>
<pre><code>#[link(wasm_import_module = "env")]
extern "C" {
fn mylib_init();
}</code></pre>
<p>If you previously relied on <code>--allow-undefined</code> to import arbitrary functions from the host, you must now declare them with the correct module and name. For projects linking to precompiled C code, ensure all object files are included in the link command (e.g., using the <code>wasm-pack</code> or <code>cargo build</code> flags). Tools like <code>cargo web</code> or <code>wasm-bindgen</code> also have built-in support for imports. Check your dependencies and update build scripts to avoid undefined symbols.</p>
<h2 id="what-are-undefined-symbols"><a href="#what-are-undefined-symbols">#</a> What are undefined symbols in the WebAssembly context?</h2>
<p>In WebAssembly, a symbol is a named entity (function, global, or memory) that is either defined within the module or expected to be provided by the environment. When a Rust program uses <code>extern "C"</code> to declare a function, that function becomes an undefined symbol—the linker must find its definition. Without <code>--allow-undefined</code>, if no definition exists (e.g., from another object file or library), the linker errors out. With the flag, it instead generates an import instruction in the Wasm binary, like <code>(import "env" "my_symbol")</code>. This means the undefined symbol is silently transformed into a runtime dependency. The flag's removal forces you to explicitly state these dependencies, making the build process fail early if a symbol is missing—a safer approach for producing robust WebAssembly modules.</p>