Rust
Completed
-
Section 1: Getting Started
-
Section 2: Core Syntax
-
Section 3: Ownership and Borrowing
-
Section 4: Structuring Data
-
Section 5: Collections and Error Handling
-
Section 6: Traits and Generics
-
Section 7: Concurrency
-
Section 8: Building for the Web
-
Section 9: Memory and Performance
-
Section 10: More Standard Library and Ecosystem
-
Section 11: Advanced Rust
-
Section 12: Rust for Systems and WebAssembly
-
Section 13: Tooling and Best Practices
-
Section 14: Data Structures and Algorithms in Rust
-
Section 15: Practical Projects
-
Section 16: Interview Practice
-
Section 17: std::collections In Depth
-
Section 18: std::io and std::fs In Depth
-
Section 19: std::net
-
Section 20: std::option and std::result In Depth
-
Section 21: std::iter In Depth
-
Section 22: std::sync In Depth
-
Section 23: std::string and std::str
-
Section 24: Cargo and Crates.io Ecosystem
-
Section 25: Popular Crates Ecosystem
-
Section 26: Rust Memory Model Deep Dive
-
Section 27: More Practice Exercises
-
Section 28: More Interview Practice
-
Section 29: Async Rust Deep Dive
-
Section 30: Tokio Ecosystem In Depth
-
Section 31: Error Handling Ecosystem Deep Dive
-
Section 32: Serde In Depth
-
Section 33: Web Development with Rust Deep Dive
-
Section 34: Database Access Ecosystem
-
Section 35: Rust for Embedded Systems Deep Dive
-
Section 36: Rust Macros In Depth
-
Section 37: Advanced Trait System
-
Section 38: Unsafe Rust In Depth
-
Section 39: Rust CLI Development
-
Section 40: Testing Ecosystem Deep Dive
-
Section 41: WebAssembly Deep Dive
-
Section 42: Rust Design Patterns
-
Section 43: More Data Structures in Rust
-
Section 44: Final Practice Projects
-
Section 45: Rust Performance Optimization
-
Section 46: Rust Ecosystem Tooling
-
Section 47: More Interview and Review
70: Embedded Rust Basics (no_std)
Imagine you've lived your whole life in a fully furnished, modern apartment. You have running water, electricity, a refrigerator, and a trash collection service. You don't think about how the water gets to the tap or how the power reaches the outlets; you just use the "services" provided by the building. This is exactly what std (the Rust Standard Library) is for you. It provides the "utilities"—heap allocation, file system access, and networking—by leaning on the underlying Operating System (Windows, Linux, macOS).
Now, imagine you're going on a hardcore backpacking trip into the wilderness. You've left the apartment behind. There is no plumbing, no power grid, and no one to take out your trash. You only have what you can fit in your pack. If you want water, you have to find a stream and filter it yourself. If you want fire, you have to strike a flint. This is no_std. You are stripped of the luxuries of an OS, and you're interacting directly with the raw hardware.
Cutting the Cord with #![no_std]
To tell the Rust compiler that we're heading into the wilderness, we use an inner attribute at the very top of our crate root. I want you to notice the exclamation mark; it's an inner attribute, affecting the entire crate.
#![no_std]
The moment you add this, the compiler stops linking the standard library. You'll suddenly find that std::vec::Vec, std::string::String, and std::fs are gone. It's a bit jarring at first, but it's necessary because those types assume there is an allocator (a heap) and an OS to manage memory and files. On a tiny microcontroller with 32KB of RAM, those assumptions are dangerous lies.
Living off the Land with core
You aren't completely stranded, though. Rust provides a crate called core. Think of this as your "survival kit." It contains everything that doesn't require an OS to function: basic types, Option, Result, and most of the iterator logic you're used to. In a no_std environment, the compiler automatically maps things like core::option::Option to just Option, so your logic remains mostly the same.
But here is the catch: no Vec and no String. If you need a collection, you'll either have to use fixed-size arrays [T; N] or bring in a third-party crate like heapless that provides "stack-allocated" versions of these structures. I've spent far too many hours trying to use a String in a bootloader only to realize I didn't have a memory allocator defined.
Handling the Crash
In a normal program, when your code panics, the standard library catches it, prints a nice error message to the console, and exits the process. But remember: we have no console and no OS to "exit" to. If the hardware panics in no_std, the CPU literally doesn't know what to do next.
You are required to define your own panic_handler. This is a function that the compiler calls when something goes horribly wrong. In a real production environment, you might log the error to a flash chip or trigger a system reset. For basics, we usually just loop forever to freeze the system for debugging.
#[panic_handler]
fn panic(_info: &core::panic::PanicInfo) -> ! {
loop {
// Spin forever. In a real chip, you'd maybe blink an LED rapidly.
}
}
The ! return type is the "never" type. It tells Rust that this function will never return, which is exactly what happens when your embedded device hits a fatal error.
Talking to the Metal
Without std::io, how do you actually do anything? In embedded Rust, we use Peripheral Access Crates (PACs) and Hardware Abstraction Layers (HALs). Instead of calling println!, you write values to specific memory addresses called "Registers."
Essentially, the hardware designers mapped the physical pins of the chip to specific addresses in memory. To turn on an LED, you don't "call a function" in the traditional sense; you flip a specific bit at a specific memory address. The HAL crates wrap these ugly memory writes into nice Rust functions so you don't have to memorize a 1,000-page datasheet just to blink a light.
📋 Practical Task
Building a Bare-Metal Heartbeat LED
You are tasked with setting up the skeleton for a "Heartbeat" indicator on a microcontroller. Since we don't have a physical chip in this browser environment, you will write the no_std boilerplate required to make this work.
Create a main.rs file that satisfies the following requirements:
- Disable the standard library.
- Implement a
panic_handlerthat enters an infinite loop. - Define a
main` function that is marked as#[no_mangle](to prevent the compiler from changing the name, which the hardware bootloader needs to find the entry point). - Inside
main, simulate "toggling a register" by creating astatic mutvariable namedLED_REGISTERand flipping its value from0to1using anunsafeblock.
Constraint: You must not use any std imports. Only use core.
There are no comments for now.