-
Notifications
You must be signed in to change notification settings - Fork 24
Description
Proposal
Problem statement
It is a fairly common mistake to write x.max(8)
and expect "x but no more than 8", when it's in fact the opposite.
Motivating examples or use cases
While I don't have any concrete examples from my own codebases (because I've fixed any I've found), I think everyone has written something like arr[..x.max(arr.len())]
at some point.
There are also a few clippy lints related to similar errors, such as min_max
with x.max(100).min(0)
, and manual_clamp
which mentions a rather confusing input.max(min).min(max)
. These indicate that there is precedent for accidentally swapping min and max.
Solution sketch
I propose adding a function .limit()
to Ord (and to float types), which serves the role of both .min()
, .max()
, and .clamp()
. In brief:
x.limit(5..) == x.max(5)
x.limit(..=10) == x.min(10)
x.limit(5..=10) == x.clamp(5, 10)
x.limit(..) == x // because why not?
and yes I did confuse min and max while writing the above. Case in point.
With this, the above examples would be written as arr[..x.limit(arr.len()..)]
, x.limit(100..).limit(..=0)
, and input.limit(min..).limit(..=max)
, respectively. I think all three make the intention (and the mistakes) significantly clearer.
I also imagine a clippy lint that suggests replacing .min()
and .max()
, and maybe also .clamp()
, with their corresponding .limit()
forms.
Implementation sketch
pub trait Ord {
...
fn limit<B: LimitBounds<Self>>(self, bounds: B) -> Self {
bounds.limit_bounds(self)
}
}
pub trait LimitBounds<T: Ord>: Sized + Sealed {
fn limit_bounds(self, value: T) -> T;
}
impl<T: Ord> LimitBounds<T> for RangeFrom<T> {
fn limit_bounds(self, value: T) -> T {
value.max(self.start)
}
}
impl<T: Ord> LimitBounds<T> for RangeToInclusive<T> {
fn limit_bounds(self, value: T) -> T {
value.min(self.end)
}
}
impl<T: Ord> LimitBounds<T> for RangeInclusive<T> {
fn limit_bounds(self, value: T) -> T {
let (start, end) = self.into_inner();
value.clamp(start, end)
}
}
impl<T: Ord> LimitBounds<T> for RangeFull {
fn limit_bounds(self, value: T) -> T {
value
}
}
impl f32 {
...
fn limit<B: LimitBounds<f32>>(self, bounds: B) -> f32 {
bounds.limit_bounds(self)
}
}
impl LimitBounds<f32> for RangeFrom<f32> {
fn limit_bounds(self, value: f32) -> f32 {
value.max(self.start)
}
}
// and so on for each range and float type
Alternatives
This could totally be a separate crate, and I would have made one if I could come up with a name. But I think it's a common enough annoyance that it would fit in std.
The operations can be written less error-pronely with function form (usize::min(x, arr.len())
) or with clamp (x.clamp(0, arr.len())
). In my opinion these are both unsatisfactory: both are rather verbose, functoin form doesn't fit into method chains, and clamp requires the type to be bounded.
Unfortunately the name clamp
is already taken, and the function cannot be retrofitted due to different number of arguments. Otherwise, that name would be much better for this operation.
Links and related work
The clamp RFC considered making clamp
take a range, but as I understand it, that was rejected because inclusive ranges were rather immature at the time. This is no longer the case.
What happens now?
This issue contains an API change proposal (or ACP) and is part of the libs-api team feature lifecycle. Once this issue is filed, the libs-api team will review open proposals as capability becomes available. Current response times do not have a clear estimate, but may be up to several months.
Possible responses
The libs team may respond in various different ways. First, the team will consider the problem (this doesn't require any concrete solution or alternatives to have been proposed):
- We think this problem seems worth solving, and the standard library might be the right place to solve it.
- We think that this probably doesn't belong in the standard library.
Second, if there's a concrete solution:
- We think this specific solution looks roughly right, approved, you or someone else should implement this. (Further review will still happen on the subsequent implementation PR.)
- We're not sure this is the right solution, and the alternatives or other materials don't give us enough information to be sure about that. Here are some questions we have that aren't answered, or rough ideas about alternatives we'd want to see discussed.