no_std runtime
- Rust course
- Expert
- Lesson 45/46
- 7 min
Write a minimal runtime for no_std.
Final task: no_std runtime
Embedded systems have no operating system; you write a small runtime that runs the tasks yourself. To work without a heap, tasks are kept as function pointers in a fixed-capacity array:
struct Calistirici {
gorevler: [Option<fn() -> u32>; 4],
adet: usize,
}
impl Calistirici {
const fn new() -> Self {
Self { gorevler: [None; 4], adet: 0 }
}
fn ekle(&mut self, g: fn() -> u32) -> Result<(), &'static str> {
let yer = self.gorevler.get_mut(self.adet).ok_or("kuyruk dolu")?;
*yer = Some(g);
self.adet += 1;
Ok(())
}
}const fn new lets the runner be built at compile time as a static.
Running the tasks
The runner calls the tasks in the queue in order and sums their results. In a real embedded runtime (like embassy), tasks are async and woken by interrupts; the basic idea is the same:
impl Calistirici {
fn hepsini_calistir(&self) -> u32 {
self.gorevler.iter().flatten().map(|g| g()).sum()
}
}flatten lets only the Some values of the Options through.
Practice A tiny runtime
Write the Calistirici type above with new, ekle and hepsini_calistir. Add three tasks returning 1, 2 and 3; print N görev, toplam X.
Starter code
fn main() {
// buraya yaz
}Sample solution
struct Calistirici {
gorevler: [Option<fn() -> u32>; 4],
adet: usize,
}
impl Calistirici {
const fn new() -> Self {
Self { gorevler: [None; 4], adet: 0 }
}
fn ekle(&mut self, g: fn() -> u32) -> Result<(), &'static str> {
let yer = self.gorevler.get_mut(self.adet).ok_or("kuyruk dolu")?;
*yer = Some(g);
self.adet += 1;
Ok(())
}
fn hepsini_calistir(&self) -> u32 {
self.gorevler.iter().flatten().map(|g| g()).sum()
}
}
fn bir() -> u32 {
1
}
fn iki() -> u32 {
2
}
fn uc() -> u32 {
3
}
fn main() {
let mut c = Calistirici::new();
for g in [bir as fn() -> u32, iki, uc] {
c.ekle(g).unwrap();
}
println!("{} görev, toplam {}", c.adet, c.hepsini_calistir());
}Expected output
3 görev, toplam 6
Questions and answers
Review the lesson questions together with their answers. To test yourself, solve the lesson interactively.
1 Why was a <code>fn() -> u32</code> pointer used instead of a closure?
- It's more readable
- No reason
- Its size is fixed and it needs no heap or Box Correct answer
- Closures don't exist in no_std
Explanation: Keeping different closure types in one array needs Box<dyn Fn>; a capture-free function pointer solves it without a heap.
2 Skip the empty ones
self.gorevler.iter().____().map(|g| g()).sum()
self.gorevler.iter().flatten().map(|g| g()).sum()
Explanation: On an iterator of Options, flatten yields only the Some values.
3 Piece and role
| Term | Meaning |
|---|---|
[Option<fn() -> u32>; 4] |
Fixed queue |
const fn new |
Compile-time setup |
get_mut(adet) |
Capacity check |
flatten |
Skips empty slots |