Feature #22175
openAdd `Range#clamp`
Description
I would like to propose Range#clamp, which returns a new Range whose begin and end values are clamped to the given bounds.
Proposed call-seq:
This is a range counterpart of Comparable#clamp. While Comparable#clamp clamps a single value, Range#clamp clamps both endpoints of a range.
Examples:
(1..10).clamp(3, 7) #=> 3..7
(1...10).clamp(3, 7) #=> 3..7
(1...10).clamp(3, 10) #=> 3...10
(0...).clamp(0, 10) #=> 0..10
(1..10).clamp(3..7) #=> 3..7
(1..10).clamp(3...7) #=> 3...7
(1..5).clamp(3...7) #=> 3..5
clamp(min, max) behaves like clamping by an inclusive range min..max. If an exclusive upper bound is needed, a range argument can be used:
Beginless and endless ranges are also supported:
(..10).clamp(3, 7) #=> 3..7
(0...).clamp(0, 10) #=> 0..10
(1..10).clamp(..7) #=> 1..7
(1..10).clamp(...7) #=> 1...7
(1..10).clamp(3..) #=> 3..10
If the receiver is entirely outside the clamping bounds, the returned range is empty:
The returned range excludes its end when the returned end value is an excluded end value of either the receiver or the argument range:
Otherwise, the returned range includes its end.
Relation to [Feature #16757]¶
[Feature #16757] proposes Range#intersection / Range#& as a general operation for intersecting two ranges.
Range#clamp is closely related, but intentionally narrower. It treats the argument as clamping bounds for the receiver, similar to how Comparable#clamp treats its arguments as bounds for one value.
For overlapping ranges, range.clamp(bounds) often produces the same result as a range intersection. For example:
However, clamp has a bounds-oriented API and naturally supports the two-argument form:
Also, when the receiver is outside the bounds, clamp returns an empty Range at the nearest bound, rather than needing to decide whether a general intersection operation should return nil, [], an empty range, or raise:
So this proposal can be considered either independently, as a range counterpart of Comparable#clamp, or as a smaller operation that could coexist with a future Range#intersection.
Motivation¶
It is common to restrict ranges to known boundaries, for example when limiting source locations, pagination windows, numeric domains, date/time windows, or user-provided ranges.
Currently this has to be written manually by clamping both endpoints and reconstructing the range while preserving the correct excluded-end behavior. That logic is easy to get subtly wrong, especially with exclusive ranges, beginless/endless ranges, and ranges that become empty after clamping.
Range#clamp would provide a small, direct API for this operation.
Notes¶
The method returns a new Range instance.
The single-argument form accepts a range-like object accepted by Ruby’s range conversion logic.
Source checked: [Feature #16757]: Add intersection to Range.