Skip to content

05 — Rust for Systems Programming

Technical Overview

Rust occupies a unique position in the programming language landscape: it provides C-like performance and low-level control alongside memory safety guarantees that previously required garbage collection. This combination makes Rust suitable for domains where C and C++ have been dominant — operating systems, embedded systems, network infrastructure, and performance-critical services — while eliminating the memory safety vulnerability class that constitutes ~70% of security CVEs in those systems. This document surveys Rust's capabilities for systems programming, examines real operating systems and projects built in Rust, and maps the production adoption of Rust at major technology companies.

Prerequisites

  • Rust ownership model (see 02), unsafe Rust (see 03)
  • Operating system architecture fundamentals
  • Embedded systems: bare-metal, interrupt-driven programming, RTOS
  • Async programming models
  • C++ RAII, templates, and zero-cost abstractions

Historical Context

C has been the language of systems programming since the early 1970s (Ritchie's C on the PDP-11, 1972). C++ added zero-cost abstractions, RAII, and templates beginning in 1985. For 50 years, the argument was: "If you need performance and low-level control, you use C or C++. If you want safety, you accept GC overhead." Rust invalidated this dichotomy. It is not the first attempt — Cyclone, Ciao, ATS, and others tried. But Rust is the first to achieve practical adoption, a mature ecosystem, and industrial production deployment at scale.


Rust vs C: A Systems Programming Comparison

Performance: Zero-Cost Abstractions

Rust's design principle is "zero-cost abstractions" — features that are as efficient as hand-written low-level code. This principle, borrowed from C++, means that abstractions that appear to add overhead compile away completely:

Iterators:

// Rust iterator chain — same machine code as the C loop
let sum: i32 = (0..1_000_000)
    .filter(|&x| x % 2 == 0)
    .map(|x| x * x)
    .sum();

// C equivalent:
// int sum = 0;
// for (int i = 0; i < 1000000; i++) {
//     if (i % 2 == 0) sum += i * i;
// }

// Both compile to the same SIMD-vectorized loop on x86-64 with -O2

Generics (monomorphization):

fn max<T: PartialOrd>(a: T, b: T) -> T {
    if a > b { a } else { b }
}

// max::<i32>, max::<f64>, max::<MyType> are each compiled to specialized code
// No runtime dispatch, no boxing, no overhead vs writing three separate functions

Predictable performance (no GC pauses):

Rust uses RAII for memory management. Objects are freed deterministically when they go out of scope. There is no garbage collector, no stop-the-world pause, no pause that might occur at unpredictable times.

Latency comparison (simplified):
  Go (GC):     P99.9 latency: 1-10ms GC pause possible
  Java (GC):   P99.9 latency: 10-100ms GC pause with G1/ZGC (better but non-zero)
  Rust:        P99.9 latency: determined by workload only (no GC pauses)
  C:           P99.9 latency: same as Rust, but possible memory corruption → crash

Safety: Compile-Time Elimination of Common C Bugs

The comparison of safety properties:

Bug Class C C++ (with care) Rust (safe)
Buffer overflow Common Possible Compile error
Use-after-free Common Possible Compile error
Double-free Common Possible Compile error
Null dereference Common Possible No null (Option)
Data race Common Possible Compile error
Integer overflow UB Implementation-defined Panic (debug)
Uninitialized memory UB UB Compile error

The key difference from C++: C++ provides tools (unique_ptr, shared_ptr, RAII) that help if used correctly. Rust's borrow checker enforces safety mechanically — it is not possible to write a use-after-free in safe Rust, regardless of developer expertise or inattention.

Expressiveness: Sum Types and Pattern Matching

Rust's type system is more expressive than C's in ways that eliminate entire classes of logic errors:

// Rust: explicit handling of absence (no null)
enum Option<T> {
    Some(T),  // value present
    None,     // value absent
}

// Cannot use an Option<T> as T without explicitly matching
fn double_length(s: Option<&str>) -> usize {
    match s {
        Some(s) => s.len() * 2,  // must handle Some case
        None => 0,                // must handle None case
        // Compiler error if either case is missing
    }
}

// Rust: explicit error propagation (no exceptions, no errno)
fn parse_and_double(s: &str) -> Result<i32, std::num::ParseIntError> {
    let n = s.parse::<i32>()?;  // ? propagates error up the call stack
    Ok(n * 2)
}

// C: null pointer for absence — silently usable without checking
char *s = get_string();  // might be NULL
int len = strlen(s);     // segfault if NULL, no enforcement of check

Traits vs inheritance:

Rust uses traits (similar to type classes in Haskell, interfaces in Go) rather than class inheritance. Traits provide code reuse without the "banana-gorilla-jungle" problem of inheritance hierarchies:

// Trait: defines behavior
trait Drawable {
    fn draw(&self);
    fn bounding_box(&self) -> Rectangle;
}

// Multiple types implement Drawable independently
struct Circle { radius: f64, center: Point }
struct Square { side: f64, center: Point }

impl Drawable for Circle { ... }
impl Drawable for Square { ... }

// Generic function over any Drawable: zero-cost (monomorphization)
fn render<T: Drawable>(shape: &T) {
    shape.draw();
}

// Dynamic dispatch when needed: explicit opt-in
fn render_dynamic(shape: &dyn Drawable) {
    shape.draw();  // vtable lookup — only when explicitly needed
}

Rust for Operating System Development

Redox OS

Redox OS (github.com/redox-os/redox) is a complete, production-intended operating system written entirely in Rust. It implements a microkernel architecture with a Unix-like API.

Architecture: - Microkernel: minimal kernel (process management, memory management, IPC) - Drivers: run in user space (USB, AHCI, network) — driver crash cannot corrupt kernel - Userspace: Rust-implemented libc, shell, applications

Security properties: - Memory safety in the kernel by construction - Driver isolation: driver crash causes device failure, not kernel panic - Address space isolation enforced by Rust's type system (no unsafe pointers between kernel and user space)

Current state (2024): - Bootable, runs on real hardware and QEMU - Basic application support (text editor, browser — incomplete) - Primary value: proof that a full OS in Rust is feasible, performance is comparable to Linux

Key insight from Redox: The microkernel design is more natural in Rust than in C. Rust's type system enforces the isolation boundaries that microkernel architecture requires. Memory regions that should not be accessible from drivers provably cannot be accessed.

Theseus OS

Theseus OS (github.com/theseus-os/Theseus) is a research OS from Rice University exploring "intralingual" design — using Rust's type system to enforce OS policies that are traditionally enforced by runtime checks.

Novel contribution: Theseus uses Rust's ownership system to track resource liveness in the OS kernel itself. When a task holds a resource (mapped memory region, device handle), the resource is "owned" by the task. When the task ends, Rust's Drop cleans up all owned resources automatically.

Traditional OS:
  Task ends → OS kernel scans task's resource table → frees each resource
  (runtime scan: O(n), error-prone if table is wrong)

Theseus:
  Task ends → Rust's Drop chain fires automatically
  Each owned resource's Drop implementation frees it
  No runtime scan needed — Rust's static ownership graph is the resource table

This enables a class of OS safety properties ("no resource leak after task termination") that are static guarantees in Theseus, vs runtime policies in traditional OSes.

Tock OS

Tock OS (tockos.org) is an embedded RTOS (Real-Time Operating System) for resource-constrained microcontrollers, written in Rust. It is in production use on security-focused embedded devices.

Target hardware: ARM Cortex-M series (M0, M3, M4) — the dominant embedded microcontroller architecture.

Key Tock design decisions:

  1. Isolation via MPU (Memory Protection Unit): Tock uses the ARM MPU to isolate application processes from the kernel and from each other. Rust provides safety within the kernel itself; MPU provides hardware isolation between kernel and apps.

  2. Capsules (kernel drivers in Rust): Kernel-mode device drivers ("capsules") are safe Rust — the borrow checker prevents the capsule from corrupting other kernel state.

  3. Apps (in C or Rust): User-mode applications run with limited permissions. A buggy app cannot crash the OS.

Production use: - Google's OpenTitan (open-source silicon root of trust) uses Tock OS - STMicroelectronics embedded secure elements - Various research embedded security platforms


Rust for Embedded (no_std, Bare-Metal)

no_std and Bare-Metal

Embedded development requires running without an operating system. Rust's no_std mode provides the language without any OS-dependent functionality:

#![no_std]
#![no_main]  // no standard entry point

// Panic handler required in no_std
use core::panic::PanicInfo;
#[panic_handler]
fn panic(_info: &PanicInfo) -> ! {
    loop {}  // on embedded: halt forever (or reset watchdog)
}

// Entry point: the hardware reset handler
#[cortex_m_rt::entry]
fn main() -> ! {
    // Configure hardware directly
    let peripherals = cortex_m::Peripherals::take().unwrap();
    let device_peripherals = stm32f4::Peripherals::take().unwrap();

    // Blink an LED
    let gpio = device_peripherals.GPIOA;
    gpio.moder.write(|w| w.moder5().output());

    loop {
        gpio.odr.modify(|r, w| w.odr5().bit(!r.odr5().bit()));
        cortex_m::asm::delay(8_000_000);
    }
}

RTIC (Real-Time Interrupt-driven Concurrency)

RTIC (formerly RTFM) is a Rust framework for embedded real-time systems that uses Rust's type system to enforce: - Priority-based scheduling: Tasks have static priorities; the Rust type system verifies that resources shared between tasks are protected correctly (higher-priority tasks preempting lower-priority tasks cannot cause data races). - Deadlock-free resource access: RTIC uses a "ceiling protocol" — a generalization of priority inheritance — to prevent priority inversion. - Zero-cost synchronization: In many cases, RTIC's static analysis allows sharing data between interrupt handlers without runtime locking.

#[rtic::app(device = stm32f4xx_hal::pac, peripherals = true)]
mod app {
    #[shared]
    struct Shared {
        counter: u32,  // shared between tasks
    }

    #[local]
    struct Local {}

    #[init]
    fn init(cx: init::Context) -> (Shared, Local) {
        (Shared { counter: 0 }, Local {})
    }

    // Task with priority 1 (lower)
    #[task(shared = [counter], priority = 1)]
    async fn read_counter(cx: read_counter::Context) {
        let count = cx.shared.counter.lock(|c| *c);  // lock required by RTIC
        // ...
    }

    // Interrupt handler with priority 2 (higher) 
    #[task(binds = TIM2, shared = [counter], priority = 2)]
    fn timer_tick(cx: timer_tick::Context) {
        // RTIC guarantees: at priority 2, no priority-1 task can interrupt
        // but we still need the lock for shared data — ceiling protocol enforced
        cx.shared.counter.lock(|c| *c += 1);
    }
}

The critical property: RTIC can verify at compile time that shared resources are always properly locked when accessed by tasks of different priorities. Priority inversion is prevented by static analysis, not runtime checks.


Rust Async for Networking: Tokio Ecosystem

Rust's async/await system enables high-performance asynchronous networking code without OS threads. The Tokio runtime is the dominant async runtime for server-side Rust.

use tokio::io::{AsyncReadExt, AsyncWriteExt};
use tokio::net::{TcpListener, TcpStream};

#[tokio::main]
async fn main() -> std::io::Result<()> {
    let listener = TcpListener::bind("0.0.0.0:8080").await?;

    loop {
        let (socket, _) = listener.accept().await?;
        tokio::spawn(handle_connection(socket));  // spawn task for each connection
    }
}

async fn handle_connection(mut socket: TcpStream) -> std::io::Result<()> {
    let mut buf = [0; 1024];
    let n = socket.read(&mut buf).await?;
    socket.write_all(&buf[..n]).await?;
    Ok(())
}

Why Rust async beats Go goroutines for latency: - No GC pauses: Rust async futures are dropped deterministically at task end - No global goroutine scheduler pause: Rust tasks are cooperative within a thread pool, with no global stop-the-world - Lower memory per connection: a Rust async task is just the future's stack frames (typically 200-500 bytes); a Go goroutine starts at 8KB

Tokio performance: Discord's migration from Go to Rust (2020) for their Presence service (10M+ users) showed: CPU usage dropped by ~5x, memory usage dropped by ~10x, and P99 latency dropped from 500ms to ~10ms.


Production Adoption

AWS Firecracker

AWS Firecracker is the open-source microVMM (microVirtual Machine Monitor) used by AWS Lambda and AWS Fargate. It boots lightweight VMs in ~125ms and runs entire Lambda function sandboxes.

Firecracker is written in Rust. AWS chose Rust for: - Security: Firecracker runs as root, managing customer workloads. Memory safety is critical. - Performance: Lambda's cold start time is partly determined by Firecracker startup (~125ms total) - Correctness: Firecracker manages hardware virtualization (KVM ioctls) — bugs can affect tenant isolation

Firecracker's codebase: ~50K lines of Rust. Unsafe code: minimal, primarily in KVM ioctl wrappers.

Security impact: Firecracker has had zero memory-safety CVEs since its open-source release in 2018. Compare to QEMU (the traditional VM monitor) which has had hundreds of CVEs, many memory safety.

Cloudflare: Pingora Replacing NGINX

Cloudflare deployed Pingora (internal name) — a Rust HTTP proxy — to replace NGINX as their primary reverse proxy. The migration was completed for the majority of their traffic in 2021-2022.

Motivation: - NGINX connection handling: per-connection memory allocation and deallocation — expensive at Cloudflare's scale (~1M requests/second at each PoP) - NGINX lacks connection reuse across different client requests to the same upstream — each connection to an upstream server is closed and reopened - NGINX memory safety: periodic UAF CVEs in NGINX core and modules

Pingora results (Cloudflare blog, 2022): - 160B requests/day served by Pingora - CPU usage: ~70% of NGINX for equivalent throughput - Memory usage: ~2/3 of NGINX per request - Connection reuse: Pingora maintains a pool of upstream connections per thread, dramatically reducing TLS handshake overhead - Zero memory-safety CVEs in production (first 18 months)

Google: Android Kernel Drivers and User Space

Google has progressively replaced C code in Android with Rust since 2019: - Bluetooth stack: system/bt (BlueZ-derived C++ stack) replaced by packages/modules/Bluetooth in Rust (Android 11+) - WiFi driver abstraction: Rust wrappers for WiFi kernel interface - DNS resolver (netd): Rewritten in Rust for Android 12 - Virtualization service: Rust-based hypervisor infrastructure

Google's result: Android memory safety CVE fraction dropped from ~70% (Android 11) to ~36% (Android 13). The correlation with Rust adoption in new code is direct.

Microsoft: Azure and Windows

Microsoft has adopted Rust for: - Azure IoT Edge: Rust-based edge computing runtime - Windows kernel components (experimental): Microsoft is experimenting with Rust in Windows kernel code, particularly in networking and security components - Hypervisor components: Some Azure hypervisor components rewritten in Rust - Public statement (2022): "We are recommending memory-safe programming languages for new code. Rust is our primary recommendation."

Discord: Latency-Sensitive Services

Discord replaced several Elixir/Go services with Rust: - Read States service: Determines which channels/messages a user has read. At ~1M+ users, needed sub-millisecond P99 latency. - Go implementation: regular latency spikes to 500ms-1s due to GC - Rust replacement: P99 latency ~10ms, P999 latency ~100ms — 50x P99 improvement - Memory: 2GB → 200MB (10x reduction)


Rust Ecosystem Map

┌─────────────────────────────────────────────────────────────────┐
│                     Rust Ecosystem                              │
│                                                                 │
│  LANGUAGE CORE                                                  │
│  ┌────────────────────────────────────────────────────────┐    │
│  │ rustc (compiler) │ cargo (build/package mgr) │ rustup  │    │
│  │ std library      │ crates.io (package registry)        │    │
│  └────────────────────────────────────────────────────────┘    │
│                                                                 │
│  ASYNC / NETWORKING                SYSTEMS / EMBEDDED           │
│  ┌──────────────────────┐         ┌───────────────────────┐    │
│  │ tokio (async runtime)│         │ no_std (bare-metal)   │    │
│  │ hyper (HTTP)         │         │ cortex-m (ARM Cortex) │    │
│  │ reqwest (HTTP client)│         │ embedded-hal (HAL)    │    │
│  │ tonic (gRPC)         │         │ RTIC (RTOS framework) │    │
│  │ axum (web framework) │         │ probe-rs (debugger)   │    │
│  └──────────────────────┘         └───────────────────────┘    │
│                                                                 │
│  SAFETY / SECURITY                 PERFORMANCE                  │
│  ┌──────────────────────┐         ┌───────────────────────┐    │
│  │ MIRI (UB detector)   │         │ rayon (data parallelism│    │
│  │ cargo-audit (CVE scan)│        │ crossbeam (lock-free) │    │
│  │ cargo-geiger (unsafe)│         │ packed_simd (SIMD)    │    │
│  │ rustls (TLS in Rust) │         │ serde (serialization) │    │
│  └──────────────────────┘         └───────────────────────┘    │
│                                                                 │
│  OS DEVELOPMENT                    PRODUCTION USERS             │
│  ┌──────────────────────┐         ┌───────────────────────┐    │
│  │ Redox OS (full OS)   │         │ AWS Firecracker        │    │
│  │ Tock OS (RTOS)       │         │ Cloudflare Pingora     │    │
│  │ Theseus (research)   │         │ Discord (low-latency)  │    │
│  │ Linux (Rust in 6.1)  │         │ Google Android         │    │
│  │ Fuchsia (partial)    │         │ Microsoft Azure        │    │
│  └──────────────────────┘         └───────────────────────┘    │
└─────────────────────────────────────────────────────────────────┘

Rust vs C: Detailed Code Comparison

A memory-safe string processing function:

/* C: manual safety */
int count_words(const char *input, size_t len) {
    int count = 0;
    int in_word = 0;
    for (size_t i = 0; i < len; i++) {
        if (input[i] == ' ' || input[i] == '\n') {
            in_word = 0;
        } else if (!in_word) {
            in_word = 1;
            count++;
        }
    }
    return count;
}
/* Issues: if len > strlen(input), reads beyond string (UB)
           no check that input is not NULL
           count could overflow for very long inputs */
// Rust: safe by construction
fn count_words(input: &str) -> usize {
    // &str: always valid UTF-8, always has a known length, never null
    input.split_whitespace().count()
    // No buffer overflow possible: split_whitespace uses the string's length
    // No null: &str cannot be null — Option<&str> would be used if optional
    // No integer overflow: usize on 64-bit is 2^64 — not an issue in practice
}

Debugging Notes

# Rust compilation and performance
cargo build --release           # optimized build (equivalent to -O2)
cargo build --release --target  # cross-compile

# Check generated assembly
cargo objdump --release -- --disassemble src/main.rs
# or
cargo asm --release crate_name::function_name

# Benchmark with criterion
cargo bench  # runs benchmarks defined with criterion

# Profile Rust programs
cargo flamegraph  # requires flamegraph tool
perf record --call-graph=dwarf cargo run --release
perf report

# Cross-compilation for embedded
rustup target add thumbv7em-none-eabihf   # ARM Cortex-M4
cargo build --target thumbv7em-none-eabihf --release

# Async debugging
TOKIO_CONSOLE=1 cargo run  # tokio-console for async task profiling

# Memory usage
cargo bloat --release  # shows which functions contribute most to binary size
cargo build --release && size target/release/my_binary

Security Implications

  • AWS Firecracker: Zero memory safety CVEs in 5+ years of production — a direct consequence of Rust
  • Android: 50% reduction in memory safety CVEs correlating with Rust adoption
  • Rustls vs OpenSSL: Rustls (TLS in Rust) has zero buffer overflow CVEs; OpenSSL has had Heartbleed and several other critical CVEs
  • Supply chain: Cargo's dependency management is a supply chain attack surface (see XZ Utils — Cargo packages are a concern but the language's safety provides some defense-in-depth)

Performance Implications

  • Benchmark data: The Computer Language Benchmarks Game shows Rust within 0-30% of C for most benchmarks, often at parity
  • Binary size: Rust binaries include runtime type info and panic infrastructure — typically 1.5-2x larger than equivalent C. Can be reduced with opt-level = "z" and panic = "abort".
  • Compilation time: Rust compiles slower than C (LLVM-based, complex generic instantiation). Large projects (like the Linux kernel modules) may add 10-30% to build time.
  • Link time: LTO (Link-Time Optimization) in Rust is as effective as in C — lto = true in Cargo.toml

Failure Modes

Rust Design Choice Failure Mode Mitigation
panic! in no_std Undefined (no default handler) Must implement #[panic_handler]
unwrap() on None/Err Panic (crash) Use ? operator, if let, match
unsafe soundness bug UB propagates from unsafe MIRI, careful review, minimal unsafe
Async task starvation Non-cooperative task blocks executor tokio::task::yield_now(), bounded blocking
Compilation time Large projects slow to compile incremental compilation, split crates

Modern Usage

  • Rust stable releases: Released every 6 weeks. Current: ~1.78 (2024)
  • Rust edition system: Editions (2015, 2018, 2021) allow backward-incompatible improvements without breaking existing code
  • crates.io: ~150,000 crates published (2024)
  • Stack Overflow Developer Survey 2023: Rust is the "most loved/admired" language for the 8th year in a row

Future Directions

  • Rust 2024 edition: Expected to stabilize several ergonomic improvements (gen iterators, async closures)
  • Polonius 2.0: More precise borrow checker — eliminates remaining false positives
  • Const generics (stabilization): More powerful compile-time computation
  • GATs (Generic Associated Types): Stabilized in 1.65 (2022) — enables more expressive trait definitions for iterators and async
  • Async traits (stabilization): async fn in traits — stabilized in 1.75 (2023), enabling async trait objects
  • GCC Rust front-end (gccrs): Alternative Rust compiler using GCC backend — enables Rust on architectures only supported by GCC, and enables Rust in kernel builds that use GCC

Exercises

  1. Benchmark a string processing function in C, C++, Go, and Rust. Use a corpus of 1GB of text. Measure: throughput (MB/s), CPU usage, and P99 latency per request. Use perf stat to measure L1/L2 cache misses and branch mispredictions.

  2. Write a bare-metal Rust program for an ARM Cortex-M4 microcontroller (or using QEMU's cortex-m emulation). Implement: LED blink at 1Hz using a timer interrupt. Use RTIC to manage the interrupt. Flash and test.

  3. Implement a simple TCP proxy in Rust using Tokio: accept connections on port 8080, forward them to localhost:80, and back. Measure the maximum connections/second using wrk. Compare with an equivalent NGINX proxy configuration.

  4. Read the Cloudflare blog post "How we built Pingora, the proxy that connects Cloudflare to the Internet" (2022). Identify: what specific limitations of NGINX did Pingora address? What Rust-specific features enabled the improvements?

  5. Build a minimal no_std Rust program for a hypothetical bare-metal target (use x86_64-unknown-none). Include: a custom allocator using a static byte array, a simple stack data structure using that allocator, and a test that pushes/pops values. Verify there are no heap allocations from the OS (size the binary and check with nm).

References

  • Discord Engineering. "Why Discord is switching from Go to Rust." discord.com/blog, February 2020.
  • Cloudflare. "How we built Pingora, the proxy that connects Cloudflare to the Internet." blog.cloudflare.com, September 2022.
  • AWS Open Source. "Firecracker: Lightweight Virtualization for Serverless Applications." NSDI 2020.
  • Balasubramanian, Abhiram et al. "System Programming in Rust: Beyond Safety." APSys 2017. (Tock OS paper)
  • Google Android Security. "Memory Safety." android-developers.googleblog.com/2022/12 (Android CVE statistics)
  • Bornholt, James; Murray, Timothy. "Theseus: an Experiment in Operating System Structure and State Management." OSDI 2020.
  • Levy, Amit et al. "Multiprogramming a 64kB Computer Safely and Efficiently." SOSP 2017. (Tock OS kernel design)
  • Klabnik, Steve; Nichols, Carol. "The Rust Programming Language." No Starch Press, 2019.
  • Computer Language Benchmarks Game. benchmarksgame-team.pages.debian.net (performance data)
  • Rust Survey. "Rust Annual Survey Results." blog.rust-lang.org (yearly)