Rust, part 4: Rust lands in the Linux kernel
Part 4from the Rust series · 15 parts in all
In December 2022, Linux 6.1 shipped the first Rust code in the kernel tree. It is easy to file that under "historical curiosity" and it would be a mistake: the constraints the kernel put on Rust are the constraints of the hardest embedding problem there is, and the way the project answered them says a lot about what the language is now for.
Why it took years to say yes
The kernel is not written in C because of inertia. It is C because C gives you:
- No runtime. No allocator you did not ask for, no unwinding, no
thread-local machinery. Rust's
no_stdcovers this, but only the parts of the language that were designed to survive it. - Total control of memory layout. Structs are passed across an ABI that
C defines. Rust's
repr(C)is a partial answer; anything else is not allowed at the boundary. - Concurrency rules the compiler cannot know. The kernel has its own locking conventions and its own notion of what may block. A foreign language must integrate with them rather than impose its own.
What was actually merged
The first commit was not a driver. It was the infrastructure — enough to write a minimal module, with the abstractions built in-tree:
// A kernel module, in the shape the in-tree `kernel` crate allows.
// no_std, no libc, and panics that abort rather than unwind.
#![no_std]
use kernel::prelude::*;
module! {
type: Hello,
name: "hello_rust",
author: "Bob Huang",
description: "a minimal module",
license: "GPL",
}
struct Hello;
impl kernel::Module for Hello {
fn init(_module: &'static ThisModule) -> Result<Self> {
pr_info!("hello from Rust\n");
Ok(Hello)
}
}
Note what is absent: no std, no alloc by default, no
Box::new, and pr_info! is a kernel macro rather than
println!. The interesting part is the Result in
init — a module that fails to initialise returns the error rather than calling
panic!, which is a language-level answer to a kernel-level problem.
What it changed outside the kernel
Even if you never write a driver, the effort paid back into the language and the ecosystem, and that is the part worth knowing:
no_stdgot real attention. Fallible allocation, panic-in-abort, and the ergonomics of writing for a target with no operating system underneath.- The stable-ABI question got an answer. Not "Rust will stabilise its
ABI" but "you interoperate through
repr(C)andextern \"C\", explicitly" — which is part 11. - Rust stopped being described as a C replacement and started being described as a C complement: new code in Rust, existing code untouched, sharing one build.
It also set the pattern every other large codebase has followed since: adopt incrementally, at the leaves, through a C ABI. Next: the type-system feature that decides which of your function calls are indirections — traits, generics and monomorphization.