Slice range notation format

Preferred slice range notation format

  • Allow specifying a start and an end index.

  • For each start/end index: choose inclusive or exclusive.

    Only shrink-only range logic is allowed:

    • No prefix → inclusive

    • Prefix !exclusive

  • Omitted start or end defaults to the original array’s actual start or end.

Examples

  • [1..4] → slice from index 1 to index 4 (both inclusive).

  • [!1 .. !4] → slice from index 2 to index 3 (exclude 1, exclude 4).

  • [..] → entire array, from index 0 to the last index.

  • [! .. !] → slice from index 1 to max_index - 1 (exclude original first and original last element).

  • All other combinations can be freely composed.

Rationale

  • The rule is explicit and requires no arbitrary memorization.

  • It follows natural human reading intuition.

Unwanted slice range notation format

  • Dislike Python-style [1..3] design: left-inclusive but right-exclusive, which is counter-intuitive for human reasoning.

As for the choice of the prefix symbol, I think it can still be further discussed and refined.

I think using < for exclusive and = for inclusive is a lot more intuitive.

  • inclusive-inclusive: sliceable[start=..=end],
  • inclusive-exclusive: sliceable[start=..<end],
  • exclusive-inclusive: sliceable[start<..=end],
  • exclusive-exclusive: sliceable[start<..<end].

That being said, for slicing, inclusive-exclusive is vastly more useful than most, and I find it quite intuitive. I don’t even remember encountering a case were I needed an exclusive start.

7 Likes

Out of the 4 possibilities, I’d argue it’s the most intuitive to work with.
e.g. it has the property that end - start gives you the length, which is something that people mess up in real life when using other systems (e.g. to calculate the number of days given inclusive start and end dates, many people resort to counting them on their calendar)
The only other system that has this property is left exclusive right inclusive, but that really makes no sense, since it would require -1 to slice things at 0.

It would add a new symbol to memorize though, and it’s also easy to miss when reading, and easy to forget when writing.

5 Likes

There is already a standard notation for inclusive/exclusive ranges. The downside is that conventional coding editors assume balanced brackets and the standard notation mixes them. We just need better editors.

Then you had to write for (0..!foo.len) |n| .. which I don’t find very intuitive.

if need to traverse all, just using this is OK;

The current state is to just do something like that;

const my_slice: []u8 = foo();
for (my_slice) |element| {
    // use element
}

Why would [..] be needed?

In general I’m unsure about what and if anything you’re trying to achieve with this. The [inclusive..exclusive) range syntax and specification is common and widely used and understood in nearly all programming languages for reasons already explained above. The only major exception I can think about off the top of my head is Julia, which has inclusive bounds for both lower and upper.

It would be nice to have ..= in Zig, but of course one can do without it.

2 Likes

It is the other way around. The [inclusive, exclusive) range syntax is what mathematicians use.

6 Likes

And this would be fine syntax if not for dumb editors.

… people who forget which one is inclusive and which one is exclusive. It is an obscure math notation that is not that common in programing languages.

foot(gun[42..69);

Can you fix the typo here? Did I miss ] or )?

2 Likes

for-loop ranges can be inconvenient.
I just had one again.
Imagine two u6 vars a and b with which you want to compute something inclusive.
In the loops there is a useless overflow danger, while we do not need the + 1 value.

fn calc(a: u6, b: u6) void {
     if (a > b) {
        for (b..a + 1) |v| {
        }
     }
    else if (a < b) {
        for (a..b + 1) |v| {
        }
    }
}
1 Like

If you use something like this:

fn calc(a: u6, b: u6) void {
    const l:u7, const u:u7 = if (a < b) .{a, b} else .{b, a};
    for (l..u + 1) |v| {
    }
}

There is no overflow danger because currently in zig v is a usize.
But I would also like if we could have typed smaller indices like this:

1 Like

yes… easily solvable when we typecast. easily solvable anyway but sometimes inconvenient.
Typed looping vars would be great indeed.

1 Like