Nov 06, 2025
Why is Golang's atomic.Bool 32 bits (not 8 bits)
TIL that Go uses uint32 instead of uint8 or just bool.
At first glance, that seems counterintuitive because ideally a bool is just a bit. Why do we need the extra 31 bits?
The runtime picks a 32-bit slot so it can rely on the same word-sized compare-and-swap instruction on every architecture Go supports. Some CPUs expose byte-sized atomics, but others do not guarantee them (or require strict alignment), so Go standardizes on word-aligned storage instead.
// A Bool is an atomic boolean value.
// The zero value is false.
//
// Bool must not be copied after first use.
type Bool struct {
_ noCopy
v uint32
}Let's see how CompareAndSwap works for a bool
// CompareAndSwap executes the compare-and-swap operation for the boolean value x.
func (x *Bool) CompareAndSwap(old, new bool) (swapped bool) {
return CompareAndSwapUint32(&x.v, b32(old), b32(new))
}CompareAndSwapUint32 is a Go-level wrapper around a lower-level atomic CAS operation in the runtime
This function is implemented in assembly.
TEXT ·CompareAndSwapUint32(SB),NOSPLIT,$0
JMP internal∕runtime∕atomic·Cas(SB)On amd64, bool swap is implemented as shown below in assembly.
// bool Cas(int32 *val, int32 old, int32 new)
// Atomically:
// if(*val == old){
// *val = new;
// return 1;
// } else
// return 0;
TEXT runtime∕internal∕atomic·Cas(SB),NOSPLIT,$0-17
MOVQ ptr+0(FP), BX // Load pointer address into BX
MOVL old+8(FP), AX // Load expected value into AX (required by CMPXCHG)
MOVL new+12(FP), CX // Load new value into CX
LOCK // Lock prefix for atomicity
CMPXCHGL CX, 0(BX) // Compare AX with [BX], if equal swap with CX
SETEQ ret+16(FP) // Set return value based on ZF (zero flag)
RET