2026-07-13

Making My Rust Program Space-Flight Ready

Yesterday someone published a crate called hugebool. It has one export, a type called Booléan, and the docs describe it as "The strongest, radiation-hardened Booléan (16-bytes wide)". A normal Rust bool is one byte. This one is sixteen, which makes it sixteen times as much boolean, and I hear you thinking: why on earth would anyone need that? I'd argue that's exactly the point. Nobody on earth does.

Or so I thought.

In 2003 an electronic voting machine in Schaerbeek, Belgium gave a candidate 4,096 extra preference votes. That number is 2^12 exactly, the official explanation is a single flipped bit, and the error was only caught because she ended up with more votes than were mathematically possible in her district. ECC memory exists because this happens often enough to matter. My laptop doesn't have ECC. My programs have been running unprotected this whole time, at sea level, like fools.

A boolean with a safety margin

The entire crate is one enum:

#[repr(u128)]
pub enum Booléan {
    falsé = 0x0,
    trué = 0x55aa55aa55aa55aa55aa55,
}

The accents are there for a reason. bool, true and false are keywords, so the radiation-hardened versions needed names of their own, and the author went with French.

The bit pattern is where I stopped laughing and started taking notes. 0x55 is 01010101 and 0xAA is 10101010, the same alternating patterns memory testers have been writing into RAM for decades. falsé is 128 zeroes. trué has 44 ones spread across the low 11 bytes (the top 5 bytes are zero, which I can only assume is headroom). For radiation to silently turn one valid value into the other, it has to flip exactly those 44 bits and no others. Compare that to a normal bool, where a single hit turns true into false instantly and with total confidence. There's no wreckage to investigate afterwards, because the wreckage is a perfectly valid boolean.

The same single bit flip hitting a 1-byte bool and a 16-byte Booléan. The bool silently becomes false. The Booléan becomes an invalid value that is still 43 bits from falsé, so the next read repairs it.

Booléan also derefs to bool, so if *launch_ready works and every if statement it touches is now aerospace grade.

Reads that repair

hugebool makes corruption detectable, since 2^128 minus two of all possible values are invalid. I wanted it repairable too. If a value isn't exactly trué or falsé, count how many bits separate it from each and pick the closer one:

const TRUÉ: u128 = 0x55aa55aa55aa55aa55aa55;

fn decode(raw: u128) -> Booléan {
    let flips_from_falsé = raw.count_ones();
    let flips_from_trué = (raw ^ TRUÉ).count_ones();
    if flips_from_trué < flips_from_falsé {
        Booléan::trué
    } else {
        Booléan::falsé
    }
}

This is nearest-match decoding, the same idea ECC memory is built on, implemented with two calls to count_ones(). Since the valid values differ in 44 bits, any corruption of up to 21 bits decodes back to the original. At exactly 22 flips in the wrong places the value sits equidistant between the two, and my decoder returns falsé, because if we genuinely can't tell anymore I'd rather the engines not fire.

Why stop at booleans

Real spacecraft apply redundancy to whole computers, and the classic trick is triple modular redundancy: run three copies, compare, and go with the majority. The Space Shuttle flew four flight computers voting against each other, plus a fifth running independently written software in case the bug was in the software itself. I don't have a Space Shuttle budget, but I do have generics.

pub struct Rad<T> {
    copies: [T; 3],
}

impl<T: Eq + Copy> Rad<T> {
    pub fn new(value: T) -> Self {
        Rad { copies: [value; 3] }
    }

    pub fn read(&mut self) -> T {
        let [a, b, c] = self.copies;
        let winner = if a == b || a == c { a } else { b };
        self.copies = [winner; 3]; // scrub: heal the outvoted copy
        winner
    }
}

Note that read takes &mut self, because reading also repairs, and Rust makes you admit that in the signature. The repair step is called scrubbing and real systems do it too: satellite memory controllers walk through RAM in the background rewriting anything ECC flags, so single-bit errors never get the time to accumulate into something unvotable.

Rad reads a value by majority vote across its three copies. The corrupted copy is outvoted two to one, and the scrubber rewrites it with the winning value.

Nothing in Rad cares that the payload is a boolean. Anything Eq + Copy votes the same way, so the rest of my imaginary flight state gets hardened with the same eight lines:

#[derive(Clone, Copy, PartialEq, Eq)]
enum FlightMode { Prelaunch, Ascent, Coast, Reentry }

let mut mode = Rad::new(FlightMode::Ascent);
let mut altitude_m = Rad::new(31_547u32);
let mut retries_left = Rad::new(3u8);

Integers actually need this more than my Booléan does. Every one of a u32's four billion values is valid, so a bit flip in a plain altitude_m just produces a different altitude, and there's nothing to detect after the fact. Schaerbeek was exactly this: a counter, one flipped bit, and a result that looked fine until someone did the math by hand. Majority voting doesn't need invalid values to exist, because it only compares the three copies against each other, so it covers types where the hugebool trick has nothing to work with.

Stack the two schemes and Rad<Booléan> stores every boolean fact three times at 16 bytes a copy. That's 48 bytes, or 384 bits, to hold one bit of information, a storage efficiency of 0.26 percent. The other 99.74 percent is margin. A Vec<Rad<Booléan>> with a million feature flags in it costs 48 MB of RAM, and RAM is a lot cheaper than a Mars mission.

What the hardening costs

I benchmarked it. A billion reads of each variant, rustc -O, with one bit pre-flipped so the correction paths do real work instead of getting predicted away:

plain bool:    0.44 ns per read
decode u128:   3.24 ns per read
vote + scrub:  5.13 ns per read

The fully hardened read costs about 12 times a normal one and still manages 195 million corrected reads per second. If boolean reads are your program's bottleneck, you have a more interesting blog post to write than I do.

The part where physics wins

I should be honest about what this protects: the bytes, while they sit in my process's heap. The moment a value is loaded into a register it's ordinary bits in ordinary silicon again. The instruction pointer isn't hardened. The page tables aren't. The kernel, the allocator and the branch predictor are all one energetic particle away from ruining everyone's afternoon, and no amount of enum design can protect them.

That's why actual missions harden the silicon instead. The RAD750, the computer in the Curiosity and Perseverance rovers, runs at about 200 MHz and costs somewhere around $200,000 a unit. My scheme costs 47 extra bytes per boolean and two bit counts, and protects a strict subset of what the RAD750 protects, the subset being my heap allocations and my feelings.

Still, the core idea survives the joke. Rust already moves memory errors from runtime to compile time, but this also moves a class of hardware errors from silent to detectable, using nothing but the type system's refusal to let invalid values pass as valid ones. If you have a boolean you care about, it's sixteen of the most reassuring bytes you can allocate.