checked_ Names, With the Answer OptionalStatus: Accepted
Date: October 4, 2026
Answers: the owner’s question, after the build-time evaluator’s integers
left i128 (0.0.416), whether the places where the compiler reached for a
wider number out of convenience would read better with a name for the
alternative - where the uses overlap
Related: ADR-285 D1 (an overflow aborts), D4 (a conversion
that may not fit aborts), D6-D7 (wrapping and saturating have names, not
operators), D13 (truncation has a name that carries its destination);
ADR-294 D11 (an integer while the program is built is a
magnitude and a sign); Part I 2.3 (T? and ??)
Nikaia has three things to say about an integer operation whose answer does
not fit: abort (+, as, D1 and D4), wrap (wrapping_add, D6) and stop at
the limit (saturating_add, D6), plus keeping the low digits of a conversion
(truncating_i32, D13). It has no way to ask - to get the answer where
there is one and to find out, as a value, where there is none.
The compiler’s own Nikaia shows what that costs. Where it asks, it asks by hand:
tools/integers.nika and tools/fold.nika add and multiply with
wrapping_add and wrapping_mul and then compare the result against an
operand to find out whether it wrapped - five places, each one a small proof
the reader has to redo;integers.nika’s integer_as_count turns a u64 magnitude into an i64
in fifteen lines of comparisons against 9223372036854775807 and
9223372036854775808;fold.nika’s fits keeps twelve constants for the ends of the eight
integer types.And the Rust that is still to move asks the same question about twenty-five
times (i32::try_from(v).is_ok(), checked_add), which is how i128 got in:
compute wide, then compare. Moving that Rust means writing each of those again
by hand, or having the name.
checked_add and the rest
returning Option<T>, and T::try_from(v) for a conversion, returning
Result<T, TryFromIntError>.addingReportingOverflow (the wrapped result and a flag) and
Int32(exactly:), an optional initialiser.@addWithOverflow (a tuple) and std.math.cast(T, v), an
optional.checked { } blocks, which throw; Go has nothing and leaves
the comparison to the program.The optional is the common answer for the conversion (Swift, Zig, Rust’s
.ok()), and Rust’s own checked_ names are the optional for the arithmetic.
| Option | For | Against |
|---|---|---|
| A. Leave it to the program (Go) | Nothing to add | The five hand-made checks above stay, and every Rust site that moves adds one |
B. An operator (a +? b) |
Short | D6 already said no to an operator for the other two families, for the reason that holds here: rare, and worth seeing by name |
C. checked_ names with a T? answer, and checked_<type>() conversions |
One more family in the pattern D6, D7 and D13 set; ?? already reads an optional; Rust’s own names, so the lowered code is idiomatic |
Thirty-three ledger entries |
D. A Result with an error (Rust’s try_from) |
Says why | There is one reason - it does not fit - and an error type for one reason is ceremony |
Option C.
checked_add, _sub, _mul, _div, _rem, _neg and _abs answer T?.a.checked_add(b) is the sum where it fits and null where it does not, for
i32, i64, u8, u32 and u64 - the types D6’s names exist for (u8 by ADR-285 D41). An unsigned
type has no _neg and no _abs, as it has no wrapping_neg.
let sum = a.checked_add(b) ?? return null
null too.checked_div and checked_rem answer null for a divisor of zero as well as
for the one quotient that overflows (the most negative number by -1). Unlike
wrapping_div, which still aborts on zero (D6), a name that asks whether there
is an answer gets one for every input: there is no quotient by zero, and
saying so is what the name is for.
checked_shl and no checked_shr.Rust’s ask whether the count is inside the width, not whether the bits that leave were zero. A name saying checked over a shift that still loses digits would claim more than it does; a program that wants the second asks it with a multiplication.
checked_i32(), checked_i64(), checked_u8(), checked_u32() and checked_u64() answer the destination, optional.n.checked_i32() is n as an i32 where an i32 holds it and null where
it does not - the conversion D4 of ADR-285 aborts on, asked instead. The
destination is in the name, as it is for truncating_ (D13), and for the same
reason: one ledger entry per destination. They exist only out of a type that
has values the destination lacks; a widening always fits and is written as.
An f64 has none of them: a float’s conversion is not a range question alone.
The arithmetic is Rust’s checked_add and the rest by name; a conversion is
<T>::try_from(n).ok(). Neither can abort, and neither leans on
overflow-checks.
tools/integers.nika and tools/fold.nika ask instead of comparing, and
fold.nika is built on integers.nika’s arithmetic rather than beside it.checked_ call, with no wide number in between.std.contracts).u8 - which has none of D6’s names yet (ADR-285’s
Built column).