5 releases
| new 0.2.0-alpha.1 | Dec 28, 2025 |
|---|---|
| 0.1.1 | Nov 20, 2025 |
| 0.1.0 | Sep 16, 2025 |
| 0.1.0-alpha.2 | Jun 29, 2025 |
| 0.1.0-alpha.1 | Jun 28, 2025 |
#505 in Rust patterns
305KB
6.5K
SLoC
cadd
cadd: painless checked arithmetics and conversions
Features:
- [
ops]: checked arithmetics withResult, informative errors, and backtrace. Cinto:TryIntoalternative for type conversions with better error messages and backtrace. Works for integer types, other primitives, arrays, and string-like types.SaturatingInto: infallible number conversion that returns the closest valid value.non_zeroandto_non_zero(): conversion toNonZerowithResult, informative errors, and backtrace..into_type::<T>()as an alternative tointo()andtry_into()without type inference errors.no_stdsupport (allocis still required).
Introduction
Rust generally offers amazing capability to create safe, correct programs. However, since it is still a systems programming language, there are still a few aspects where the "default" or the easiest way to do something is the cheapest, but not always the correct one. One of the most ubiquitous example is the behavior of arithmetic operations on integer types and casts between them:
- Many arithmetic operators (
+,-,*, ...) and some functions (like.pow()) are unchecked in Release mode. This means that they can silently overflow and wrap, producing a value that is not the true output of the operation. - Some of the operations (like
a / banda.ilog(b)) will panic if their preconditions are unmet. asconversion between integer types will silently truncate values as necessary to fit the output type. It's also capable of reinterpreting signed values as unsigned and vice versa, producing a value that is not equal to the input.
Sometimes this behavior is fine. However, there are many cases where performance is not as important as correctness. For most high level applications, calculating the values precisely is much more important, as these values may represent things like amount of money or amount of physical goods. Arguably, checked math and conversions should be the default. A faster, but potentially wrapping or truncating alternative should only be chosen in specific cases, after ensuring that it will not cause a logic bug.
Rust's core library provides very good APIs for checked operations. The only problem with them is that
they require much more verbose code. Compare a simple a + b with a properly done checked addition:
a.checked_add(b).ok_or(Error::Overflow)?. Any non-trivial code quickly becomes a wall of checked
calls and numerous error conversions.
This is where cadd comes in. This crate aims to provide a set of APIs that are (almost)
as easy to use as the basic operations, while being a safe, correct default. These functions
never wrap, truncate, reinterpret, or panic.
Any errors returned by cadd will contain as much information as possible (to a reasonable limit)
and will also capture a backtrace if it's enabled. This makes it easy to integrate it into
any error handling approach, be it custom error types or "catch-all" errors like anyhow.
Example
The easiest, but error-prone approach:
let amount = price - discount_rate * price / 100;
let output = amount as u32;
A safe approach that uses std checked functions and returns a non-informative error on overflow:
let amount = discount_rate
.checked_mul(price)
.and_then(|v| v.checked_div(100))
.and_then(|v| price.checked_sub(v))
.ok_or_else(|| Error::Overflow)?;
let output: u32 = amount.try_into().map_err(|_| Error::Overflow)?;
A safe and concise approach with traits and functions from cadd::ops,
returning an informative error:
use cadd::{ops::{csub, Cmul, Cdiv}, convert::IntoType};
let amount = csub(price, discount_rate.cmul(price)?.cdiv(100)?)?;
let output = amount.cinto_type::<u32>()?;
Errors may look like this:
failed to compute 20 - 40: u64 overflow
stack backtrace:
0: std::backtrace_rs::backtrace::libunwind::trace
...
Recommended clippy lints
In order to eliminate unintended side effects, wraps, truncations, and signed-vs-unsigned reinterpretation,
it's recommended to set the following clippy lints to warn level:
arithmetic_side_effectscast_possible_wrapcast_precision_losscast_sign_loss
Updating generated code in ops.rs
cargo run -p cadd_generator -- . && cargo fmt && cargo clippy && cargo doc
Updating README.md
cargo install cargo-readme
cargo readme --output README.md
License: MIT OR Apache-2.0