More intuitive alternative for min/clamp/max

I'm sure we've all noticed that x.max(4) doesn't behave quite intuitively: at first glance it looks like "x, but no more than 4" when it's in fact the opposite. Same for .min, of course. So what would people think about a function with a little more evocative syntax?

  • x.limit(y..) equal to x.max(y)
  • x.limit(..=y) equal to x.min(y)
  • x.limit(y..=z) equal to x.clamp(y, z)

Could also go with range.limit(value), but the point of this function is to make function chains more convenient and intuitive, so that wouldn't really work.

Obviously the name is :bicycle::hut:.

6 Likes

These methods are confusing. However, the range-based alternative design has been considered for clamp() and rejected:

1 Like

Duplicate of Public view of rust-lang | Zulip team chat ?

min and max look more intuitive using the qualified function form:

u32::max(x, 4)
3 Likes

Yes, that post was part of what motivated me to finally post about it. I've thought about this thing for a long time, though.

Yes, but chainable apis are nicer.

1 Like

Perhaps new x.at_least(4) and x.at_most(6) are added as alternative names for them then?

2 Likes

Reminder that you can always use clamp with an extremum for the other side.

For example, instead of x.limit(y..) you can x.clamp(y, u32::MAX) (the extra comparisons optimize away trivially).

Is there a way to spell that in generic code (i.e., not having to specify u32:: literally)?

Sure, x.clamp(y, Bounded::max_value()) via https://docs.rs/num-traits/latest/num_traits/bounds/trait.Bounded.html#tymethod.max_value.


EDIT: Actually, that might not be what you want for floats, since it'll give f32::MAX, not f32::INFINITY, and thus there's still a comparison involved.

The classic problem with "well, what does 'max' really mean, anyway?", since the trait method can return a value that's smaller than one of the other values...

I agree it'd be nice to have the two unary variants of clamp -- I've wanted this multiple times in just the last few months. Writing it out with clamp and Ty::min/max is verbose and typically requires repeating the type.

From what I could find, only x..=y was considered, not general ranges. It was discarded because inclusive range syntax was not stable yet at the time, not because it would be less ergonomic or so. So while a range-based design was considered, nothing like @Kyuuhachi's proposal was considered during the RFC.

It might be worth filing an ACP for limit? The range-based API looks really nice.

7 Likes

IIRC part of this is that supporting x..y would mean that it would also need some extra support like "get the largest value less than y" that we don't have traits for today. And if it takes impl RangeBounds then it would need the trait bound for next_up and next_down even if you pass x.. that wouldn't use them.

So I think it was mostly a "well we can do the simple thing and not have to add a new trait" decision.

(Maybe today a sealed trait for just the couple of types that are "easy" would be fine, though? Dunno.)

ACP posted. ACP: Intuitive alternative for `.min()` and `.max()` · Issue #665 · rust-lang/libs-team · GitHub

3 Likes

Personally I think if it ain’t broke, don’t fix it

As someone who ran into this issue recently (such a silly mistake) I'd be glad to write out the coercion as a range type instead of having to remember the 2-argument function T::min(a, b) behavior applies when using it as a method a.min(b). It's such an easy thing to internalize, yet i find it's also easy to just get it wrong without thinking deeply enough.

(Imagining it as a #[cfg(random 30%)] use T::{min as max, max as min}; though this doesn't fully work, it illustrates the struggle)

1 Like

Not sure if this has already been mentioned, but for those unaware: std::cmp::min and std::cmp::max exist. They're generic so you don't need to write it as u32::max(a, b) or whatever.

3 Likes