Master the fundamental concepts of rust for systems programming through this focused micro-challenge.
You have read the whole brief, and the concepts above stay free on every task. Writing and running the code needs a plan.
Three hints are available for this task, revealed one at a time inside the code workspace so you can struggle productively before seeing them.
Every task includes starter code, theory, and hidden tests so you can implement and verify locally in the browser.
How it works#![no_std] drops the standard library and keeps core: Option, Result, iterators, but no Vec, files, threads, or networking unless you add alloc yourself. Embassy, Oreboot, and Rust-for-Linux all live here.
rustLoading…
#[no_main]: you supply _start or a board-specific entry#[global_allocator]: only if you pull in alloc for Box/Veccore works on bare metal; std layers OS syscalls, I/O, and synchronization. Cross-compile with --target x86_64-unknown-none or a board JSON when libc is absent.
For this exercise, you will build a minimal no_std binary with a custom entry and panic handler. This task asks you to explain what disappeared compared to cargo new, because every embedded bug that "works in std" traces back to missing runtime support.
Keep the relevant man page, ABI doc, or Rust reference chapter open while you work. When your output disagrees with the reference implementation on the same machine, the mismatch is usually an alignment rule, an off-by-one terminator, or a register slot you misread in GDB. Skim the official documentation for the tool or ABI named in the exercise; the prose changes, but register roles, syscall numbers, and ownership rules stay stable across releases. Treat each failure as a contract test: the CPU, kernel, and borrow checker enforce rules whether or not the tutorial mentioned them explicitly.
Without std there is no String, no println! and no heap, but core::fmt still works if you give it somewhere to write. Implement core::fmt::Write for a fixed-capacity byte buffer, then implement the integer part of Rust's format-spec mini-language on top of it. Everything happens in a buffer you own, the way firmware and kernels format log lines.
One command per line:
cLoading…
FORMAT is ASCII and contains no ". {{ and }} are literal braces. A placeholder is {} or {:SPEC}, where SPEC follows Rust's order [align][#][0][width][type]:
<, > or ^, padding with spaces. Numbers default to >. For ^, the left side gets pad / 2.0x, 0b or 0o (hex, binary, octal).- or the prefix. It overrides the alignment.x, X, b or o. Radix types print the 64-bit two's-complement bits, like Rust does for i64: {:x} of -1 is ffffffffffffffff.The format string is split into pieces: each run of literal text, and each formatted argument (with its padding). Each piece is one write_str call, which is all or nothing: if the piece does not fit in the remaining capacity it returns fmt::Error, and the formatting stops there. Pieces written earlier stay in the buffer.
write: ok: wrote N bytes, or fmt::Error: wrote W of N bytes (N is the full formatted length).show: buffer LEN/CAP: "CONTENTS". cap: buffer: [u8; N].error: invalid format string: unmatched { (or });error: bad format spec {:...} (the whole placeholder);error: bad argument TOKEN;error: K arguments expected, M given (argument for 1);error: missing closing quote;error: capacity must be 1..256;error: bad command: LINE.Input:
cLoading…
Output:
cLoading…
fmt::Write::write_str on your buffer, and do not allocate while writing.format! produces for the same spec, and the tests were checked against it.Hidden tests cover centre alignment with odd padding, # combined with 0 and width, negative numbers in every radix, i64::MIN, a write that fails part-way at the exact capacity, escaped braces, and each error.