Skip to content

Mojo (2026)

A Rust-like ownership system with simpler call-site syntax, taped onto a Python-shaped surface, sitting on MLIR, headed to open source in fall 2026.

§1 Provenance

§2 Core type discipline

Mojo bakes single-owner discipline into argument conventions, not types. The three keywords are:

  • read (was borrowed pre-2024): immutable borrow, callee may read, not mutate.
  • mut (was inout): mutable borrow, callee reads and writes the caller’s storage.
  • var (was owned): callee takes ownership; caller’s binding is dead after the call.

Defaults:

  • fn-defined functions default arguments to read (Mojo’s safe surface).
  • def-defined functions default arguments to var (Python-compat surface; copies are made for caller-isolation).

Transfer operator: f(a^) explicitly moves a into f, regardless of the parameter convention. The compiler will perform this implicitly when it sees the call is the last use of a (NRVO-style).

Judgement form: a flow-sensitive last-use analysis across MIR-equivalent. The borrow checker tracks:

  1. Aliasing-XOR-mutation per stack slot.
  2. Move-after-use forbidden.
  3. Lifetime / provenance parameters on references that escape (the active “lifetimes & provenance” RFC formalises these as second-class capability-style markers, not full Rust regions).

Principal example: the Mojo read/mut/var triple lets a numerics function read its inputs without copying tensors and mutate an output in place:

fn matmul(read a: Tensor, read b: Tensor, mut out: Tensor): ...

The caller writes matmul(a, b, out) with no & sigil. The borrow checker proves a, b, out are disjoint.

§3 Memory-safety invariant

Same as Rust: aliasing-XOR-mutation, no UAF, no double-free, no data race on memory. Mojo additionally claims Python-interop without aliasing leaks: when a Python object enters Mojo it is wrapped in PythonObject, which is RC’d, opting out of the borrow checker for that subgraph.

§4 Compiler implementation cost

  • Built on MLIR, not bare LLVM. Borrow checking is one of several MLIR dialects/passes.
  • Lattner has stated the borrow checker took less code than Rust’s because lifetimes are second-class (parameters cannot be parameterised over lifetimes the way Rust’s <'a> does), so the inference engine is simpler.
  • Diagnostics: better than early Rust because the team learned from a decade of Rust’s mistakes; you get suggestions like “did you mean to write f(a^)?” automatically.
  • The compiler is closed source as of May 2026; commitment to open source is fall 2026. Until then, third-party verification of the soundness claims is limited.

§5 Production / language adoption status (May 2026)

  • Modular ships Mojo inside its MAX AI runtime; the production-ready Mojo release is Q1 2026.
  • Standard library is open; compiler closes for now.
  • Major adopters: Modular’s own kernels (matmul, attention), Lambda Labs and a handful of HPC shops experimenting.
  • Mojo is the only post-Rust language that pairs a borrow checker with first-class GPU/TPU codegen. That, plus the Python surface, is the differentiator.

§6 Mochi adaptation note

Mojo’s design choice is the most directly relevant to Mochi: borrow conventions on parameters, no lifetime variables, no & sigil at call site. That is exactly the shape MEP-41 should target.

Smallest surface-language change MEP-41 could lift verbatim:

fun fill(read src: list<int>, mut dst: list<int>) { ... }
  • read (alias for current implicit default): documentation only, no extra check.
  • mut: type checker rejects the call if the caller’s argument expression is not assignable, and warns if two mut arguments alias.
  • var: caller’s binding is removed from scope after the call; only legal in a var (mutable) binding context.

The transfer operator f(a^) is too cryptic for Mochi’s style; instead, an explicit consume a statement before the call (or built-in f(consume a)) is more readable.

vm3 tie-in: a var parameter conceptually moves a handle Cell. The runtime does nothing different — handle is copied, then source slot is set to null. Optionally, the gen on the moved-from slot is bumped, giving any leftover handle a fast UAF trap.

Effects (MEP-15) tie-in: a mut parameter is an effect on the caller’s storage. MEP-15 could grow a local effect (writes to caller-visible bindings) and propagate it, completing the symmetry with io/fs/net.

Options (MEP-16) tie-in: var over an Option<T> plus a static check that the source is Some gives a panic-free take. This is the exact Swift Optional.take() idiom (SE-0437).

Incompatible pieces:

  • Mojo’s lifetimes / provenance system. Mochi has GC + generations; there is no need for static lifetime parameters.
  • The ^ transfer operator (visual noise that does not fit Mochi’s surface).
  • Python interop. Mochi’s FFI is into Go, not Python; the analogous problem (a Go pointer that outlives a Cell) is different.

§7 Open questions for MEP-41

  1. Does Mochi want a def / fn split where def is permissive (auto-copy) and fn enforces the borrow conventions? This buys gradual adoption.
  2. Should mut propagate to closure captures the way Mojo’s does?
  3. Can MEP-41’s var convention be implemented as a bytecode transform (zero source slot on call) without touching the type checker?
  4. Mojo defers full lifetime tracking to a future provenance system. Does MEP-41 want to defer the same way, or commit to vm3’s gen-check as the permanent answer?

Sources: https://docs.modular.com/mojo/manual/values/ownership/ ; https://www.modular.com/blog/deep-dive-into-ownership-in-mojo ; https://github.com/modular/modular/discussions/338 ; https://en.wikipedia.org/wiki/Mojo_(programming_language) .