9 min readbackend

Rust in the Linux Kernel is no longer an experiment — what that means for the rest of us

In late 2025 the kernel maintainers declared Rust 'no longer experimental.' The real story isn't speed — it's a changed risk model for systems code. Here's what the milestone means from the architecture, developer, and DevOps angles, even if you never touch a kernel.

On this page

# The Problem

For thirty years the deal with systems programming was simple and brutal: you got performance and control, and in exchange you accepted that a single off-by-one, a stale pointer, or a forgotten kfree could corrupt memory somewhere else entirely and hand an attacker the keys. Roughly two-thirds of the serious vulnerabilities in large C codebases — browsers, kernels, TLS libraries — trace back to exactly this class of bug. Not logic errors. Memory errors. Use-after-free, double-free, out-of-bounds, data races.

We built an entire industry around coping: fuzzers, sanitizers, static analyzers, KASAN, an ocean of code review. All of it valuable, all of it probabilistic. None of it makes the bug class impossible.

In December 2025, something shifted. At the Linux Kernel Maintainers Summit, the consensus was blunt: Rust in the kernel is no longer an experiment. LWN called it “the (successful) end of the kernel Rust experiment.” Phoronix confirmed a patch declaring Rust “here to stay.” The New Stack said it had gone mainstream. For the most conservative, most scrutinized C codebase on the planet to say that out loud is a genuine inflection point — and the lesson generalizes well beyond people who write device drivers.


# What Actually Happened

Let’s be precise, because the headlines invite hype. As of early 2026 the kernel holds roughly 25,000 lines of Rust against ~34 million lines of C. Rust did not “take over” anything. What it did was cross the line from “interesting experiment” to “a permanent, load-bearing part of how new drivers and subsystems get written.” The proof points that landed through H1 2026:

  • NVIDIA’s Nova GPU driver, written entirely in Rust, hit major milestones — twelve iterations of Hopper and Blackwell enablement by June 2026.
  • Google reported zero memory-safety bugs in the Rust Android driver code shipped to production so far — across a fleet measured in billions of devices.
  • Debian committed to a “hard Rust requirement” in APT from May 2026, meaning mainstream distributions now ship Rust in the base toolchain by default.
What 'no longer experimental' actually means

It doesn’t mean the kernel is being rewritten. It means the maintainers have accepted Rust as a supported, permanent language for new code — with the review processes, tooling, and long-term maintenance commitments that implies. The beachhead is at the driver and subsystem boundary, and it’s expanding.


# The Architecture Perspective

The interesting claim isn’t “Rust is fast” — C is fast too. The claim is that Rust changes the risk model of systems code. It takes an entire category of bug and makes it unrepresentable at the safe boundary. That’s an architectural property, not a micro-optimization.

But the architecture is subtler than “rewrite in Rust.” Kernel Rust is safe abstractions layered over an inherently unsafe C core. You don’t get to pretend the 34 million lines of C don’t exist. Instead you build a thin unsafe binding layer that talks to the C subsystems, and above it a safe Rust API that upholds the invariants C leaves to convention. Driver authors then live almost entirely in the safe layer.

flowchart TD
    subgraph K["Kernel"]
        C["C core subsystems<br/>(34M lines, unsafe by nature)"]
        BIND["unsafe Rust bindings<br/>(thin, audited, invariant-checked)"]
        SAFE["safe Rust 'kernel' crate API<br/>(RAII, ownership, Result)"]
        DRV["Rust driver<br/>(e.g. Nova GPU)"]
        C --> BIND --> SAFE --> DRV
    end
    DRV -.->|"bug class removed at this layer"| WIN["No use-after-free<br/>No double-free<br/>No data races"]

    classDef unsafe fill:#dc2626,color:#fff,stroke:#991b1b;
    classDef bridge fill:#d97706,color:#fff,stroke:#92400e;
    classDef safe fill:#059669,color:#fff,stroke:#065f46;
    classDef win fill:#2563eb,color:#fff,stroke:#1e40af;
    class C unsafe;
    class BIND bridge;
    class SAFE,DRV safe;
    class WIN win;

This layered model is the transferable architectural idea, and it’s why this post matters even if you never send a kernel patch. Any organization writing long-lived, safety-critical systems code — a network data plane, a storage engine, a hypervisor, an embedded firmware image — faces the same choice the kernel community just worked through. The pattern is: introduce memory safety at a boundary, wrap the unsafe world in an audited layer, and write new code in the safe layer. You don’t rewrite the world; you change where new risk accrues.

The generalizable lesson

The kernel added ~25k lines of Rust alongside 34M lines of C — not instead of it. The winning strategy is incremental adoption at a boundary, not a rewrite. Wherever you have a mature C/C++ core and a steady stream of new, security-sensitive code, this same “safe-over-unsafe” seam applies.


# The Developer Perspective

What does writing kernel Rust actually feel like? The headline experience is that the compiler enforces the cleanup discipline you used to hold in your head.

In C, resource management at the hardware boundary is a manual, error-prone ritual — the infamous goto err_free ladder, where one misplaced label leaks a registration or frees something twice:

/* C: cleanup is your responsibility, and the compiler won't help */
dev = alloc_device();
if (!dev) return -ENOMEM;
ret = register_device(dev);
if (ret) goto err_free;      /* miss one of these paths -> leak or UAF */
ret = enable_irq(dev);
if (ret) goto err_unregister;
return 0;

err_unregister:
    unregister_device(dev);
err_free:
    free_device(dev);
    return ret;

In Rust, ownership and RAII fold that ritual into the type system. When a value goes out of scope — on any path, including an early ? return — its Drop runs. The registration is released because the compiler guarantees it, not because you remembered:

// Illustrative kernel-style Rust: cleanup is automatic and total.
fn probe(dev: &Device) -> Result<()> {
    let registration = Registration::new(dev)?;  // Drop unregisters, always
    let _irq = registration.request_irq()?;      // Drop frees the IRQ, always
    // ... use the device ...
    Ok(())
    // On return OR on any `?` early-exit, both are cleaned up in reverse order.
}

Two things a developer coming from C or from userspace Rust has to internalize:

unsafe doesn’t disappear — it gets contained. Talking to the C world requires unsafe bindings. The discipline is to keep those blocks small, document the invariant each one upholds (// SAFETY: dev is non-null and lives for 'a because ...), and let reviewers audit those lines with the same rigor they’d apply to all of C. Safety isn’t free; it’s localized.

Panics can’t unwind freely in-kernel. A stray .unwrap() is a very different hazard here than in a web service. Error handling leans hard on Result at the boundary, and idioms that would panic in application code are deliberately avoided.

The unsafe layer is where bugs still live

Rust in the kernel is safe abstractions over an unsafe core. The unsafe binding blocks don’t get memory-safety guarantees — they provide them to the layer above. Review them like you’d review the equivalent C: line by line, invariant by invariant. “It’s Rust” is not a substitute for that scrutiny.


# The DevOps Perspective

“Introduce a new language into a mature C codebase” is a build-and-operations problem long before it’s a correctness win, and the kernel community’s experience is a useful preview for any team considering the same move.

Your toolchain surface grows. Rust support means a Rust compiler, bindgen, and a matching toolchain version pinned into your build. Debian’s move to a hard Rust requirement in APT is exactly this playing out at distro scale: Rust stops being optional tooling and becomes part of the base image everyone ships. If you’re adopting Rust in your own systems stack, plan for the toolchain to become a first-class, version-pinned dependency in CI — not a developer’s local install.

flowchart LR
    SRC["Rust + C sources"] --> BIND["bindgen:<br/>generate FFI bindings"]
    BIND --> BUILD["Pinned Rust toolchain<br/>(reproducible in CI)"]
    BUILD --> LINT["clippy + custom lints<br/>audit unsafe blocks"]
    LINT --> ART["Signed artifact<br/>/ kernel module"]

    classDef step fill:#2563eb,color:#fff,stroke:#1e40af;
    classDef gate fill:#d97706,color:#fff,stroke:#92400e;
    classDef out fill:#059669,color:#fff,stroke:#065f46;
    class SRC,BIND,BUILD step;
    class LINT gate;
    class ART out;

The maintenance cost is the binding layer. The C APIs churn; your unsafe bindings have to track them. This is the single most cited operational burden from the kernel experience — not writing the drivers, but keeping the safe/unsafe seam in sync as the C side evolves. Budget for it as ongoing work, and treat binding updates as high-scrutiny changes.

Reviewer skill-up is a real line item. You now need reviewers fluent in both languages and, specifically, fluent in reasoning about unsafe invariants. The kernel community handled this by keeping the Rust surface small and concentrated enough that a manageable group of maintainers could own it. That’s a deliberate operational choice you can copy: don’t sprinkle Rust everywhere; land it at a bounded subsystem so review expertise can concentrate.

Phase it at a boundary

The operational risk of “new language in an old codebase” drops sharply when you scope Rust to a single subsystem or a new component with a clean interface, rather than interleaving it throughout. Concentrate the toolchain, the review expertise, and the unsafe audit burden in one place.

Pros

  • Eliminates the memory-safety bug class at the safe boundary
  • Compiler-enforced RAII replaces manual cleanup
  • Incremental adoption alongside existing C
  • Field-proven: zero memory-safety bugs in shipped Android Rust drivers

Cons

  • unsafe binding layer still needs C-grade review
  • Binding maintenance tracks a churning C API
  • Larger toolchain and CI surface
  • Reviewer fluency in two languages required
  • Panics/unwinding constrained — different idioms

# Putting It Together

The story that made the headlines is “Rust reached the Linux kernel.” The story worth internalizing is that the most conservative systems codebase in the world concluded that the memory-safety dividend clears the cost — and that it clears it incrementally, at a boundary, without a rewrite. Google’s zero-memory-safety-bugs result from shipped Android Rust drivers is the strongest field evidence we have that the guarantee holds in production, not just in theory.

If you write long-lived systems code — data planes, storage, embedded, anything where a memory bug is a CVE — the kernel just ran the experiment for you and published the result.


# References

views

Click to show appreciation

Discussion