Galoy Talk to us
← Research
Engineering

Rust for Core Banking: A Language That Says No

Tim Normark7 min read
Diagram of Rust allowing permitted actions to reach the Lana banking core while blocking forbidden actions.

The story of how Rust went from a side project to one of the world’s most-loved programming languages begins with a broken elevator.

In 2006, Graydon Hoare became frustrated when the elevator in his building stopped working because of a software failure. That frustration became part of the story behind the language he started building in his spare time.

It is a fitting origin for a language now used in Volvo cars, Android, the Linux kernel, and infrastructure at Cloudflare handling more than a trillion requests a day.

Core banking systems are another form of critical infrastructure. Modern society depends on them to keep money moving and to safeguard the savings people rely on for the futures of their families and businesses. Few systems affect our everyday lives more.

They also tend to remain in service for decades. The systems we build today will be modified by engineers who are currently in kindergarten, deployed on infrastructure not yet invented, and adapted to regulations and feature requests we cannot possibly anticipate.

Safety and Correctness Come First

When we set out to build our core banking platform, Lana, the primary thing we optimized for was long-term guarantees of safety and correctness.

We were not primarily looking for the fastest language, the simplest syntax, or the easiest language to work with. We wanted one that would help us keep a large financial system safe and correct through decades of change.

Rust was built around similar priorities. The Rust project describes reliability as one of the language’s defining goals, with its type system and ownership model designed to eliminate entire classes of bugs before the software runs.

That alignment made Rust a natural fit. But not all of its strengths mattered equally to our decision.

The Usual Selling Points Were Not the Point

Rust is fast, and it operates safely without a garbage collector. Both are valuable, but neither had much influence on our decision.

Core banking workflows are usually constrained more by databases, external systems, and other I/O than by application runtime. Avoiding garbage collection can matter greatly in latency-sensitive systems, but that was not our concern either.

What mattered was how Rust achieves safety without a garbage collector: its ownership model. The compiler tracks who owns a value, who may access or change it, and for how long.

These rules reach far beyond memory management. We use them to restrict what different parts of our banking system are allowed to do.

Rust offers many other benefits, but the ones that follow are the ones that mattered most to our decision.

We Chose a Language That Says No

Rust’s real value for us is not what it allows, but what it refuses to allow.

Permission to Change Is Not Permission to Submit

Rust can distinguish between permission to change something and authority to finalize it:

Rust · Permission to Change Is Not Permission to Submit
fn add_processing_fee(payment: &mut PaymentDraft) { ... }
fn submit(payment: PaymentDraft) -> SubmittedPayment { ... }

add_processing_fee can modify the draft, but it cannot submit it because submission requires ownership. Code that calculates a fee should not also be able to send the customer’s money. Giving it that authority later requires changing the function signature, making the increased authority visible.

submit takes ownership of the draft, so it cannot be changed or submitted again afterwards.

Adding a fee borrows and modifies a payment draft; submitting consumes the draft and produces a submitted payment.

The function signatures act as capability boundaries inside the application, making important business rules harder to bypass as the system evolves.

We use the same idea elsewhere to make actions impossible unless the right conditions have already been met:

  • an operation cannot proceed until its required audit record has been created
  • code cannot make external service calls while holding an open database transaction
  • state that has been finalized cannot be modified or finalized again

These business and architectural rules become compile-time errors rather than conventions developers have to remember.

A Compile-Time Read-Write Lock

Rust applies the same philosophy to mutation:

Rust · A Compile-Time Read-Write Lock
fn read_balance(account: &Account) -> Money { ... }
fn deposit(account: &mut Account, amount: Money) { ... }

An &Account gives read access. An &mut Account gives permission to change it.

Rust allows any number of readers or one writer, but never both at the same time. You can think of this as a read-write lock enforced by the compiler. The rule itself requires no runtime lock: code that breaks it simply does not compile.

Diagram contrasting many readers accessing an account with a single writer updating it while reads are excluded.

This prevents data races, but the benefit goes further. In a banking system full of state that must change carefully, exclusive mutation makes those changes easier to reason about. When data is being changed, Rust forces the program to be explicit about who has the authority to change it. It also gives readers a strong guarantee: the value they are reading cannot be changed while they borrow it.

Functional programming recognizes the same problem and often avoids shared mutable data altogether. Rust instead allows mutation only when the compiler can prove exclusive access. For this problem, we get the same safety as immutability without giving up the simplicity of changing state directly.

The Billion-Dollar Mistake

Null references are another dangerous assumption many languages allow. Tony Hoare, who introduced them in 1965, later called the decision his “billion-dollar mistake” after decades of errors, vulnerabilities, and crashes.

Safe Rust does not allow them. A value that might be missing must say so in its type and be handled before use. Rust refuses to let “this probably exists” remain an unchecked assumption.

AI Changes the Tradeoff

AI-assisted development is making code faster and cheaper to produce, and we do not think banks can simply opt out of that shift. The question is how to capture those productivity gains without weakening the safety and correctness financial systems require.

Historically, Rust’s safety guarantees came with a productivity cost: developers spent more time satisfying the compiler. AI changes that tradeoff. Coding agents are particularly good at responding to compiler feedback and iterating until the code satisfies Rust’s type and ownership rules. Microsoft Research has explored this approach directly with RustAssistant, using compiler feedback to guide an LLM toward code that satisfies Rust’s type and ownership rules.

A coding agent submits code to Rust compiler rules. Compiler feedback returns to the agent for another iteration, while accepted code proceeds.

That gives us a combination we find compelling: AI makes code cheaper to produce, while Rust raises the bar for correctness that the generated code must meet.

As AI becomes more capable and widespread, we think strong compiler-enforced constraints become more valuable, not less. For critical financial infrastructure, that pushes the tradeoff further in Rust’s favor.

Alternatives and Costs

Rust was not the only reasonable choice. Java is a strong alternative for core banking: mature, widely used in finance, backed by an enormous ecosystem, and supported by a much larger talent pool. Functional languages such as OCaml and Clojure also offer strong tools for modeling business logic and controlling complexity.

A capable team could build a good core banking system with any of them.

We chose Rust because its combination of safety, correctness, and compiler-enforced constraints fits particularly well with what we want from a core banking platform. But that choice has real costs: a smaller talent pool, longer onboarding, and time for engineers to become productive with its ownership model.

Those are tradeoffs we knowingly accept in exchange for stronger guarantees in a system we expect to maintain for decades.

The Refusal Is the Feature

Rust lets us turn important invariants and boundaries into rules the compiler can enforce long after the original code was written and its authors are gone. The value of those rules only grows as the system ages, more developers contribute, and more code is produced with AI assistance.

In core banking, a compiler error is far cheaper than a financial error. Strict guardrails around safety and correctness are exactly what we want for our system.

That is why we chose a language that says no.

Further Reading

Build with Galoy

We build banking infrastructure for institutions that put safety and correctness first.

Talk to us