CodeBlanks Logo Icon CodeBlanks

no_std runtime

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

main.rs
fn main() {
    // buraya yaz
}

Sample solution

main.rs
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() -&gt; 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

Question
self.gorevler.iter().____().map(|g| g()).sum()
Answer
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

All lessons in Expert