RFA-249 · Case file with fixtures · Case 221 of 694 · Runtime evidence
Why checked_next_power_of_two Returns None for u32::MAX
The next power must be greater than or equal to the input and fit in the same integer type. Above the highest representable power of two, checked_next_power_of_two reports overflow with None.
- Reviewed
- Rust
- Rust 1.98.1, edition 2024
- Targets
- all targets
- Profiles
- dev, release, test
Direct answer
What this Rust failure means
- Why it happens
- The smallest qualifying power requires 33 bits, so no next power greater than or equal to the input fits in u32.
- First discriminating check
- Test the largest representable power of two and the next integer before widening or changing the capacity policy.
Rounding a size upward to a power of two sounds harmless because the input already fits in the integer type. The rounded result may not fit.
The failing program calls checked_next_power_of_two on u32::MAX and expects Some. Rust returns None.
The destination boundary matters
For u32, the largest representable power of two is 1 << 31. Values above it would need to round to 1 << 32, which requires 33 bits and cannot be represented as u32.
u32::MAX is valid input storage, but there is no valid output satisfying the method's contract. Checked arithmetic reports this with None.
This is the same broad lesson as addition overflow: valid operands do not guarantee a representable result. Rounding APIs can overflow even though they look like classification helpers.
“Next” includes the current power
The method returns the smallest power of two greater than or equal to the value. For 16, the result is 16, not 32. The word “next” here means next boundary at or above, not strictly later.
is_power_of_two can make this visible in tests. Values just below, at, and just above each boundary reveal both inclusivity and overflow.
Zero is another special input: it is not a power of two, while next_power_of_two conventions produce one for small zero/one inputs. I include zero rather than inferring its behavior from positive sizes.
checked is the portable failure channel
The non-checked next_power_of_two can panic on overflow when overflow checks are enabled and otherwise wrap. That makes it poor for user-controlled sizes unless an earlier bound proves safety.
The checked method behaves consistently through Option. The repaired program proves a normal rounding from 17 to 32 and the unrepresentable maximum case.
I translate None into a domain error containing the requested size and maximum supported rounded capacity. Falling back to zero can turn overflow into a dangerous undersized allocation or division error later.
Widening is useful only if the consumer also widens
One repair is to convert the input to u64, round there, and obtain 1 << 32. This works only if every downstream API can represent and safely use the wider result.
Casting it back to u32 recreates the same overflow. Converting to usize introduces a target-width difference: it may work on a 64-bit target and fail on a 32-bit target.
For portable file formats or network protocols, I keep the specified integer width explicit. For memory allocation, I validate against usize, allocator limits, and an application resource cap. Representability is only the first safety bound.
Power-of-two capacity is not always a requirement
Hash tables, ring buffers, alignment helpers, and bit masks often use powers of two, but modern library types may choose their own growth strategies. Pre-rounding every request can waste nearly half the range and amplify untrusted allocations.
I document why the invariant is needed. If an algorithm uses index & (capacity - 1), power-of-two capacity is structural. If it is only an assumed optimization, measurement should justify the extra memory.
The caller also needs a policy for inputs beyond the largest power: reject, cap, switch representation, chunk the work, or use a non-power-of-two algorithm. Saturating to the highest power silently produces a value smaller than the requested minimum, breaking the “at least” contract.
Avoid calculate-then-check
Calling a panicking or wrapping method and checking the result afterward is too late. A wrapped zero might pass unrelated validation, while a panic stops control flow.
Use the checked operation first, then validate resource policy on the returned value. If a bound proves safety, keep that proof close enough for a reviewer to connect it to the unchecked call.
What I test
My boundary table includes zero, one, two, three, the largest power of two, one above it, and the integer maximum. For generic code I repeat the cases across integer widths and relevant targets.
I also test the consumer invariant: rounded capacity is a power of two, is at least the request, and stays below the configured resource maximum. Testing only the exact returned number misses why rounding was performed.
The core principle is that rounding upward is arithmetic with a representability boundary. checked_next_power_of_two returns None not because the input is invalid, but because no qualifying result exists in u32. Handling that absence explicitly protects both numerical correctness and allocation policy.