so I realized some unfortunate problems with our current 1.to(5) syntax:
1..5 is commonly "1, 2, 3, 4" - but how do you express that in a method name? I think if 1.to(5) excluded 5, that would surprise a lot of people unpleasantly.1.up_to(5) is concise but still not very clear. 1.below(5) or 1.under(5) kinda works but doesn't read well with variables like x.below(y) - reading that, it doesn't look like it's going to return a range.1.to(5), 1 is an unbound number type, so we don't know what concrete type to dispatch on. That in turn means we can't even know whether to is returning an Iter, which in turn means we have nothing to unify against based on usageidea for a way to fix for all of these: just actually do numeric range syntax, and have it return a Range. so inclusive range looks like Rust's:
1..=5
...and so does inclusive:
1..5
the exclusive one would return a value of type:
Iter(num) where [
num.from_literal : ...,
num.plus : ...,
num.lt : ...
]
...which in turn would naturally unify the way we want, and then everything would work from there
and then of course we have an obvious range syntax for pattern matching
Regarding naming, Scala has to and until methods which create inclusive and exclusive Range objects respectively:
scala> 1.to(5)
val res1: collection.immutable.Range.Inclusive = Range(1, 2, 3, 4, 5)
scala> 1.until(5)
val res2: Range = Range(1, 2, 3, 4)
For me, until is obviously exclusive, but to is a bit less obviously inclusive
yeah I looked at that...until doesn't seem clear to me :sweat_smile:
lol. I wonder if Dijkstra wrote something about this
https://www.cs.utexas.edu/~EWD/transcriptions/EWD08xx/EWD831.html
Richard Feldman said:
...and so does inclusive:
1..5
Just to confirm, this was supposed to say "exclusive", right?
yes, oops
fixed!
I'd assume 1..5 and 1..=5 is as cryptic as "to" and "until". Both require prior knowledge :sweat_smile:
Given all of us should use modern ides with autocomplete (and given that would likely also be easier to pick up for ai) what about simply to_including or to_excluding? Or is that too long?
Tobias Steckenborn said:
I'd assume
1..5and1..=5is as cryptic as "to" and "until". Both require prior knowledge :sweat_smile:
I was going to say that these are pretty standardized because so many programming languages have them, but I just researched it a bit and it turns out the syntax is standard but inconsistent across languages as to whether .. means inclusive or exclusive ![]()
I think ..= is clearly inclusive, and interestingly Swift uses ..< for exclusive
I don't really love how either of those look, but if you know both of them, I do like how self-descriptive they are
fwiw, they look cryptic to me (not against this or anything, just not familiar with this syntax)
I would have assumed 1..5 == 1,2,3,4,5
Is there a specific reason to have both inclusive and exclusive?
Our last range discussion I can think about was a long time ago and we also talked about steps and direction too
having a sort of configuration object with defaults is not in line with what's done everywhere else, right?
So e.g.:
1.to(5)
being implicit for something like
1.to(5, {mode: "exclusive", stepSize: 1 [...]})
to be clear, we have a types problem with 1.to(5) which means we need to move away from it.
For example, @Aurélien Geron immediately ran into it in practice in exercism examples - it was a cool idea but it just gives type mismatches instead of doing what you want in too many real-world programs.
so the decision has to be about what syntax to use instead of the plain-method one that looks cool but doesn't work well in practice :smile:
Luke Boswell said:
Is there a specific reason to have both inclusive and exclusive?
Exclusive range is the most important one for languages with 0-based indexing because the most common pattern is:
a = ["foo", "bar"]
for i in 0..a.len() {
# TODO
}
I believe inclusive range is less useful but convenient when the last value is meaningful
for month in 0..=12 {
# TODO
}
Our last range discussion I can think about was a long time ago and we also talked about steps and direction too
rust does
for i in (0..10).rev().step_by(2) {
# TODO
}
Python does it with <start>:<end>:<step> "slice" syntax (e.g. 0:10:-2) which is quite common for indexing ndarray:
a = np.zeros((10, 10))
a[0, :] == a[0, 0:10:1] == a[0, 0:10] == a[0, 0:] == a[0, :10] # first row, all columns
a[0, ::2] == a[0, 0:10:2] # first row, even columns
a[0, ::-1] # first row flipped
python also provides a alternative slice(start, end, stop) function for constructing slices.
python provides a range(start, end, stop) standalone function which is the iterator version.
Note that from lexical perspective, using .. (and ...) as a token for integer ranges is the only place in zig grammar that requires more than single character lookahead. Consider having lexed 0 and seeing a .. Is this a float literal or start of an integer range? (0.0 vs 0..3). The zig lexer currently guesses that it is a float literal and if it fails, it backtracks. It is the only place that requires this special treatment. (https://ziggit.dev/t/300-mib-s-zig-lexer-fixing-an-edge-case-in-the-grammar/9514)
Therefore if the decision is between 0..5 and 0:5 I would be for the colon since i find them equivalent in readability :D
Richard Feldman said:
I think
..=is clearly inclusive, and interestingly Swift uses..<for exclusiveI don't really love how either of those look, but if you know both of them, I do like how self-descriptive they are
I like these.
we can't use a : b because that's already a type annotation :smile:
Nick Gravgaard said:
https://www.cs.utexas.edu/~EWD/transcriptions/EWD08xx/EWD831.html
Nice article! I agree. Inclusive ranges aren't as useful, so maybe it's not worth addig a syntax for them.
also agree with the fact that semi-exclusive ranges are much more useful in maths and matrix/vectors manipulation. Inclusive lower bound, paired with exclusive higher bound. Also very convenient for everything related to versioning and dependency solving. Though for the latter, having both inclusive and exclusive bounds is very useful. See for example the version_ranges package from the pubgrub dependency solver: https://github.com/pubgrub-rs/pubgrub/blob/dev/version-ranges/src/lib.rs
yeah I don't think "only do exclusive" is the answer
Norbert Hajagos said:
Inclusive ranges aren't as useful, so maybe it's not worth addig a syntax for them.
They should still be representable. It would be annoying to not be able to represent a loop/range over every valid value of an integer type. Would you have to write 0..maxint + 1? Would that overflow?
Richard Feldman said:
yeah I don't think "only do exclusive" is the answer
This decided it, but I'll answer with saying that you can still construct a range manually and for such an extreme case, it wouldn't be that strange to reach for a slightly less convenient approach.
But the article doesn't take pattern matching into account, which is where I can see this being useful. I still think guard clauses would suffice, but I can see the appeal of something like:
match char {
'A'..'Z' => ...
}
Prokop Randacek said:
Note that from lexical perspective, using
..(and...) as a token for integer ranges is the only place in zig grammar that requires more than single character lookahead. Consider having lexed0and seeing a.. Is this a float literal or start of an integer range? (0.0vs0..3). The zig lexer currently guesses that it is a float literal and if it fails, it backtracks. It is the only place that requires this special treatment. (https://ziggit.dev/t/300-mib-s-zig-lexer-fixing-an-edge-case-in-the-grammar/9514)Therefore if the decision is between
0..5and0:5I would be for the colon since i find them equivalent in readability :D
We already have ... as a meaningful syntax, so that already applied to us.
But it's a really cool thing, well worth it.
If we’re looking for potential names, I would definitely consider through for the inclusive case (i.e. the broken syntax would be 1.through(5))
fyi this landed! (syntax desugaring to iter, no range pattern matching atm) #9611
That's great! Clear syntax, covers most use cases.
Just wondering about step size and infinite ranges.
What's the right expression for:
step is interesting - if we want to support that, we could make an actual Range type with an iter() method (so it continues to Just Work in for loops) instead of returning Iter directly
we could also support 0.. as "to infinity" too :thumbs_up:
i had the same thought when implementing and looked af what rust does, and they have a step_by func on iter (https://doc.rust-lang.org/std/iter/trait.Iterator.html#method.step_by)
if we do the same in roc, it would look like 1..=10.step_by(2)
sounds reasonable!
4..=1 would be nice for 4, 3, 2, 1.
However, 4..<0 would be really weird. 4..0 or 4..>0 would make more sense.
Last updated: Jun 16 2026 at 16:19 UTC