From: "mame (Yusuke Endoh) via ruby-core" Date: 2026-07-04T15:08:24+00:00 Subject: [ruby-core:125929] [Ruby Feature#22175] Add `Range#clamp` Issue #22175 has been updated by mame (Yusuke Endoh). For the record, here is the use case I had in mind when talking with @nobu: When traversing the 3x3 neighborhood of a cell (x, y) on a 2D grid of width x height, one is tempted to write code like this: ```ruby (y - 1).upto(y + 1) do |ny| (x - 1).upto(x + 1) do |nx| grid[ny][nx] end end ``` However, this may access cells outside the grid, so in practice we have to write something like the following, which I don't find very readable: ```ruby [y - 1, 0].max.upto([y + 1, height - 1].min) do |ny| [x - 1, 0].max.upto([x + 1, width - 1].min) do |nx| grid[ny][nx] end end ``` With `Range#clamp`, it can be written as: ```ruby (y - 1 .. y + 1).clamp(0...height).each do |ny| (x - 1 .. x + 1).clamp(0...width).each do |nx| grid[ny][nx] end end ``` That said, I'm a bit concerned about creating a Range object on every iteration, so I'm not sure I would actually use this in a hot loop. So I'm not super enthusiastic about this proposal, but I can imagine cases where it would be handy, so I'm not against it either. ---------------------------------------- Feature #22175: Add `Range#clamp` https://bugs.ruby-lang.org/issues/22175#change-117897 * 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/