From: "matz (Yukihiro Matsumoto) via ruby-core" Date: 2026-07-10T00:40:47+00:00 Subject: [ruby-core:126018] [Ruby Feature#22175] Add `Range#clamp` Issue #22175 has been updated by matz (Yukihiro Matsumoto). Accepted. `Range#clamp` is a natural range counterpart of `Comparable#clamp`, and the use case (restricting a range to known boundaries) is common enough. Regarding `Range#intersection` (#16757): I see them as different operations, even though they often produce the same result. `clamp` is asymmetric by design; the argument is bounds, not a peer range, which is why the two-argument form `clamp(min, max)` makes sense and why returning an empty range at the nearest bound is a natural answer for disjoint cases. A symmetric `intersection` would have to decide between `nil` and an empty range for disjoint inputs. Also, once we have `intersection`, people would naturally expect `union` as well, but `Range#union` cannot be defined in general because the union of two ranges may be discontiguous. These points can be discussed separately in #16757; accepting `clamp` does not preclude `intersection`. Matz. ---------------------------------------- Feature #22175: Add `Range#clamp` https://bugs.ruby-lang.org/issues/22175#change-117997 * Author: nobu (Nobuyoshi Nakada) * Status: Open ---------------------------------------- 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: ```ruby range.clamp(min, max) -> range range.clamp(bounds) -> range ``` This is a range counterpart of `Comparable#clamp`. While `Comparable#clamp` clamps a single value, `Range#clamp` clamps both endpoints of a range. Examples: ```ruby (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: ```ruby (1..10).clamp(3...7) #=> 3...7 ``` Beginless and endless ranges are also supported: ```ruby (..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: ```ruby (1..10).clamp(20..30) #=> 20...20 (1..10).clamp(-10..0) #=> 0...0 (1..).clamp(..0) #=> 0...0 ``` The returned range excludes its end when the returned end value is an excluded end value of either the receiver or the argument range: ```ruby (1...10).clamp(3, 10) #=> 3...10 (1..10).clamp(3...10) #=> 3...10 ``` 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: ```ruby (1..10).clamp(3..7) #=> 3..7 ``` However, `clamp` has a bounds-oriented API and naturally supports the two-argument form: ```ruby (1..10).clamp(3, 7) #=> 3..7 ``` 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: ```ruby (1..10).clamp(20..30) #=> 20...20 ``` 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](https://bugs.ruby-lang.org/issues/16757). #### Implementation [GH-17652](https://github.com/ruby/ruby/pull/17652) -- https://bugs.ruby-lang.org/ ______________________________________________ ruby-core mailing list -- ruby-core@ml.ruby-lang.org To unsubscribe send an email to ruby-core-leave@ml.ruby-lang.org ruby-core info -- https://ml.ruby-lang.org/mailman3/lists/ruby-core.ml.ruby-lang.org/