It is straightforward; you only need to do the intermediary arithmetic in a representation that includes all possible values of each input type. In other words, to add an i8 and a u16, which have ranges [-128 . . +127] and [0 . . 65535], you need to do the computation in a type whose representation includes all values between -128 and 65535, inclusive. The minimum such representation in Rust is i32.
Of course overflow can still occur in such a larger representation, depending on the details of the computation, but at least arithmetic in the larger representation is defined for all potential sets of inputs.
The underlying problems discussed in this thread are due to conflating different mathematical rings of integers:
a) arithmetic in the infinite ring of all integers, which is learned at a young age, with
b) arithmetic in the finite ring of unsigned integers implied by a given storage representation, and
c) arithmetic in the finite ring of signed integers implied by the same storage representation.
Arithmetic in the latter two mathematical rings “wrap” between the minimum value of the representation and the maximum value of the representation. Conventional add, subtract and multiply operators on integers are defined only on a); they do not “wrap”, and thus the result of an operation in ring a) potentially can overflow the representation of the input values.
The add, subtract and multiply operators of the Rust Wrapping type, and Rust’s wrapping_add, wrapping_sub and wrapping_mul operators, are defined on b), and separately on c), but not on a mixture of b) and c). These operators never overflow; instead they “wrap”, providing the remainder of the operation (op) modulo the representation size. For these operators, when x and y are both in uN the result in uN is
(x op y) % (1 << N)
and when x and y are both in iN the result in iN is
(x op y + (1 << N - 1)) % (1 << N) - (1 << N - 1)
where the additional complexity over simple
(x op y) % (1 << N - 1)
exists to handle a result of
-(1 << N - 1) correctly. (To see why the simpler formula amost works, recall that the result of the
% operator has the sign of the first operand.)
Note that overflow can also occur whenever a value of one type is converted to a different type whose range does not include the first type. Thus the result of the i32 computation of adding an i8 and a u16 can overflow when converted to either u16 or i16, truncating high-order bits of the result value with respect to the output representation.