July 7, 2020
I recently decided to switch the engine of Boardgame Lab from TypeScript to Rust. The application itself is an SPA written in Svelte. I only switched the logic that updates the game state to Rust. Here is a summary of my experience with the transition:
With WebAssembly taking off, you can get the same advantage writing Rust. Instead of pushing client-side code to the server, we push server-side code to the client by compiling Rust to WebAssembly.
This Rollup plugin allows you to import a Cargo.toml file into your TypeScript codebase, allowing a seamless integration between Rust and TypeScript code. The dev experience is almost as smooth as writing TypeScript itself (for example, the browser refreshes automatically when you change a line of Rust code).
wasm-bindgen facilitates serialization of Rust structs into JSON objects and vice-versa.
Coding in TypeScript has been a largely pleasant experience, but switching to Rust immediately brought to light some of the limitations of TypeScript.
TypeScript is more of a type-hinter than a type-checker. It primarily ensures that you’re not accessing fields that you aren’t declaring via the type system (which is quite expressive). It also enables nicer autocomplete for your IDE.
However, it does not actually ensure that the data you are manipulating corresponds to the type that you have declared to represent it. For example, the data might contain additional fields or even incorrect values for declared types.
You have to write data validation code to ensure that you’re operating on correct data in TypeScript. You get this for free in Rust, which will throw an error if you parse data that doesn’t line up with the struct that is to hold it in memory.
This leads to a great degree of confidence that if it compiles, it’s probably not going to throw an error at runtime.
The initial rewrite without any performance optimizations is already faster than the previous TypeScript codebase. This doesn’t matter too much yet, but Boardgame Lab will eventually ship with bots, so the performance will matter once game trees need to be searched.
The multiplayer server receives updates from clients via WebSockets and then:
Overall, I’m pretty happy with the switch. I haven’t really encountered much resistance from the borrow checker despite being fairly new to Rust (I come from a C++ background, so maybe that helps).