Rust Meets Bare Metal: I’m Building an OS While Barely Knowing Rust
It Involved Tears, a Dead Keyboard, and a Questionable Life Choice
Why Would Anyone Build an OS? (A Totally Fair Question)
Let’s be real — staring at Windows or Linux and thinking, “I could do this” is either delusional or genius. I’m choosing to believe it’s the latter.
Am I qualified for this? Absolutely not.
Will that stop me? Also no.
This blog series is my attempt to build an operating system in Rust from absolute zero. No prior OS development experience. No deep Rust expertise. Just caffeine-fueled determination and an unhealthy curiosity for how computers actually work.
Welcome to my journey — where I document this brilliantly terrible idea in real time.
What’s the Plan? (Good Question. Wish I Had One.)
I don’t just want to learn Rust — I want to throw myself into the deep end, flailing, and see what happens. The goal?
- Boot up a Rust-powered kernel
- Make it do something useful
- Hopefully, not break my computer in the process
This is either going to be a masterpiece of self-taught engineering or a cautionary tale. Either way, you get front-row seats.
Why Rust? (Because I Enjoy Pain, Apparently)
Rust is like a brutally honest best friend — it will stop you from making mistakes, but not before making you feel deeply incompetent.
Here’s why I’m choosing Rust for OS development:
- Memory Safety: No more segmentation faults haunting my dreams.
- No Garbage Collector: Perfect for low-level system programming.
- Pain? Guaranteed.
I could’ve picked C like the old-school OS devs, but I enjoy making life harder for myself.
This Isn’t a Tutorial. It’s a Chaos-Fueled Experiment.
There are already great tutorials on writing an OS in Rust. This is not one of them.
This is a firsthand account of what happens when someone with more enthusiasm than expertise decides to take on systems programming
Expect:
✅ Unfiltered coding struggles (because debugging is 80% of programming).
✅ Weird but useful Rust insights (things I’ll learn the hard way so you don’t have to).
What you won’t get:
❌ A structured, step-by-step guide (unless you enjoy learning from my mistakes).
❌ Perfect, clean code (we don’t do that here).
❌ Sanity (mine is already slipping).
What to Expect in This Blog
Here’s what I plan to tackle:
- Setting up a Rust environment for bare-metal development (Easy, right?).
- Booting a “Hello, World” kernel (Expect a lot of panic — both the Rust kind and the emotional kind).
- Diving into bootloaders, memory management, and beyond (Pray for me).
You’ll Learn:
How computers actually boot..
How to make your résumé scream “I tamed Rust. Hire me or regret it.”
Setting Up My Rust OS Dev Environment (a.k.a. Installing Pain):
I started with the basics:
✅ Installing Rust Nightly (because stable Rust doesn’t play nice with bare-metal programming).
✅ Disabling the standard library (no_std
) because an OS doesn’t have the luxury of built-in conveniences.
✅ Choosing a bootloader—and oh boy, that was a rabbit hole.
Installing the Essential Tools
To get started, I needed to install the right tools:
Install Rust Nightly:
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
rustup default nightly
rustup default nightly
: Sets nightly as the default Rust version for this project.
Add Required Components:
rustup component add rust-src llvm-tools-preview
1️⃣ rust-src
(Rust Source Code)
- This package provides the full Rust standard library source code.
- It is needed for
no_std
development because when you’re working without the standard library (std
), the compiler needs access to the raw source to compile necessary core components. - Without
rust-src
, Rust cannot compilecore
andalloc
crates, which are essential forno_std
development.
📌 Why is this needed?
- When building an OS, you don’t use Rust’s default runtime or standard library (
std
). - Instead, you use the
core
andalloc
libraries, which are part of Rust but require source code access to compile properly.
2️⃣ llvm-tools-preview
(LLVM Tools Preview)
- This is a collection of LLVM-based tools that Rust uses for advanced compilation, linking, and debugging.
- It includes tools like
llvm-objcopy
,llvm-objdump
,llvm-nm
, andllvm-addr2line
, which are required for cross-compilation and kernel development.
📌 Why is this needed?
- OS and kernel development require custom linking and binary manipulation.
LLVM tools help with:
- Converting Rust binaries into bootable formats.
- Debugging low-level assembly output.
- Extracting and analyzing compiled code.
Install Bootimage for Easy Bootloader Integration:
cargo install bootimage
Now that the tools were installed, it was time to decide how to actually boot my kernel.
Bootimage vs. Limine: My First Existential Crisis
There are two main ways to boot a Rust kernel:
Bootimage — A Rust-friendly tool that magically bundles your kernel with a built-in bootloader.
- Pros: Easy to set up, great for beginners.
- Cons: No UEFI support, limited control.
Limine — A real-world bootloader used in serious OS projects.
- Pros: Supports both BIOS & UEFI, highly customizable.
- Cons: More complex, requires manual configuration
Why Not Write a Custom Bootloader?
At this point, you might be wondering:
“Wait, aren’t we writing an OS from scratch? Why not build our own bootloader?”
Great question! The short answer:
- I value my sanity (what’s left of it).
- This isn’t 1995. Tools like
bootimage
do the heavy lifting. - I’d rather focus on the fun part — making Rust do cursed things at the kernel level.
Writing a bootloader is insanely complex. It requires low-level assembly, manually handling BIOS or UEFI firmware, and a deep dive into bootstrapping hardware. If you’re feeling brave and want to write your own, check out this fantastic blog series by Kevin Trieu:
🔗 Introduction to Writing a Custom Bootloader
For now, we use bootimage and focus on the kernel.
Setting Up a New Rust Kernel Project:
we need to set up a Rust project. But this isn’t your usual “cargo run” kind of deal — because operating systems don’t run in a normal environment. They have no standard library, no OS features, and no safety nets. Just raw, terrifying hardware.
What is Cargo? (And Why Should You Care?)
If you’re new to Rust, Cargo is Rust’s package manager and build system. It handles dependencies, compilation, and even testing. It’s basically Rust’s version of Make, CMake, and npm — all in one.
With Cargo, you can:
✅ Create new Rust projects
✅ Compile and build code easily
✅ Manage dependencies (external crates)
✅ Run tests and benchmarks
✅ Generate documentation
Think of it as your Rust project’s manager that does all the boring work for you
Creating the Kernel Project:
We’re going to create a new Rust project using Cargo:
cargo new UntitledKernelProject --bin
cd UntitledKernelProject
Here’s what’s happening:
cargo new UntitledKernelProject --bin
→ Creates a new Rust project with a binary executable.cd UntitledKernelProject
→ Moves into the project folder.
This generates a project with the following structure:
UntitledKernelProject/
│── src/
│ ├── main.rs # Our main Rust file
│── Cargo.toml # Our project configuration file
│── .gitignore
What’s This Cargo.toml
File?
Cargo.toml
is where we define our project settings, dependencies, and compiler configurations. Here’s what the default file looks like:
[package]
→ Contains metadata about our project.name
→ The project name (UntitledKernelProject).version
→ The current version of our project.edition
→ Rust has different editions
If you don’t specify an edition, Rust assumes the latest stable one. So yes, it’s required, but Cargo will pick the default if you don’t set it.
Configuring the Kernel for Bare Metal
Rust is usually used for applications that run on an operating system, like Linux or Windows. But since we’re building an OS from scratch, we don’t have access to things like:
❌ The standard library (std
)
❌ System calls (because there’s no OS to call)
❌ Memory management (we have to do this ourselves)
Disabling the Standard Library
We need to tell Rust that we’re working in a bare-metal environment by disabling std
.
Open Cargo.toml
and modify it like this:
[dependencies]
[profile.dev]
panic = "abort"
[profile.release]
panic = "abort"
This tells Rust how to handle panics (serious errors that crash the program) in both debug mode (dev
) and release mode (release
).
What is a Panic?
In Rust, if something goes wrong (like dividing by zero or accessing invalid memory), the program panics — basically, it stops running. Rust has two ways to handle this:
- Unwinding: Tries to clean up (undo) everything before the program stops.
- Aborting: Instantly stops the program without cleaning up.
By default, Rust unwinds the stack, but for OS development, we disable unwinding and use abort instead.
Why Use panic = "abort"
for an OS?
Since you’re building an operating system, your code runs without Rust’s standard library (no_std
). That means:
✅ There’s no system (like Windows/Linux) to help with error handling.
✅ Cleaning up (unwinding) is not needed in an OS.
✅ Unwinding makes the program bigger and slower.
By setting panic = "abort"
, your OS immediately stops on an error, instead of wasting time trying to clean up. This makes your OS smaller, faster, and simpler.
Think of it like this:
- Unwinding: A computer game crashes, but it saves your progress before closing.
- Aborting: The game crashes and instantly shuts down with no saving.
Since an OS should manage errors on its own, aborting is the better choice.
🔥 How You Can Support This Madness
If you enjoyed this, show some love!
👏 Clap on Medium.
📝 Drop a comment (so I know I’m not suffering alone).
📢 Share this with fellow nerds who enjoy cursed Rust experiments.