1.3 Who Uses Rust: Systems, WebAssembly, CLI, Embedded, and Beyond
Let’s be honest, you don’t pick up a language like Rust because you heard it has a cute mascot. You’re here because you have a problem that needs solving, and you want a tool that won’t break in your hands at the worst possible moment. Rust isn’t a one-trick pony; it’s a shockingly versatile workhorse that has infiltrated almost every corner of computing, from the deepest bowels of an operating system kernel to the pixel-pushing frenzy of your browser. The common thread? A brutal, uncompromising demand for correctness and performance.
Systems Programming: The Core Domain
This is Rust’s home turf. If you’re building an operating system, a database engine, a game engine, or a browser component, you’re in the systems realm. You’re managing memory manually, talking directly to the kernel, and squeezing every last nanosecond out of the CPU. Traditionally, this was C and C++ territory, languages that give you enough rope to hang yourself and everyone on your team. Rust enters the scene and says, “Hey, how about we keep all that performance and control but, and hear me out, not have a dozen security vulnerabilities per 1000 lines of code?”
The borrow checker is your new best friend here. It’s the brilliant, slightly pedantic friend who stops you from accidentally creating a use-after-free bug or a data race. You’re not just hoping your concurrent code is safe; you’re getting a compile-time guarantee. This is why projects like Microsoft’s Windows, Google’s Android, and even the Linux kernel are now welcoming Rust with open arms. They’re tired of spending 70% of their patching cycles on memory safety issues. The following isn’t just a function; it’s a statement of intent. It says, “I will have a string, and I will own it completely and safely.”
fn process_data(data: Vec<u8>) -> String {
// We take ownership of the Vec<u8>, so no one else can mess with it.
// The safety of converting to a string is our responsibility, hence the `unwrap_or_else`.
String::from_utf8(data).unwrap_or_else(|_| {
// Handle the error explicitly. We're not just letting the program panic.
log::error!("Invalid UTF-8 sequence encountered");
String::from("<?>")
})
}
// The `data` vector is dropped here, its memory cleaned up. No leaks, no dangling pointers.
WebAssembly: Taking Rust to the Browser
This might seem like the polar opposite of systems programming, but it makes perfect sense. The web is the world’s most ubiquitous delivery platform, but JavaScript can be a performance bottleneck for heavy lifting like image processing, physics simulations, or cryptography. WebAssembly (WASM) lets you run bytecode in the browser at near-native speed.
So why compile Rust to WASM? Because you can write incredibly performance-sensitive code with absolute confidence. You’re offloading a heavy task from the JavaScript VM to a tightly compiled module. The tooling here, like wasm-pack, is fantastic. It feels like magic, but it’s just good engineering. You write Rust, and it bundles everything up into an npm package that your JavaScript can call like any other library. It’s a cheat code for the web.
use wasm_bindgen::prelude::*;
#[wasm_bindgen]
pub fn greeter(name: &str) -> String {
// This Rust function can be called directly from JavaScript!
format!("Hello, {}! from Rust/WASM", name)
}
The best practice? Don’t try to replace your entire frontend with Rust. That’s missing the point. Use it for the compute-heavy islands in your JavaScript sea. And for heaven’s sake, minimize data transfer between the JS and WASM worlds; marshaling data across that boundary has a cost.
Command-Line Interfaces (CLI): The Unsung Hero
Every developer eventually needs to whip up a script that outgrows Bash. You could reach for Python or Go, but Rust gives you two killer features: blistering speed and single-binary deployment. No more “just run pip install -r requirements.txt and hope it works.” You compile it on your machine, and you get a single, static binary you can scp to a server and it. just. runs.
The ecosystem is what makes this a joy. The clap crate for argument parsing is a masterclass in API design. Using derive macros, you define your arguments in a struct, and it generates all the parsing logic, help text, and validation for you. It’s declarative, type-safe, and downright pleasant.
use clap::Parser;
/// A simple program to demystify files
#[derive(Parser)]
#[command(version, about, long_about = None)]
struct Args {
/// The path to the file to analyze
path: std::path::PathBuf,
/// Show detailed information
#[arg(short, long)]
verbose: bool,
}
fn main() -> Result<(), Box<dyn std::error::Error>> {
let args = Args::parse(); // That's it. Parsing is done.
let metadata = std::fs::metadata(&args.path)?;
if args.verbose {
println!("{:#?}", metadata);
} else {
println!("File size: {} bytes", metadata.len());
}
Ok(())
}
Pitfall? Be mindful of error handling. A CLI tool needs to communicate clearly with the user. Don’t just unwrap(); use Result and provide friendly, actionable error messages. anyhow and thiserror are your go-to crates for making this painless.
Embedded and IoT: Conquering the Resource-Constrained Frontier
This is where Rust truly flexes its unique muscles. Embedded development is a world of microcontrollers with kilobytes of RAM, no operating system, and absolutely no room for a garbage collector or a runaway null pointer. C has ruled here for decades.
Rust attacks this space with a ferocious advantage: fearless concurrency and zero-cost abstractions. You can use modern, high-level language features like iterators and pattern matching, and the compiler will optimize them all away to code that is as efficient as the most hand-tuned, obfuscated C. You can write safe, expressive code for an interrupt handler without worrying about corrupting shared state because the borrow checker won’t let you create a data race. It’s a superpower.
// A simplified example for an ARM Cortex-M microcontroller
use cortex_m_rt::entry;
use stm32f1xx_hal::{prelude::*, pac, gpio};
#[entry]
fn main() -> ! {
// Get access to the microcontroller's peripherals
let dp = pac::Peripherals::take().unwrap();
let mut flash = dp.FLASH.constrain();
let mut rcc = dp.RCC.constrain();
// Configure the system clock
let clocks = rcc.cfgr.freeze(&mut flash.acr);
// Configure a GPIO pin as an output to blink an LED
let mut gpioc = dp.GPIOC.split(&mut rcc.apb2);
let mut led = gpioc.pc13.into_push_pull_output(&mut gpioc.crh);
loop {
led.set_high(); // Turn LED on
delay::delay_ms(1_000, clocks); // Block for 1000ms
led.set_low(); // Turn LED off
delay::delay_ms(1_000, clocks);
}
}
// Note: The `-> !` return type means this function never returns, which is required for an embedded system's main.
The rough edge? The hardware abstraction layer (HAL) ecosystem is still maturing. You might find yourself dealing with more low-level register crates for a specific chip than you’d like. But the trend is unequivocally toward more polished, unified HALs.
And Beyond: The Rest of the Universe
The list doesn’t stop. Cryptography? The ring crate provides audited, best-in-class implementations. Distributed systems? The async/await ecosystem with tokio or async-std is a powerhouse for writing robust network services. The key takeaway is that Rust is not just a systems language. It’s a stability language. Anywhere the cost of a bug is high—whether it’s a security hole, a crashed server, or a frozen microcontroller—Rust is increasingly the answer. You use it not because it’s easy, but because the problem you’re solving is hard.