While Microsoft Is Busy Learning Rust, I'm Rebuilding .NET for WASI A developer has released NetWasm, an independent CIL-to-WebAssembly compiler, CoreLib, and runtime for WASI that produces standalone native binaries without shipping the conventional .NET runtime. A clean Release build of a simple Console.WriteLine(42) program yields an 88,419-byte uncompressed WASI Preview 2 component that bundles the needed CoreLib and runtime pieces plus a precise Boehm garbage collector. The project targets the deployment model now known as Native AOT, which the developer first pursued in 2008, and follows Kotlin/Wasm's example of a WebAssembly path that does not carry a virtual machine along. Apparently Stephen Toub https://x.com/stephentoub is busy with Rust now, so I've decided to cover his .NET shift. Okay, that's a tongue in cheek obviously. Toub recently wrote about leading the migration of GitHub Copilot's agent runtime from TypeScript to more than 800,000 lines of production Rust https://github.blog/ai-and-ml/generative-ai/migrating-the-github-copilot-runtime-to-rust-using-copilot/ , with AI agents writing most of the code. But the timing made me laugh. One of the engineers most closely associated with modern .NET is moving a major Microsoft-owned runtime to Rust. Meanwhile, I'm over here building an independent .NET compiler, CoreLib, and runtime for WASI. C syntax has always felt like driving a luxury car to me. I love writing in it. But for years I also wanted something I could get from C++: a native binary without having to ship the conventional .NET runtime alongside it. In 2008, fourteen years before Native AOT shipped with .NET 7, I made my first attempt at this idea and wrote my own generational concurrent garbage collector and coupled it with Borland's C++ compiler. In a later project, I built a managed programming model on top of C++ using template metaprogramming, ran Boehm GC in precise mode, and used GCC plugins to generate the GC maps. I was already chasing the same combination: the productivity and memory model I liked from C , with native output and control over the runtime. The term AOT is overloaded here. In 2008, Microsoft's .NET Framework couldn't publish this kind of standalone native executable. NGen existed, but it installed precompiled machine code into the computer's native image cache for the CLR to load. Mono had an AOT mode too, but its output still ran with the Mono runtime. Neither gave me the deployment model I wanted: one native executable containing the runtime support it needed, with no CLR or virtual machine to install on the target system. That is what .NET developers now call Native AOT. I didn't have that name for it at the time. I just wanted the binary. Today my answer is NetWasm https://www.netwasm.com GitHub Repo https://github.com/zion-sati/NetWasm : an independent CIL-to-WebAssembly compiler, a new CoreLib, and a runtime designed around what the program actually uses. It doesn't compile C into C++; it takes a different route to the same ambition. The output includes a runtime and a garbage collector, built for the target and linked according to what the program needs. Here's the deliberately boring result that made me think the idea was worth pursuing: Console.WriteLine 42 ; A clean Release build produces an 88,419-byte, uncompressed WASI Preview 2 component . That file includes the program, the pieces of CoreLib and runtime it needs, and a precise Boehm garbage collector. It doesn't include the Wasm host. It's one small-program measurement, not a claim that every C application will be 88 KB or that a collection cycle happened while printing 42 . The exact setup and reproduction notes are here https://github.com/zion-sati/NetWasm/blob/main/docs/size-and-methodology.md . The number is interesting. The choices that make it possible are the actual project. While working on EffinDOM https://github.com/zion-sati/EffinDOM and Fui https://github.com/zion-sati/fui-rs , Kotlin/Wasm brought that old question back into focus. It has a path to WebAssembly that doesn't have to take the JVM along for the ride. I wanted to see what a similarly deliberate path for C could look like. Microsoft has done real work on AOT and trimming. But when the foundations of a runtime were designed for different environments, making the output smaller can only take you so far. I wanted to change the foundations: start with a small runtime, define the supported surface, and make features pay their way into the final binary. That meant I couldn't simply reuse CoreLib and swap out the last compilation step. I wrote a separate one, and built the compiler and runtime together. Roslyn compiles C to CIL, the .NET intermediate language. NetWasm takes that CIL, follows the code the application can reach, and compiles it to WebAssembly. It ships its own CoreLib and runtime with the output. The resulting application doesn't need CoreCLR, Mono, dotnet.js , or a pile of managed application DLLs alongside it. Today's supported language is C , including C 15 when you select the .NET 11 SDK. “It reads CIL” doesn't mean every F or VB.NET application, or every NuGet package, works today. The main target is a WASI Preview 2 component . Think of WASI as the host interface: the component gets capabilities from the environment that runs it. It can run standalone with a suitable Wasm host. The playground https://playground.netwasm.com/ is another way to try it: the compiler runs as Wasm in your browser , so the code you enter is compiled locally rather than sent to my compilation server. NetWasm has no runtime reflection and no runtime type-name catalog . There's no switch that puts full names and namespaces back into the output. dynamic and reflection are intentional design boundaries, and I don't plan to support them in the future. Keeping that machinery would change the cost model for every small app, including apps that never ask for it. I strongly believe the future of .NET won't need them. Source generation, static analysis, and closed-world compilation can move work to build time and generate exactly what an application needs. NetWasm is designed for that future rather than carrying a dynamic runtime model forward indefinitely. The programming model is closer to C++ with a garbage collector than to shrinking a conventional desktop .NET application. Given my earlier C++ and Boehm GC work, that's deliberate. You still write C . You can still use things like LINQ. But a feature doesn't get a free ride just because it's familiar from desktop .NET. Time zone data, for example, is opt in. Serialization is a good example of the tradeoff too: source-generated JSON support is there when you need typed serialization, and you pay for it when you use it. If all you need is a lightweight parsed dictionary, you can choose that instead. This isn't limited to toy programs. There's a separate libraries repository https://github.com/zion-sati/NetWasm.Libraries with ports covering areas such as LINQ, HTTP JSON, configuration, options, dependency injection, and caching. There's also TUnit support through dotnet test . I'm building toward a useful ecosystem without making a clean Console.WriteLine 42 carry the whole ecosystem around. With the .NET SDK installed: dotnet new install NetWasm.Templates dotnet new netwasm-app -n HelloNetWasm cd HelloNetWasm dotnet restore dotnet run dotnet publish -c Release -o publish/local If you have Wasmtime installed, you can run the published component directly: wasmtime run publish/local/HelloNetWasm.wasm That prints 42 . You can also open the browser playground https://playground.netwasm.com/ without installing anything. NetWasm is early I already have a known list of bugs I need to fix . Expression trees, managed threading, and the remaining WASI surface—including general managed File / FileStream / Directory APIs and raw sockets—aren't supported yet. They are on the roadmap. dynamic and runtime reflection are different: they are intentional non-goals. The support inventory https://github.com/zion-sati/NetWasm/blob/main/docs/support-status.md is the place to check before trying a package. The compiler and tooling are source available under the NetWasm Community License https://github.com/zion-sati/NetWasm/blob/main/docs/licensing.md ; the CoreLib, runtime, templates, and ported libraries are MIT licensed. I'd also like to explore WasmGC as a future collector backend. For now, the precise collector is part of the result you can build and run today. What excites me about Mono's history is that it showed another implementation could change what .NET was for . I wanted that room to experiment again: a .NET subset built for small, portable WebAssembly components, with a different answer to what should be included by default. If you have a small C workload you'd like to run as a WASI component, try NetWasm on GitHub https://github.com/zion-sati/NetWasm or paste it into the playground https://playground.netwasm.com/ . I'm especially interested in what breaks and which missing capability matters to you.