NASA’s paper “The Power of 10 Rules” presents a disciplined coding approach designed to increase confidence in software correctness. In safety-critical systems, where failure can cause irreversible harm, this discipline matters: reducing ambiguity, constraining complexity, and making behavior easier to understand, analyze, and verify before defects can propagate into real-world incidents.
The core rules are simple and intentionally strict:
goto statements, setjmp or
longjmp constructs, or direct or indirect recursion.typedef
declarations. Function pointers are never permitted.The stakes are not theoretical.
When safety-critical software fails, consequences are immediate and often irreversible. Notable failures include:
Standards and certification make these practices enforceable.
Standards and certification processes are essential for correctness and reliable operation in safety-critical software. Domain-specific standards such as DO-178C (aerospace), IEC 62304 (medical devices), and ISO 26262 (automotive) define lifecycle controls across requirements traceability, design, coding, testing, and verification. Compliance is mandatory before systems can be certified for deployment in safety-critical contexts, and it depends on rigorous documentation, testing, and independent verification.
Declarative programming can further support this assurance model when applied in the right places. By describing intended state and constraints explicitly, declarative approaches reduce hidden control flow, make dependencies visible, and improve traceability from requirement to implementation artifact. This can strengthen reviewability, static analysis, and reproducibility, especially in build, configuration, and deployment layers. It is not a replacement for verification and testing, but it is a strong force multiplier for them.
Deterministic build environments are part of that same safety case. A system cannot be treated as verified if its build depends on ambient host state, undeclared tools, mutable references, or ad hoc environment variables. In safety-critical work, reproducibility is not convenience engineering; it is assurance engineering. If two approved build hosts can produce different binaries from the same revision, certification evidence weakens, root-cause analysis slows, and rollback confidence erodes.
Some workflows still require controlled impurity, such as hardware-bound vendor tooling or protected local credentials. When unavoidable, impurity must be explicit, justified, narrowly scoped, and recorded in build documentation. Production and certification builds should reject silent impurity and require documented exceptions with reviewer approval.
Practical build-integrity checklist:
A quick implementation comparison makes the difference concrete.
The impact of these rules is easiest to see by comparing a typical unsafe implementation with one designed for correctness.
#include <stdio.h>
#include <stdlib.h>
int *samples = NULL;
int idx = 0;
void process_sensor(void) {
while (1) {
int v;
if (scanf("%d", &v) != 1) break;
if (!samples) samples = malloc(sizeof(int) * 1000);
samples[idx++] = v;
if (idx > 16) {
goto report;
}
}
report:
long sum = 0;
for (int i = 0; i < idx; ++i) sum += samples[i];
double avg = (double)sum / idx;
printf("Avg: %f\n", avg);
}
int main() {
process_sensor();
free(samples);
return 0;
}The use of goto makes control flow harder to reason
about, while the unbounded loop risks resource exhaustion. Dynamic
allocation without strong ownership discipline can leak or corrupt
state. The function is also too long and mixes responsibilities.
Variables are scoped too broadly, index bounds are unchecked, and input
handling is weak, all of which increase risk.
#include <stdio.h>
#include <stdbool.h>
#include <stdint.h>
#define SAMPLE_COUNT 16
static int sensor_read(void) {
int v;
if (scanf("%d", &v) != 1) return INT32_MIN;
return v;
}
#define CHECK(cond, action) do { if (!(cond)) { action; } } while (0)
int compute_average(const int buf[], size_t n, double *out) {
CHECK(buf != NULL, return -1);
CHECK(out != NULL, return -1);
CHECK(n > 0, return -2);
long sum = 0;
for (size_t i = 0; i < n; ++i) {
sum += buf[i];
}
*out = (double)sum / (double)n;
return 0;
}
int main(void) {
int samples[SAMPLE_COUNT];
size_t count = 0;
for (size_t i = 0; i < SAMPLE_COUNT; ++i) {
int v = sensor_read();
if (v == INT32_MIN) {
break;
}
samples[count++] = v;
}
double avg;
int rc = compute_average(samples, count == 0 ? 1 : count, &avg);
if (rc != 0) {
fprintf(stderr, "compute_average failed (rc=%d)\n", rc);
return 1;
}
printf("Average over %zu samples: %.3f\n", count, avg);
return 0;
}No goto statements are used, so control flow is clearer.
A fixed upper bound is enforced by SAMPLE_COUNT, making
iteration limits explicit and analyzable. Dynamic allocation is avoided
by using a fixed-size samples array. Functions remain short
and focused, return values are checked, and error paths are explicit
through CHECK. Variables are scoped tightly, preprocessor
use is minimal, and pointer usage stays straightforward. The code is
also written to compile cleanly with strict warnings, which supports
static analysis and stronger verification.
When compiling this code, the most pedantic warnings should be enabled with:
gcc -Wall -Wextra -pedantic -o safe_avg safe_avg.cCorrect source code is necessary, but not sufficient. In safety-critical systems, the build and release pipeline must be as deterministic, auditable, and constrained as the software it produces.