Tous les articles

Why Spacestocks is written in Go

We compared Go with Python, Node, Java, Rust and C#. Here is what decided it, and what Go costs us.

Cette page n'est pas encore traduite ; elle s'affiche donc en anglais.

The sun rising over Earth's horizon above Namibia, seen from the International Space Station.
Photo: NASA

Before writing any code we spent a while on one question: which language makes this particular exchange easiest to get right, to prove right, and to explain? We were not looking for the best language in general. We were looking for the one whose defaults fail loudly on the mistakes an exchange cannot afford.

What the exchange punishes

  • Numbers that are not exact. Every price, quantity and balance is a whole number that fits in 64 bits. A price of 10.5 must be rejected, not quietly rounded.
  • Errors in the wrong order. If a request has no valid token and a broken body, the answer must be 401, not 400. The order of checks is part of the contract.
  • Races. Two orders reaching for the same cash, a cancel racing a fill.
  • Slow recovery. The server is killed repeatedly while traders keep working.

What Go gives us

  • Strict numbers by default. Decoding JSON into an int64 rejects 10.5, "10" and 1e2 without any extra code.
  • Our own order of checks. Login first, then parsing, then lookups, then business rules, in plain code. No framework decides it for us.
  • A natural shape for the design. One goroutine per asset, receiving commands over a channel, is the standard Go way to have a single writer. And go test -race checks every test run for data races.
  • Fast restarts. The server is back in milliseconds after a crash.
  • One binary. Nothing to install next to it, so it runs the same on any server.

Where the others lost

Python with FastAPI is the fastest to write, and its tools for property-based testing are excellent. But FastAPI parses the JSON body before it runs authentication, so a broken body without a token gets a 422 instead of a 401. Its default number handling also accepts "10" and true as integers. Each of these can be configured away, but each one is a trap you have to know about.

TypeScript on Node would give us one language for everything. But every JSON number in JavaScript is a 64-bit float, so integers above 2^53 are rounded without a word. In a system whose first rule is “never create or lose money”, that is a risk we did not want to manage everywhere.

Java with Spring Boot is the industry’s default for backends, and that counts. But its JSON library accepts 10.5 as 10 by default, the MCP library supports the current protocol only in a pre-release, and the framework does a lot of work out of sight. We want every line of our code to be easy to explain, and hidden behaviour makes that harder.

Rust would rule out data races at compile time, but learning it well takes weeks we would rather spend on the exchange itself.

C# came closest. It is strict and fast, with good tooling. It is also the language our team knows least, and that decided it.

What Go costs us

Go is not free of traps either. Integer overflow wraps around silently, so we check every price × quantity ourselves, with a test. An empty list encodes as null unless we create it explicitly. There are no sum types, so a linter checks that every switch over order sides and statuses handles every case. And error handling is verbose.

We chose these costs on purpose. Each of them is visible in the code, and each has a test or a linter rule that keeps it in check.

Affichage

Thème
Taille du texte
Interligne
Lecture
Vision
Mouvement et navigation
Structure de la page

Titres

    Zones

      Enregistré dans ce navigateur uniquement.