Early access for Channel Partners and ISVs opening soon. Learn more →

Engineering

Why the FastYoke core is written in Rust

FastYoke Engineering · 9 min read · Apr 11, 2026

  • Rust
  • Architecture
  • Multi-tenancy

One process, every tenant's data

FastYoke's engine is a monolith by design: one process holds the state machines, the schemas, and the request handlers for every tenant on that node. That's a deliberate simplicity trade — fewer moving parts, fewer network hops, one thing to operate. But it raises the bar on correctness. In a single-tenant app, a memory-safety bug is typically a crash. In an engine that serves many organizations from one process, that same class of bug carries a much higher cost — which is exactly why we wanted it designed out of the core at compile time rather than guarded against at runtime.

And the deployment story makes the stakes higher, not lower. The same binary that runs in our cloud also has to run inside a customer's own data center, on a small edge box, or fully air-gapped with no one around to babysit it. That rules out a fleet of heavyweight side-processes to paper over a fragile core. We needed a language where a whole category of memory bugs simply doesn't compile, wrapped in a single binary small enough to hand someone and say "run this."

This post is about why that language is Rust.

Why we ruled out a garbage-collected runtime

The obvious alternative is a garbage-collected language — the default choice for most backend services, and for good reason: fast to write, forgiving, huge ecosystems. We considered it seriously and ruled it out for two reasons specific to what FastYoke's engine actually does.

The first is latency predictability. FastYoke's core job is evaluating state-machine transitions and guard logic on a request path where the caller is waiting on an answer. A garbage collector reclaims memory on its own schedule, not yours, and every GC'd runtime pays for that with pause behavior somewhere on the spectrum from "usually fine" to "occasionally not." When you're trying to guarantee that a transition evaluates in single-digit milliseconds regardless of how many other tenants are active on the box, "usually fine" isn't good enough — you want a runtime that never takes memory management out of your hands at an inconvenient moment.

The second is footprint. A GC'd runtime brings its own scheduler, its own memory manager, and typically a chunkier baseline resident-memory footprint before your code has done anything at all. That's a real cost when the deployment target is "small edge box" or "one VM handling many tenants" rather than an elastic cloud fleet with fungible compute to throw at the problem.

None of this is a knock on garbage-collected languages in general — they are the right call for a huge share of software, and their design tradeoffs are well documented by the language communities themselves; see the Rust project's own comparison of its ownership model against tracing-GC languages for how that tradeoff is usually framed. It just isn't the right call for a multi-tenant FSM engine that has to be both predictable per-request and light enough to run anywhere.

The decision: Rust

Rust gives us memory safety enforced at compile time, without a garbage collector. The compiler's ownership and borrow-checking rules mean that whole classes of bugs — use-after-free, double-free, data races on shared memory — are rejected before the binary is ever produced, not caught later by a fuzzer or, worse, in production. There's no runtime deciding when to reclaim memory and no pause to reason about; ownership is tracked statically, and cleanup happens deterministically when a value goes out of scope.

That compile-time guarantee is not a FastYoke-specific opinion — it's increasingly the position of the people whose job is defending production software at scale. The U.S. Cybersecurity and Infrastructure Security Agency has made the case directly: adopting memory-safe languages eliminates entire vulnerability classes that otherwise show up release after release, and it's specific enough to publish as guidance for how organizations should plan the transition — see CISA's "The Case for Memory Safe Roadmaps". We didn't pick Rust because it's fashionable; we picked it because the failure modes we care most about in a shared-process engine are exactly the ones this class of language is built to remove.

The rest of the language earns its keep too. Rust's async ecosystem (we build on tokio and the Axum web framework) gives us "fearless concurrency" — the same ownership rules that stop memory bugs also stop a large share of the data-race bugs that make concurrent code so easy to get wrong in other languages. And Rust compiles to a single statically linked binary: no separate runtime to install, no matching interpreter version to babysit on the target machine, no dependency tree to reconcile before it'll start. You copy one file and run it.

Design decision: let the type system hold the multi-tenancy rule

Compile-time memory safety is the headline, but the same discipline shows up in how we enforce FastYoke's most important invariant: every access to tenant data must be scoped to the tenant that owns it. That rule can't live only in a code-review checklist — checklists get skipped under deadline pressure. So we lean on Rust's type system to make tenant-scoped access the path of least resistance and unscoped access something you have to go out of your way to write, and we hold it to the same severity bar as a memory-safety issue.

The same "let the compiler carry the invariant" instinct shows up in how FastYoke's route handlers deal with failure. We don't allow .unwrap() or .expect() in a production request handler — a panic there would take down the request, and potentially the connection pool, for every tenant sharing that process, not just the one whose input triggered it. Instead every fallible path returns a typed error enum that gets converted into a structured JSON response. The type system won't let a handler compile without an error path being handled explicitly; there's no way to accidentally ship the equivalent of a null-pointer crash to a caller.

Design decision: one binary, one file per tenant, anywhere

The deployment shape follows directly from the language choice. FastYoke compiles down to a single binary that embeds the API and the served frontend together, and each tenant's data lives in its own local database file. There's no cluster to stand up, no matching runtime version to install alongside it, no fleet of sidecar processes for memory isolation the language itself already gives us.

That's what makes the deployment envelope FastYoke actually supports — our own cloud, a customer's data center, an edge gateway, or a fully air-gapped install with no outbound network at all — realistic instead of aspirational. A team can hand the binary to an environment we've never seen, with no toolchain to install first, and it behaves the same way it does in our own infrastructure, because there's no separate runtime in the loop to drift out of sync.

The payoff

Put together, the Rust core buys FastYoke four things at once:

Safety. The tenant-isolation boundary inside a shared process is backed by a compiler guarantee, not just application-level discipline. The bug class we were most worried about is one the toolchain rejects before the binary exists.

Density. Per-tenant SQLite files plus a language with no garbage collector and a small runtime footprint mean we can run many tenants' workloads on one node without each one paying the tax of its own heavy runtime.

Portability, and with it sovereignty. A single statically linked binary with no external runtime dependency is what makes "run this identically in the cloud, on-prem, at the edge, or air-gapped" a real answer instead of a slide. Customers who need their data and logic to stay inside their own walls can have that, running the exact same core.

Correctness. Types that encode the rules we actually care about — scoped-by-tenant queries, no silent panics in request handlers — turn invariants we'd otherwise have to remember into invariants the compiler remembers for us.

Where to go next

The Rust core is the foundation; it's not the whole isolation story. Once tenant logic itself needs to run — guards, scripts, decision rules authored by customers — we reach for a second, complementary sandbox on top of this same core. We wrote about that decision in why FastYoke chose WebAssembly.

If you want to see what running on that core looks like for your own workloads, take a look at FastYoke's runtime or talk to us about building on FastYoke.