跪拜 Guibai
← Back to the summary

MoonBit, Rust, and Go Compete on WASM Binary Size and Raw Speed

Recently, while researching WASM performance, I ran a test across different programming languages.

I implemented the same Fibonacci algorithm in Rust, Golang, and MoonBit, compiled them all to WASM, and checked which produced the smallest binary and ran the fastest.

To keep things fair, all three languages used 64-bit integers for the comparison.

After testing, the results were quite representative.


Why test this?

Honestly, WASM has been getting hotter over the past few years.

High-performance computing in the browser, edge functions, serverless, and mini-program plugins all depend on it.

But here's the question: which language is more suitable for writing WASM?

Rust has the most mature ecosystem, Go is the backend veteran, and MoonBit is a new language designed specifically for WASM and cloud-native in the last couple of years.

I've been using MoonBit myself, so I've always wanted to know: when compiled to WASM, how does it compare to Rust and Go?

This time, I used a very straightforward Fibonacci algorithm, uniformly implemented with 64-bit integers, to do a simple horizontal evaluation across two dimensions: binary size and execution speed.


Test Environment

The algorithm included recursive and iterative versions, with test parameters of n=30, n=35, and n=40.

All three implementations were compiled into WASM that could be loaded and run in Node.js, invoked under the same JS host environment.


Binary Size Comparison

First, let's look at how large the compiled outputs are.

Language Raw Size Gzipped
MoonBit 211 B 178 B
Rust 286 B 235 B
Go 1.84 MB 565.78 KB

MoonBit is 211 bytes, Rust is 286 bytes — both are tiny.

For a new language, MoonBit's ability to compress its output to this level shows its compiler is indeed optimized specifically for WASM.

Rust is no slouch either; 286 bytes is also top-tier.

Go, however, comes in at 1.84 MB.

The reason is well-known: Go's WASM output must include an entire runtime — GC, scheduler, goroutines, the works.

This is a design choice by Go, not a flaw, but it's simply not suitable for size-sensitive scenarios.


Performance Comparison

Recursive Fibonacci

n Rust MoonBit Go
30 5.74 ms 5.00 ms 14.24 ms
35 55.77 ms 50.03 ms 154.23 ms
40 667.43 ms 662.88 ms 1.70 s

In the recursive scenario, MoonBit and Rust are very close, with MoonBit slightly faster.

Go is slower; at n=40, it's about 2.5 times slower than MoonBit.

This result isn't surprising. Go has runtime and GC overhead under WASM, and recursion, a scenario with heavy function calls, isn't its strong suit.

Iterative Fibonacci

n Rust MoonBit Go
30 35.22 ns 14.70 ns 21.29 µs
35 38.52 ns 12.04 ns 18.33 µs
40 43.34 ns 12.29 ns 16.05 µs

The gap in the iterative version is even more pronounced.

MoonBit is in the 12–15 nanosecond range, Rust is 35–43 nanoseconds, and Go is 16–21 microseconds.

Note the units: Go is in microseconds, the other two are in nanoseconds.

Go's slowness here isn't due to a bad algorithm, but because the runtime overhead of JS calling Go WASM is too high.

Every call must pass through Go's JS interop layer, with parameters being converted back and forth, and the GC butting in — lightweight tasks simply can't handle it.


My Take

Honestly, this test left me with three strong impressions.

First, MoonBit performed well in this test.

It had the smallest output and the fastest speed, and it doesn't just compile to WASM.

It can output to WASM, JavaScript, and Native backends, with syntax features like GC, pattern matching, and a strong type system. Its accompanying IDE and package management are also fairly complete.

From my actual usage experience, if you're working on edge functions, serverless, or mini-program plugins — scenarios sensitive to binary size and cold starts — MoonBit is an option worth considering.

Of course, its ecosystem is still growing, and there's still a gap in third-party libraries and community size compared to Rust.

Second, Rust remains the safe bet for WASM.

Its output is 286 bytes, its performance is in the same league as MoonBit, and its ecosystem maturity is far ahead.

Toolchains like wasm-bindgen, wasm-pack, and wasm-opt are already very polished, and you can generally find solutions to any problems you encounter.

If you're building a long-term maintenance project with a larger team, Rust is still the more stable choice right now.

Third, the standard Go compiler's WASM output is indeed on the large side.

But this is a design trade-off by Go; it was originally designed for server-side, high-concurrency, rapid development.

The large output is due to the bundled runtime, and the high call overhead is due to the frequent conversions needed between JS and the Go runtime.

If you already have a lot of Go code to reuse, or your team members are all Go developers, using standard Go to compile WASM is acceptable.

But if you're choosing a language from scratch, TinyGo would be a more suitable WASM solution than standard Go.


Final Three-Sentence Summary

What do you think? Feel free to leave a comment.

Thanks for reading. I'm Chen Suiyi, the Frontend Tiger. Official account: 陈随易, personal website: https://chensuiyi.me.