A spot matching engine is a small state machine surrounded by hard constraints: price-time priority, partial fills, a latency tail that does not move under burst load. This is how the order book, the matching loop, the journal and the replay harness are built, and where the language stops helping.
A spot exchange matching engine has a narrow job. It takes an ordered stream of commands - new order, cancel, replace - applies them to an order book under price-time priority, and emits an ordered stream of events: trades, book updates, rejects, acknowledgements.
Everything hard about it comes from three constraints stacked on top of that job: the result must be identical on every replay of the same input, the remainder of a partially filled order must keep its place in the queue, and the latency tail must not move when a burst arrives.
The book is two sides, each a price-ordered collection of levels. A level is not a number - it is a queue of resting orders at that price, in arrival order. Matching touches the best level constantly and the deep levels rarely, so the structure is chosen for that access pattern rather than for elegance.
The consequence of intrusive queues and index handles is that a resting order never moves in memory while it lives. Its queue position is a property of its links, not of where it happens to sit, which is what makes partial fills cheap later on.
An incoming aggressive order walks the opposite side from the best price inward. At each level it walks the FIFO queue from the front. It stops when the level price is no longer acceptable to the incoming order or when the incoming quantity reaches zero.
A partially filled resting order keeps its place. Its remainder stays at the front of its queue with its original arrival sequence, because a fill changes a quantity and nothing else. A partially filled aggressive order that is a plain limit becomes a resting order at the back of its own price level, with a new arrival sequence - it arrived now, not earlier.
Order type semantics are decisions taken at the boundary of this loop, not inside it. Immediate-or-cancel drops the remainder instead of resting it. Fill-or-kill runs a dry pass first and either executes whole or rejects. Post-only rejects if the order would cross on arrival. Keeping these outside the loop means the loop stays the only place where book state changes.
Self-trade prevention, minimum quantities, and tick and lot validation belong before the loop as well. An order that reaches matching has already been proven well formed, so the loop has no error branches to slow it down or to disagree about.
Average latency is rarely the problem. The problem is the worst observation during a burst, which is when the engine matters most and when a stop-the-world pause is most likely to land. Under a managed runtime with a garbage collector, that pause is scheduled by the collector rather than by you, and it lands in the middle of the burst that produced the garbage.
Manual allocation is a smaller version of the same problem. A general purpose allocator can walk free lists, take a lock, or ask the kernel for more memory, and the call that does so is the call that shows up in the tail. The fix is the same in either case: do not allocate on the hot path at all.
An engine is deterministic when the same input sequence produces the same output sequence, byte for byte, on a different machine and a year later. Anything that reads wall clock time, thread scheduling or hash iteration order inside the matching path breaks that property.
Timestamps are therefore an input, not something the engine reads for itself. The sequencer stamps a command when it accepts it, and the matching loop treats the stamp as data. Randomness, if any is needed, comes from a seeded generator whose seed is part of the journal.
Recovery is not a feature bolted on after matching works. The engine writes an append-only journal of accepted commands in sequence order, and the in-memory book is nothing more than the result of folding that journal. Rebuilding after a crash means replaying it.
The engine writes the journal, but durability is a property of the storage path and of how many machines have the record before the acknowledgement goes out. That is a replication and hardware decision, and it is where recovery time is actually won or lost.
Determinism is what makes the engine testable. Because the same input gives the same output, a captured session is a regression test, and a failure found once can be reproduced exactly instead of chased.
A reference model is worth more than it looks. Two implementations written from the same specification disagree in exactly the places where the specification was ambiguous, and matching rules are full of ambiguity at the edges - crossed limits, zero remainders, cancels racing fills.
Rust removes a category of problem rather than making the loop faster by itself. There is no garbage collector, so no pause is scheduled behind your back. Ownership makes the single-writer discipline something the compiler enforces instead of something a code review has to notice. Slab handles and intrusive links, which are error prone in a language without lifetimes, are checkable here. Panics on integer overflow in debug builds catch a class of bug that silently corrupts a book.
The honest boundary is that most of what determines tail latency is not the language:
Rust also costs something. The borrow checker slows down the first weeks of a design that is still moving, the ecosystem for exchange specific protocols is thinner than in older languages, and unsafe blocks around lock-free structures need the same review discipline as the equivalent code anywhere else. Choosing it is a decision about the latency tail and about memory safety in a single-writer core, not a decision about developer comfort.
amBrain has built trading infrastructure in Yerevan, Armenia since 2019, with hot paths in Rust - market data delivered in under 5 ms, pre-trade risk checks in under 1 ms. If you are designing a matching engine and want to walk through the book structure, the journal format or the replay harness, that conversation is worth having before the first line of the hot path is written.
Our engineering team specializes in FinTech solutions. Let's discuss how we can bring your project to life.