Safety defaults¶
Is "no recursion / no dynamic allocation" really the default?¶
No. Those are NASA/JPL Power of 10 rules for --safety-level=critical. The default is --safety-level=standard, which permits recursion and fc_alloc — appropriate for almost all fastC code, including agent runtimes that are inherently allocator-heavy. Critical mode is opt-in for the embedded / safety-critical niche where Rust is not competing hard.
What's on by default in standard mode?¶
| Power of 10 rule | Standard | Critical |
|---|---|---|
| 1: No recursion | — | ✓ |
| 2: Bounded loops | ✓ | ✓ |
| 3: No dynamic allocation | — | ✓ |
| 4: Function size limit (60 lines) | ✓ | ✓ |
| 5: Assertion density | planned | planned |
| 6: Minimal scope | by design | by design |
| 7: Check return values | by design | by design |
| 8: No preprocessor | by design | by design |
| 9: Single-level pointers | ✓ | ✓ |
10: Zero warnings (--strict) |
opt-in | opt-in |
Three columns of "by design" rules are baked into the language and don't need to be opted-into — fastC has no preprocessor, no implicit conversions (no truncation surprises), and no swallowed return values (every call result either binds to a let, gets explicitly discard-ed, or is the function's return value).
How do I check what's enforced for my safety level?¶
Lists every rule with its enabled / disabled / planned state for the requested level.
Why is Power of 10 even in the picture?¶
fastC's design is rooted in NASA/JPL's "Power of 10" rules for safety-critical code, developed by Gerard Holzmann for the Mars Science Laboratory mission. We treat the rules as a maturity ceiling: standard mode picks the rules that pay for themselves everywhere; critical mode enables the rest for code where the answer to "is this allowed to allocate?" is a hard no.
Critical mode is not the default because it would make most real-world fastC code ergonomically painful for no proportionate safety gain. Agent runtimes need vec::push (allocates). Compilers need recursion. Most working programs need to choose --safety-level=critical only when their domain requires it.
Module-level mandatory headers (v1.3)¶
Power of 10 enforces function-level discipline. Stage 1.3 added a complementary structural-safety layer at the module boundary via //! @module / @owns / @arch / @depends / @threading / @invariants headers. See Modules for the full grammar.
What the module-graph pass enforces when at least one module declares a header:
@ownsuniqueness — every namespace has exactly one owner module. Two modules claiming@owns = "logging"is a compile error.@dependsexhaustiveness — everyuse mod::Xfrom a header-bearing module body must point inside the declared@dependslist. Implicit cross-module reaches are rejected at parse time.@archDAG layering — a module whose@archranks "lower" cannot depend on a higher-arch module. Architectural inversions are diagnosed before they ship.
Like Power of 10, the headers are opt-in (lenient by default; strict via [package] strict_modules = true in fastc.toml). The wedge: a reviewer reading a fastC project tree with headers in place gets the architectural contract from the source — without an out-of-band design document that drifted six months ago.