Rust for Linux
Rust is rewriting the rules of kernel development — here is why that matters, how it works, and how you can write your first safe kernel module today.
Overview
For fifty years, the Linux kernel has been written almost entirely in C — a language that gives you extraordinary power over the machine but demands extraordinary discipline in return. A single misplaced pointer, a forgotten bounds check, a subtle use-after-free: these bugs have caused thousands of CVEs, enabled privilege escalations, and cost the industry billions. The kernel's developers are among the most careful programmers alive, and the bugs keep coming anyway. This is not a talent problem. It is a language problem.
Rust is a systems programming language designed from first principles to eliminate entire categories of those bugs — not by adding a garbage collector or a runtime, but by encoding ownership, borrowing, and lifetime rules directly into the type system. The compiler refuses to compile code that might cause data races or dangling-pointer dereferences. The bugs become impossible to express, rather than merely unlikely to write.
The Rust-for-Linux (RfL) project, initiated by Miguel Ojeda and merged into the mainline kernel tree starting with Linux 6.1 (December 2022), brings this guarantee into the kernel itself. Today you can write a Linux kernel module in Rust, and the abstractions layer — a set of safe Rust wrappers around the kernel's C internals — will prevent you from corrupting kernel memory in ways that would be trivially easy in C. This is not a rewrite of the kernel. It is a careful, module-by-module introduction of a safer language wherever new code is written.
This module takes you from the theoretical question of "why would you bring Rust into a C codebase?" all the way to building and loading a real Rust kernel module. Along the way you will understand the abstractions/bindings architecture, the ongoing debates in the community, and the frontier problems that make this one of the most exciting areas of kernel development today.
Memory-safety bugs are not a minority of kernel vulnerabilities — studies consistently show they account for roughly 65-70% of reported Linux kernel CVEs. Every privilege escalation that chains together a use-after-free with an information leak, every container escape that exploits a race condition in a driver: these are the bugs Rust is structurally designed to prevent. If you want to contribute to the kernel in 2025 and beyond, understanding Rust-for-Linux is not optional — it is the direction the project is explicitly moving toward for new subsystems.
Beyond safety, Rust-for-Linux represents a rare experiment in language co-existence at the systems level: two languages sharing the same address space, the same ABI, the same linker, calling each other's functions across the FFI boundary in both directions. The techniques you learn here — safe abstractions over unsafe interfaces, zero-cost type-system enforcement, the interplay between compiler guarantees and runtime invariants — are directly transferable to every domain where you need to write reliable low-level code. Kernel contribution is the destination; deep systems literacy is the journey.
Rust-for-Linux Layered Architecture
The three-layer RfL architecture: your safe module code calls the kernel crate's safe abstractions, which internally call the bindgen-generated unsafe bindings, which call into the C kernel. The unsafe surface area is concentrated in the bindings layer; module authors operate in safe Rust.
The concepts
01Why C is Dangerous in the Kernel (And Why That Matters)
C gives the programmer direct access to memory: you can compute any address, cast any pointer, and write to any location the OS permits. In userspace, the kernel can catch most mistakes — a segfault terminates the offending process, other processes are unaffected. In the kernel itself, there is no higher authority. A bad write corrupts kernel memory directly. The kernel cannot kill itself and recover; it either panics or, worse, silently operates in a corrupted state that an attacker can exploit.
The three most dangerous classes of memory bugs in C are:
Use-after-free (UAF): You allocate memory, store a pointer to it, free the memory, then later use the pointer. The pointer is now dangling — it points to memory that has been returned to the allocator and may have been reused for something else. In the kernel, UAF bugs can be exploited to overwrite kernel function pointers or data structures. CVE-2022-0185, CVE-2021-4154, and many others in recent years are UAF bugs.
Data races: Two kernel threads read and write shared memory without proper synchronization. In C, data races are undefined behavior — the compiler is free to assume they do not happen and may optimize away the very check you wrote to handle them. In the kernel this causes everything from torn reads (seeing half-written values) to silent corruption.
Buffer overflows and out-of-bounds accesses: Writing past the end of an array can overwrite adjacent kernel data structures. The C compiler has no obligation to check array bounds; it trusts you.
The critical insight is that these are not bugs caused by inattention. The Linux kernel has tens of millions of lines of code, touched by hundreds of contributors. The invariants that prevent a UAF — 'this pointer is valid until exactly this event occurs' — exist only in comments and in human memory. Rust moves those invariants into the type system, making them machine-checked.
/* C kernel code — UAF waiting to happen */
struct my_device {
struct list_head list;
int value;
};
static void dangerous(struct my_device *dev) {
list_del(&dev->list);
kfree(dev); /* dev is freed here */
/* ... some other code ... */
printk("%d\n", dev->value); /* UAF: dev is invalid now! */
}
02Rust's Ownership Model: Safety Without a Garbage Collector
Rust achieves memory safety through three interlocking rules enforced at compile time — no runtime overhead, no garbage collector pauses.
Ownership: Every value has exactly one owner. When the owner goes out of scope, the value is dropped (freed). This alone eliminates double-frees and most memory leaks.
Borrowing: You can lend a reference to a value without transferring ownership. The borrow checker enforces two rules simultaneously: (1) you may have any number of shared (immutable, &T) references, OR (2) exactly one exclusive (mutable, &mut T) reference — never both at the same time. This rule, at compile time, makes data races impossible: a data race requires two concurrent accesses where at least one is a write. The borrow checker structurally prevents that combination.
Lifetimes: Every reference has a lifetime — the scope in which it is valid. The compiler proves that no reference outlives the value it refers to. This eliminates dangling pointers. The compiler does not add runtime checks; it rejects programs it cannot prove are safe.
In userspace Rust, memory is freed when the owner is dropped (via the Drop trait), which calls the allocator's free. In the kernel, Rust uses the kernel's own allocator (kmalloc/kfree) via a custom allocator implementation. The same ownership rules apply — the compiler ensures you never use kernel-allocated memory after it is freed.
The key word is structural. A UAF is not a bug you forgot to check; it is a program shape that the Rust type system cannot represent without the unsafe keyword. When you see unsafe, you know exactly where to focus your code review.
// Rust borrow checker: the UAF is a compile error
fn dangerous() {
let v = vec![1, 2, 3]; // v owns the allocation
let r = &v[0]; // r borrows from v
drop(v); // v is freed — r is now dangling!
println!("{}", r); // ERROR: borrow of moved value
// The compiler refuses to compile this program.
}
// The data-race equivalent:
use std::sync::Arc;
fn no_races() {
let x = Arc::new(5u32);
// You cannot get a &mut while a & exists.
// Shared XOR Exclusive — enforced at compile time.
}
03The Rust-for-Linux Project: History and Architecture
The Rust-for-Linux (RfL) project was started by Miguel Ojeda in 2019, building on earlier explorations by other contributors. After years of RFC submissions, community debate, and iterative improvement, the core infrastructure was merged into Linux 6.1 (released December 2022) — a historic moment: the first time a second language was added to the mainline kernel in its 30+ year history.
What was merged: The build system integration (Kbuild support for .rs files), a rust/ directory in the kernel tree containing the core Rust support libraries, and the samples/rust/ directory with example modules. Crucially, this was infrastructure, not drivers.
The layers of RfL:
-
rust/kernel/— the kernel crate: This is the safe abstraction layer. It provides Rust types that wrap kernel C concepts:Device,File,Mutex,SpinLock,Task,Box(backed bykmalloc),Vec, etc. These types encode the kernel's usage rules into Rust's type system. For example,SpinLock<T>only allows access toTwhen the lock is held — enforced by the borrow checker, not a runtime assertion. -
rust/bindings/— the raw FFI layer:bindgen(a tool that auto-generates Rust FFI declarations from C headers) is run at build time against the kernel headers. This producesunsafeRust declarations for every C function and struct the kernel exposes. Everything inbindings/isunsafe; it is the raw, unguarded interface to C. -
Your module code: You write safe Rust against the
kernelcrate. You almost never touchbindings/directly. Thekernelcrate is the fence between unsafe C reality and safe Rust code.
The unsafe budget: Rust uses unsafe blocks to mark code that the compiler cannot verify. The RfL philosophy is to minimize the unsafe surface area: push unsafe down into the kernel crate's internals, and expose a safe API to module authors. A kernel module can then be written with zero (or near-zero) unsafe blocks, yet run in the kernel.
# Directory layout in the kernel tree (Linux 6.8+)
rust/
kernel/ # Safe abstractions (the fence)
sync/ # Mutex, SpinLock, Arc, etc.
net/ # Networking abstractions
fs/ # Filesystem abstractions
...
bindings/ # Auto-generated from C headers (unsafe)
alloc/ # Custom allocator backed by kmalloc
samples/rust/ # Example modules you can build
drivers/ # Real drivers (e.g. Apple M1 IOMMU, PHY drivers)
04The Abstractions/Bindings Architecture in Depth
The most technically interesting part of RfL is how the safe/unsafe boundary is managed. Let us trace a real example: a kernel mutex.
In C, a struct mutex is just a struct in memory. Nothing stops you from accessing the data it is supposed to protect without holding the lock — you just read the pointer. The mutex only works if every programmer in the entire codebase remembers to call mutex_lock before every access. The compiler enforces nothing.
In Rust's kernel::sync::Mutex<T>, the data of type T is stored inside the mutex struct. The only way to access T is through the lock() method, which returns a MutexGuard<T>. MutexGuard implements Deref<Target=T>, so you can use the data normally through the guard. When the guard is dropped (goes out of scope), the lock is automatically released via the Drop trait. You literally cannot access the protected data without holding the lock, and you literally cannot forget to release the lock — the compiler enforces it.
How is this built? The Mutex<T> struct in the kernel crate wraps a raw bindings::mutex (the C struct, obtained via bindgen). The lock() method calls bindings::mutex_lock() (the C function, called via FFI) inside an unsafe block. The unsafe is justified by a safety comment that proves the C function's preconditions are met. Then lock() returns the safe MutexGuard. All the unsafe is contained inside the implementation of Mutex<T>; callers see only safe Rust.
This pattern — unsafe internals, safe public API — is the central architectural idea of RfL. It is also the standard pattern in Rust's standard library (Vec, Rc, Mutex all work this way). RfL applies it to the entire Linux kernel API surface.
The role of bindgen: At build time, bindgen parses selected kernel C headers and generates Rust extern "C" declarations. These are placed in rust/bindings/bindings_generated.rs. A handwritten rust/bindings/bindings_helper.h selects which headers to include. The generated code is platform-specific — it must be regenerated for each architecture.
// Simplified sketch of how Mutex<T> works in the kernel crate
// (Real code in rust/kernel/sync/mutex.rs)
use crate::bindings; // unsafe C FFI declarations
pub struct Mutex<T> {
inner: UnsafeCell<bindings::mutex>,
data: UnsafeCell<T>,
}
pub struct MutexGuard<'a, T> {
lock: &'a Mutex<T>,
}
impl<T> Mutex<T> {
pub fn lock(&self) -> MutexGuard<'_, T> {
// SAFETY: self.inner is a valid, initialized mutex.
unsafe { bindings::mutex_lock(self.inner.get()); }
MutexGuard { lock: self }
}
}
impl<T> Drop for MutexGuard<'_, T> {
fn drop(&mut self) {
// SAFETY: We hold the lock (we are the guard).
unsafe { bindings::mutex_unlock(self.lock.inner.get()); }
}
}
impl<T> core::ops::Deref for MutexGuard<'_, T> {
type Target = T;
fn deref(&self) -> &T {
// SAFETY: We hold the lock, so exclusive access is valid.
unsafe { &*self.lock.data.get() }
}
}
05Writing Your First Rust Kernel Module
A kernel module in C implements a set of C functions and registers them with the kernel using macros. A Rust kernel module does the same thing, but through a safe Rust trait interface.
Every loadable Rust module implements the kernel::Module trait, which requires a single method: fn init(module: &'static ThisModule) -> Result<Self>. This is called when the module is inserted (insmod). When the module is removed (rmmod), the module's struct is dropped, and Rust's Drop infrastructure runs cleanup code automatically.
The module! macro (defined in rust/macros/) generates the MODULE_LICENSE, MODULE_AUTHOR, MODULE_DESCRIPTION metadata and the init_module/cleanup_module C entry points that the kernel's module loader expects. It bridges the gap between the kernel's C module ABI and Rust's trait system.
To build a Rust module, you need:
- A Linux kernel source tree with Rust support enabled (
CONFIG_RUST=y). - The exact version of Rust that the kernel requires (pinned via
rust-toolchain.tomlin the kernel tree). bindgenat the required version.
You do NOT use cargo to build kernel modules — you use the kernel's own make build system (Kbuild), which knows how to invoke rustc with the right flags, link against the kernel, and produce a .ko file.
The build process: make -C /path/to/kernel/source M=$(pwd) modules — Kbuild descends into your module directory, sees a Kbuild file listing your .rs source, invokes rustc with --extern kernel=... so your code can do use kernel::..., and links the result into a .ko.
// samples/rust/rust_minimal.rs — a real file in the kernel tree
// This is the canonical "hello world" Rust kernel module.
#![no_std] // No Rust standard library — kernel provides everything
use kernel::prelude::*;
module! {
type: RustMinimal,
name: "rust_minimal",
author: "Rust for Linux Contributors",
description: "Rust minimal sample",
license: "GPL",
}
struct RustMinimal {
numbers: Vec<i32>,
}
impl kernel::Module for RustMinimal {
fn init(_module: &'static ThisModule) -> Result<Self> {
pr_info!("Rust minimal sample (init)\n");
let mut numbers = Vec::new();
numbers.try_push(72, GFP_KERNEL)?;
numbers.try_push(108, GFP_KERNEL)?;
numbers.try_push(200, GFP_KERNEL)?;
Ok(RustMinimal { numbers })
}
}
impl Drop for RustMinimal {
fn drop(&mut self) {
pr_info!("My numbers are {:?}\n", self.numbers);
pr_info!("Rust minimal sample (exit)\n");
}
}
06The `no_std` Environment: Kernel Rust vs. Userspace Rust
Userspace Rust programs have access to std — the Rust standard library — which provides threads, files, networking, heap allocation, and hundreds of utilities, all backed by OS system calls. Kernel code cannot use std. It cannot call system calls; it is the system.
Kernel Rust modules are compiled with #![no_std]. This means:
- No
String(requires heap allocation via the OS) - No
Vec(same reason) - No
println!(requires a file descriptor to stdout) - No
std::sync::Mutex(uses pthread locks, which are userspace) - No unwinding panics (the kernel cannot unwind across C stack frames safely)
Instead, the kernel crate re-exports a carefully chosen subset of core (the no_std-compatible subset of std — pure algorithms, formatting, iterators, option/result, etc.) and provides kernel-specific alternatives:
kernel::prelude::Vec— backed bykmalloc/krealloc/kfreekernel::sync::Mutex<T>— backed by the kernel'sstruct mutexpr_info!,pr_warn!,pr_err!— wrappers aroundprintkResult<T>where the error type is a kernel error code (e.g.,ENOMEM,EINVAL)
Another critical difference: no panicking. In userspace, a panic unwinds the stack and terminates the process. In the kernel, a panic would be a kernel BUG() — potentially catastrophic. RfL therefore avoids panicking operations. Instead of vec.push(x) (which can panic on OOM), you use vec.try_push(x, GFP_KERNEL)? which returns Err(ENOMEM) on allocation failure, propagating the error up via the ? operator rather than panicking.
This try_* convention pervades kernel Rust: try_new, try_push, try_clone. It maps cleanly to the kernel C convention of returning negative errno values, but now expressed as Rust's Result type, with the ? operator making error propagation ergonomic.
// Error propagation with ? — no panic, no goto, just types
use kernel::prelude::*;
fn allocate_buffer(size: usize) -> Result<Vec<u8>> {
// try_with_capacity returns Err(ENOMEM) if kmalloc fails.
// The ? operator returns that error to our caller immediately.
let mut buf = Vec::try_with_capacity(size, GFP_KERNEL)?;
for i in 0..size {
// try_push returns Err(ENOMEM) on failure.
buf.try_push(i as u8, GFP_KERNEL)?;
}
Ok(buf) // Return the filled buffer on success
}
// Compare to C:
// u8 *buf = kmalloc(size, GFP_KERNEL);
// if (!buf) return -ENOMEM;
// ... fill buffer ...
// The C version is correct but forgetting the NULL check is easy.
// The Rust version cannot compile without handling the error.
07Real-World Rust Drivers in the Kernel
RfL moved beyond samples quickly. Several production subsystems now have Rust components in the mainline kernel:
Apple M1/M2 IOMMU driver (drivers/iommu/apple-dart.c was C; the newer AGX GPU driver uses Rust): Asahi Linux, the project porting Linux to Apple Silicon, has been a major driver of RfL adoption. The Asahi GPU driver (for the M1/M2 GPU, named AGX) is written in Rust and is the largest Rust driver in the kernel. Its author, Asahi Linux founder Hector Martin, has said that writing the driver in Rust surfaced invariant violations that would have been extremely hard to find in C.
PHY drivers and NVMe drivers: Several smaller networking PHY drivers (drivers/net/phy/) have been written in Rust, serving as real-world tests of the networking abstractions layer.
The Nova GPU driver: An effort to write the open-source Nvidia GPU driver (for older Turing/Volta GPUs) in Rust is underway. This is particularly interesting because GPU drivers are notoriously complex and bug-prone.
Android's use case: Google, which ships the Android kernel, has been a strong advocate of RfL. Android's kernel team has written Rust drivers for the Binder IPC mechanism and has made Rust a required language for new kernel contributions on certain teams.
The pattern you see across these cases: Rust is being adopted first for NEW drivers, not rewrites of existing C code. This is the right approach — forced rewrites of working C code would be risky; new code benefits from Rust's guarantees from day one.
// A simplified Rust PHY driver structure (real abstraction in kernel tree)
// The trait-based approach replaces C's function-pointer structs.
use kernel::net::phy::{self, DeviceId, Driver, PhyDevice, Registration};
use kernel::prelude::*;
kernel::module_phy_driver! {
drivers: [PhyDriverImpl],
device_table: [
DeviceId::new_with_driver::<PhyDriverImpl>()
],
name: "rust_phy_sample",
author: "Example Author",
description: "Sample Rust PHY driver",
license: "GPL",
}
struct PhyDriverImpl;
#[vtable]
impl Driver for PhyDriverImpl {
fn config_init(dev: &mut PhyDevice) -> Result<()> {
dev.init_hw()?;
dev.start_aneg()?;
Ok(())
}
}
08The Community Debates: Safety, Complexity, and Coexistence
The introduction of Rust into the kernel has not been without controversy. Understanding the debates helps you participate as a contributor rather than walking into them blindly.
The Linus Torvalds position: Torvalds has been cautiously supportive. He merged the initial support in 6.1, saying the worst case is "we tried it and it didn't work out." He has consistently pushed back on over-engineered abstractions and insisted that Rust code in the kernel must be readable to people who are NOT Rust experts. This is a real constraint: a senior kernel developer who has spent 20 years reading C must be able to understand a Rust driver that they need to debug at 2 AM.
The Wedson Almeida Filho departure (2024): Wedson, one of the lead RfL developers and the author of many of the core abstractions, left the project citing what he described as hostile pushback from some long-time C kernel maintainers. This episode highlighted a real tension: some C maintainers are uncomfortable reviewing and maintaining Rust code they do not know, and some Rust advocates have been perceived as dismissive of C's 30-year track record.
The 'unsafe is still unsafe' argument: Critics correctly note that the kernel's unsafe code in the abstractions layer is still C-like in its danger. If Mutex::lock() has a bug in its SAFETY justification, the safety guarantee breaks. RfL shifts the trust surface, not eliminates it — you now trust the kernel crate rather than every module author. Whether this is an improvement depends on whether the crate's unsafe code is better reviewed than the equivalent C would be.
The learning curve: Rust has a steeper initial learning curve than C for many systems programmers. The borrow checker is famous for rejecting perfectly reasonable-seeming code. This is a real barrier to contribution.
The consensus: The kernel community has broadly accepted that Rust will play a role, particularly for new drivers and subsystems. The debate has shifted from 'should we do this?' to 'how do we do it well?' — which is a healthy place to be.
09Setting Up a Rust Kernel Development Environment
The toolchain setup for RfL is more involved than for C kernel development because the kernel requires a specific, pinned version of the Rust toolchain — not the latest stable.
Step 1: Get the kernel source. Clone the RfL development tree (or mainline): git clone https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
Step 2: Check the required Rust version. Run make rustavailable from the kernel root. This checks whether your installed Rust matches what the kernel needs. The kernel's rust-toolchain.toml in the tree root specifies the exact version.
Step 3: Install the right Rust. Use rustup to install the pinned version: the kernel will tell you what it needs. Install the rust-src component (needed for core and alloc recompilation) and bindgen-cli at the required version.
Step 4: Configure the kernel. Use a minimal config like make tinyconfig or start from make defconfig, then enable CONFIG_RUST=y. Check with make menuconfig under 'General setup' → 'Rust support'.
Step 5: Build a QEMU-bootable kernel. For development, you do not test on real hardware. Build the kernel, create a minimal root filesystem (using Buildroot or a simple initramfs), and boot it in QEMU. This is the standard kernel developer workflow.
Step 6: Build and load your module. Compile your module with make -C /path/to/kernel M=$(pwd) modules, copy the .ko to the QEMU VM, and insmod it. Use dmesg to see your pr_info! output.
A good shortcut for beginners: the rustup setup script in the kernel tree (scripts/rust_is_available.sh) will diagnose your setup and tell you exactly what is missing.
# Abbreviated setup sequence (run from kernel source root)
# 1. Check Rust availability
make rustavailable
# 2. If not available, install the required version
# The kernel tells you what version it needs:
cat rust-toolchain.toml
# 3. Install it with rustup
rustup toolchain install <version-from-above>
rustup component add rust-src --toolchain <version>
cargo install --locked bindgen-cli --version <required-version>
# 4. Configure with Rust enabled
make LLVM=1 defconfig
scripts/config --enable CONFIG_RUST
scripts/config --enable CONFIG_SAMPLES
scripts/config --enable CONFIG_SAMPLES_RUST
# 5. Build (use LLVM=1 — Rust requires LLVM-based tools)
make LLVM=1 -j$(nproc)
# 6. Boot in QEMU (after setting up a rootfs)
qemu-system-x86_64 -kernel arch/x86/boot/bzImage \
-initrd /path/to/rootfs.cpio.gz \
-append "console=ttyS0" -nographic
10Lifetimes and Kernel Object Management
One of the most elegant applications of Rust lifetimes in the kernel is the management of kernel objects whose valid lifetimes are hard to track manually in C.
Consider struct device in the Linux driver model. Every device object has a reference count managed by kobject's kref mechanism. In C, you call get_device() to increment the refcount and put_device() to decrement it. Forgetting a put_device() leaks the object; calling it too many times causes a use-after-free. The rules are documented but not enforced.
In Rust, the kernel crate wraps struct device in a type that integrates with Rust's Arc (atomic reference counted pointer). Taking a reference returns a ARef<Device> — when it is dropped, put_device is called automatically. You cannot hold a raw pointer to a device; you must hold an ARef<Device> (or a borrowed reference to one). The refcount is now managed by the type system's ownership rules, not human memory.
This pattern extends to many kernel objects: struct file → ARef<File>, struct task_struct → ARef<Task>. The Rust type represents the 'held reference' concept that in C is purely conventional.
Pinning: Some kernel data structures (like kernel mutexes, wait queues) must not be moved in memory after initialization — their addresses may be stored in linked lists or hardware registers. Rust has a Pin<P> type that enforces this: once pinned, the object behind the pointer cannot be moved (the Unpin trait is not implemented). The kernel crate uses Pin for objects that have this requirement, turning a runtime invariant ('don't move this') into a compile-time guarantee.
// Lifetime-managed device reference (conceptual sketch)
// Real implementation in rust/kernel/device.rs
use kernel::device::Device;
use kernel::types::ARef;
fn use_device(dev: ARef<Device>) {
// dev is an owned, reference-counted handle.
// We can use dev here safely.
pr_info!("Using device: {}\n", dev.name());
// When `dev` goes out of scope here, Drop runs,
// which calls put_device() automatically.
// You cannot forget to release the reference.
}
// In C, the equivalent requires:
// get_device(dev); // Manual increment
// ... use dev ...
// put_device(dev); // Manual decrement — easy to forget
11The Future: Where Rust in the Kernel Is Heading
As of 2025, RfL is past the 'proof of concept' phase and into the 'proving itself in production' phase. Several trajectories are worth watching:
Subsystem-level adoption: The filesystem, networking, and block-layer maintainers are incrementally adding Rust abstractions. The goal is not to rewrite existing drivers but to make it possible to write new ones — or new subsystem code — in Rust. The VFS (virtual filesystem) Rust abstractions, for instance, would allow new filesystems to be written in Rust.
The abstractions quality problem: The biggest near-term challenge is completeness and correctness of the abstractions layer. Every new Rust driver needs kernel API abstractions that may not yet exist in rust/kernel/. Writing a new abstraction requires both Rust expertise and deep knowledge of the wrapped C subsystem. This is a high-skill bottleneck. Contributions to the abstractions layer are among the highest-leverage contributions you can make.
Formal verification interest: Because Rust's type system already provides machine-checked properties, it is a more tractable target for formal verification tools (like Kani, a Rust model checker developed by Amazon) than C. There is active research into verifying the correctness of the RfL abstractions layer — not just type-safety, but full functional correctness.
The Rust for Linux book and documentation: The project has been building out documentation aggressively. The rust/ directory has rustdoc comments that generate browsable API docs. Understanding how to navigate and contribute to this documentation is a practical entry point for new contributors.
Long-term vision: The most optimistic view, shared by people like Ojeda and the Android kernel team, is that within a decade, new subsystems will default to Rust, and the kernel will have a clear policy of 'new code in Rust, existing C maintained as-is.' The pessimistic view is that Rust never escapes niche adoption because the learning curve and toolchain complexity are real barriers. The most likely outcome is somewhere in between — Rust becomes a well-supported first-class option for driver and module development, especially in safety-critical areas.
Key insights
- ✦Memory safety bugs account for ~65-70% of Linux kernel CVEs — Rust eliminates entire classes of these at compile time, not with runtime overhead.
- ✦Rust's borrow checker makes data races literally impossible to express without `unsafe` — two concurrent accesses where one is a write cannot compile.
- ✦The RfL architecture has three layers: your safe module code → the `kernel` crate's safe abstractions → `bindings/` raw unsafe FFI. Push `unsafe` as deep as possible.
- ✦The `module!` macro bridges Rust traits to the kernel's C module ABI — you implement `kernel::Module`, the macro generates `init_module`/`cleanup_module`.
- ✦`#![no_std]` means no standard library — the kernel crate replaces `Vec`, `Mutex`, and memory allocation with versions backed by `kmalloc` and kernel synchronization primitives.
- ✦Use `try_*` variants everywhere and propagate errors with `?` — panicking in the kernel would be a BUG(), so RfL's API surface makes failure-handling ergonomic rather than unsafe.
- ✦Rust lifetimes encode kernel reference-counting rules (get_device/put_device) into the type system — you cannot forget to release a reference because Drop does it automatically.
- ✦The debates matter: Rust shifts the trust surface, it does not eliminate it. The `unsafe` in the abstractions layer must be as carefully reviewed as the most critical C in the kernel.
Common pitfalls
- ⚠Using the wrong Rust toolchain version — the kernel pins an exact version. `rustup default stable` will not work. Always check `rust-toolchain.toml` in the kernel tree and install that exact version.
- ⚠Trying to use `cargo` to build kernel modules — kernel modules are built with Kbuild (`make M=...`), not `cargo`. Cargo does not know about kernel headers, linker scripts, or the kernel's ABI.
- ⚠Forgetting `LLVM=1` when building — the Rust support in the kernel requires an LLVM-based toolchain (clang/lld). Mixed GCC+Rust builds have issues.
- ⚠Treating `unsafe` as an escape hatch rather than a documented contract — every `unsafe` block must have a `// SAFETY:` comment explaining why the invariants are upheld. Reviewers will reject blocks without one.
- ⚠Writing `vec.push(x)` instead of `vec.try_push(x, GFP_KERNEL)?` — the infallible `push` (which panics on OOM) does not exist in kernel Rust. OOM must be handled as a `Result`.
- ⚠Confusing `core::` with `std::` — in `no_std` kernel code, you get `core::` (pure algorithms) and the kernel crate, not `std::`. Importing `std::` will fail to compile.
- ⚠Expecting existing C subsystems to have complete Rust abstractions — as of 2025, many kernel subsystems lack Rust wrappers. Writing a driver for an unabstracted subsystem means writing the abstraction first.
- ⚠Testing on real hardware before QEMU — always validate your module loads and runs correctly in a QEMU VM before touching real hardware. A bug in a kernel module can panic or corrupt the host.
Practice
- 01medium
Build and load the `rust_minimal` sample module. Clone a Linux kernel tree that supports Rust (6.1+), install the exact Rust toolchain the tree requires, enable `CONFIG_RUST` and `CONFIG_SAMPLES_RUST`, build the kernel and the sample, boot it in QEMU, and `insmod rust_minimal.ko`. Capture the `dmesg` output showing your module's init and exit messages.
- 02hard
Write a Rust kernel module that creates a kernel thread (using `kernel::task::Task` or the threading abstractions available in the kernel crate for your kernel version) that counts from 0 to 9, printing each number with `pr_info!`, then exits. The module's `Drop` impl should wait for the thread to finish before the module unloads. This exercises both the threading abstraction and the lifetime/ownership model.
- 03easy
Write a minimal `bindgen` experiment outside the kernel: take a simple C header file with a struct and two functions (e.g., a toy `counter.h` with `counter_init`, `counter_increment`, `counter_get`), run `bindgen` on it, and write a Rust program that calls the C functions through the generated unsafe FFI. Then write a safe Rust wrapper that hides the `unsafe` behind a clean API. This simulates, in miniature, exactly what `rust/kernel/bindings/` and `rust/kernel/` do.
- 04medium
Explore a real Rust driver in the kernel tree. Check out the `drivers/net/phy/` directory and find a Rust PHY driver (they exist in Linux 6.8+). Read it carefully. Write a 500-word technical explanation of: (1) what kernel C API it is wrapping, (2) what safety invariants the `unsafe` blocks in the corresponding kernel crate abstraction maintain, and (3) one thing it can express in Rust that would have required a comment-only convention in C.
- 05easy
Reproduce a use-after-free scenario in C (userspace is fine — write a small C program that deliberately triggers a UAF and observe it with AddressSanitizer: `gcc -fsanitize=address`). Then translate the equivalent program logic to Rust and observe that it does not compile. Write up what error the Rust compiler produces and which specific rule it is enforcing.
Resources
The canonical project hub: news, kernel API docs, contributor guide, and links to the development trees.
Official kernel documentation for Rust support, including quick-start, coding guidelines, and architecture overview.
The authoritative Rust language reference; chapters on ownership, borrowing, and lifetimes are essential prerequisites.
Deep dive into what `unsafe` means, when it is valid, and how to write safe abstractions over unsafe code — directly relevant to the kernel crate.
LWN has tracked every major RfL development since 2020; reading the chronological articles is the best way to understand the community dynamics.
Practical lab exercises for kernel development; useful for building the QEMU environment needed to test Rust modules.
Browse the kernel's `rust/` directory online with hyperlinked cross-references — indispensable for reading the kernel crate source.
The most thorough systems-focused Rust book; the chapters on lifetimes and unsafe code are clearer than The Book.
The development tree where new RfL code lands before mainline; often has abstractions and drivers not yet in Linus's tree.
Essential for understanding how the kernel's C headers become Rust FFI declarations.
Checkpoints — you can now…
- ✓You can explain, without reference notes, why C's memory model makes use-after-free bugs structurally possible and why the Rust borrow checker makes them structurally impossible.
- ✓You can describe the three layers of the RfL architecture (your module, the kernel crate, the bindings crate) and explain which layer contains `unsafe` code and why.
- ✓You can set up a kernel build environment with the correct pinned Rust toolchain, build the `rust_minimal` sample, and load it in QEMU.
- ✓You can write a kernel module skeleton from scratch — the `module!` invocation, the `Module` trait impl, and a `Drop` impl — without looking at a template.
- ✓You can explain what `#![no_std]` means for kernel Rust and name four things from `std` that do not exist in kernel Rust, along with their kernel-crate replacements.
- ✓You can explain the `try_*` convention and why `vec.push(x)` is replaced by `vec.try_push(x, GFP_KERNEL)?` in the kernel.
- ✓You can articulate the main community objections to Rust in the kernel and give a fair summary of the counter-arguments.
- ✓You can read a Rust PHY driver in the kernel tree, identify where the `unsafe` code lives, and explain the safety invariant each `unsafe` block relies on.