What runs before main¶
Level: 301 · deep dive
One line: fn main is not where a Rust program starts — std's runtime runs first and makes decisions you never wrote down — and #![no_main] hands that job back to you, down to the reset handler that has to prepare RAM before a single line of Rust can rely on it.
Stub — an outline, not a lesson. There is no runnable example behind this page yet, so nothing on it has been through the check that backs every other claim in this library. The bullets below are the questions the finished page has to answer.
What it has to cover¶
- What std does on Unix before your
main, read from 1.98.0'slibrary/std/src/sys/pal/unix/mod.rs: reopens any closed standard descriptor on/dev/null, setsSIGPIPEto ignored, installs the stack-overflow handler, storesargc/argvforenv::args, and names the main thread on Apple targets. Thenrt.rsrunsmaininsidecatch_unwind, turns a panic into exit code 101, and flushes stdout on the way out. - Why the
SIGPIPEline is the one people meet: Broken pipe is that single start-up decision, seen fromtool | head. - The refusals that prove the runtime is there.
#![no_std]with an ordinaryfn mainstops aterror: using `fn main` requires the standard library, whose help line points at#![no_main]. Add#![no_main]on macOS and the next failure comes from the linker, which lists"_main"underUndefined symbols for architecture x86_64— the platform still expects a symbol by that name (both on 1.98.0, x86_64 macOS). - On a microcontroller there is no loader. The Embedonomicon's account: the first two vector-table entries are the initial stack pointer and the reset vector, and the reset handler is
extern "C" fn Reset() -> !because there is no frame to return to. What must it do to the.bssand.datasections before Rust code can trust astatic? - What a runtime crate does for you:
cortex-m-rt↗'s#[entry]function must have typefn() -> !and is called by the reset handler after RAM has been initialized (its docs). Which linker-script regions does it expect the application to name? - Rust gives your own code no hook before
main: astaticneeds aconstinitializer. What doOnceLockandLazyLockdo instead, and where does a crate that runs code beforemainget its hook? - A checkable example on the host: a
#![no_std]#![no_main]library exporting#[unsafe(no_mangle)] extern "C"symbols, linked by a C program whose ownmaincalls it — the "no C runtime" build ↗ in the C library is the same idea from the other side.
The trap it exists for¶
Porting a working program to no_std and expecting it to behave like the original minus printing. What goes missing is everything the runtime did silently — the ignored SIGPIPE, the reopened descriptors, the exit code 101 — and on a chip, the initialisation of RAM itself, which nothing in the program's source appears to depend on.
Where this sits¶
The panic handler is the runtime's exit path; this page is its entry path. The linker explains the symbol-resolution step that the macOS failure above comes from, and Broken pipe owns the SIGPIPE consequence in full.
See also¶
- Broken pipe — the start-up decision you are most likely to have already met
- The linker — where
_mainis looked for, and not found - The call stack — the stack pointer the reset vector sets up
- Catching a signal — what std does not install for you
- Cross-compiling — building the binary this page's reset handler lives in
- The Embedonomicon: the smallest
#![no_std]program ↗ and memory layout ↗ - head closes the pipe early ↗ — the shell side of the
SIGPIPEstory
If you are coming from another language¶
- C. The counterpart is the C start-up code that runs before
mainand calls it — on bare metal a start-up file that has the same.dataand.bsswork to do as the Embedonomicon's reset handler. Rust sits on the same layers and adds its own: on a hosted target, theSIGPIPEdecision is written instd(its source comment explains why), not inherited from libc — and on a device the reset handler is a Rust function whose-> !the compiler checks. - C++. Static constructors run before
mainin an order that crosses translation units unpredictably — the static initialization order fiasco. Rust refuses the whole category by requiringconstinitializers forstatic, and moves lazy set-up toOnceLock/LazyLock, where the first use decides the order. - Go. Program execution begins by initialising every package — its
init()functions included — and only then callsmain.main(the Go spec, Program execution). Rust has noinit(): nothing of yours runs beforemainunless a runtime crate or the reset handler calls it. Seemaindoes not wait ↗ for what happens at the other end. - Java. A class is initialised immediately before its first active use (JLS §12.4.1), so "before
main" is spread across the program's lifetime. Rust'sLazyLockis the explicit version of that behaviour, one value at a time.
Po polsku¶
Funkcja main nie jest pierwszą rzeczą, która się wykonuje. Na Uniksie std najpierw otwiera ponownie zamknięte deskryptory standardowe na /dev/null, ustawia ignorowanie sygnału SIGPIPE, instaluje obsługę przepełnienia stosu i zapamiętuje argumenty, a po main zamienia panikę na kod wyjścia 101. Atrybut #![no_main] oddaje tę pracę programiście; na mikrokontrolerze oznacza to procedurę obsługi resetu (reset handler), która musi przygotować pamięć — sekcje .data i .bss — zanim jakakolwiek zmienna statyczna będzie miała sensowną wartość.
Szukaj po polsku: co się dzieje przed main · procedura obsługi resetu · tablica wektorów przerwań · rust no_main reset handler · rust lang_start sigpipe · cortex-m-rt entry