rust-lang / rust-lang/libs-team
ACP: convenient numeric conversions dependent on overflow-checks
Nobody has claimed this yet.
- Dominant language
- Rust
- Stars
- 178
- Forks
- 28
- Avg merge
- 15m
- Merged PRs (30d)
- 1
Description
Proposal
Problem statement
Currently the default way when converting between numeric types is using as. However so many behaviors are encoded in this one little word. There long have been calls for dedicated functions which encode intent better.
In recent work (part accepted, part proposed) I've tried to make this situation better through _cast methods for integers, and to_{float|int} methods:
However, even once these become commonplace there is still a common need unfilled. Namely, when one has a fallible conversion (e.g. narrowing integer, or float -> int) when you are writing performance sensitive code.
Today, you would still use as for this. However this means that even in debug mode, if something went wrong and your fallible conversion failed, you have no way of knowing.
Motivating examples or use cases
As an example, say I am writing some sort of graphics processing code, where I have colors in f32s, and am converting to u8:
for (out, color) in img.iter_mut().zip(colors) {
out.r = (color.r * 255.0 + 0.5) as u8;
out.g = (color.g * 255.0 + 0.5) as u8;
out.b = (color.b * 255.0 + 0.5) as u8;
}
I "know" that my colors are well-behaved and in 0..1, so I want this to run at maximum speed, so I use as as to not block autovectorization or add extra checks. But there's a bug in my program, and silently some color component is actually 1.5, or NaN. With as I would have no way of knowing. With the proposed API below I would write:
for (out, color) in img.iter_mut().zip(colors) {
out.r = (color.r * 255.0 + 0.5).to_int();
out.g = (color.g * 255.0 + 0.5).to_int();
out.b = (color.b * 255.0 + 0.5).to_int();
}
and get maximum speed in release mode, while still getting a panic when running in debug mode for my broken assumptions.
Solution sketch
I propose the following set of functions to be added to all primitive integer and floating point types respectively:
impl IntT {
/// When overflow-checks is enabled this is equivalent to `strict_cast`,
/// without this is equivalent to `as`.
fn cast<Int: CheckedCastFromInt<Self>>(self) -> Int;
}
impl FloatT {
/// When overflow-checks is enabled this is equivalent to `to_int_strict`,
/// without this is equivalent to `as`.
fn to_int<Flt>(self) -> Flt
where Self: FloatToInt<Flt>;
}
Note that to_int_strict is from my other proposal: https://github.com/rust-lang/libs-team/issues/810.
Open questions
- Will these methods being the shortest and most convenient wording lead to people reaching for these even if they ought to be using the
strict_versions for fallible conversions, even in release mode?
Alternatives
One can simply write as, but this gives no errors in debug mode. The 'proper' fallible methods are no real alternative at all as their checks can block autovectorization, or otherwise be needlessly expensive as we 'know' our conversion ought to be infallible based on preconditions.
Links and related work
Contributor guide
No contributing guide indexed for this repository
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
Start by reading the linked libs-team issues 788 and 810, then compare their proposed conversion APIs with the as examples and the overflow-checks behavior described here. The work is done when the API shape and its interaction with strict conversions and release-mode performance have been resolved.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- rust
- Domain
- developer-experience
- Issue type
- Feature
- Difficulty
- 5/5
- Estimated time
- Over a week
- Activity status
- Active
- Clarity
- Mostly clear
- Newbie friendliness
- 35/100