Bounds checking not working as expected?

I’m completely new to Zig!

The fact that the compiler catches this off-by-one error at compile time is cool:

pub fn main() void {
    var my_array = std.mem.zeroes([1024]u8);
    std.debug.print("last element: {}\n", .{ my_array[my_array.len] });
}

But I’m confused why an off-by-one error on a slice compiles fine (and then causes runtime panic):

pub fn main() void {
    var my_array = std.mem.zeroes([1024]u8);
    var my_slice: []u8 = my_array[0..10];

    std.debug.print("last element: {}\n", .{my_slice[my_slice.len]});
}

The length of the slice is known, so I would have expected it to be checked at compile time.

If it can only be checked at runtime, is there a way to catch the runtime error rather than panic and process exit?

I’d like to understand what’s happening, any help appreciated!

Hey @combat-otter - your user name is great.

Let’s run a simple test to demonstrate what’s wrong:

pub fn main() void {
    var my_array = std.mem.zeroes([1024]u8);
    my_array.len = 10;
}

This will give the following error:

main.zig:163:13: error: cannot assign to constant
    my_array.len = 10;

Meanwhile, if you do it with a slice:

pub fn main() void {
    var my_array = std.mem.zeroes([1024]u8);
    var my_slice: []u8 = my_array[0..100];
    my_slice.len = 10;
}

That runs fine. So what’s up?

Basically, slices have non-const sizes. They can be altered to give a different boundary. The array length however is const and can be reasoned about at compile time.

Thus, using the my_array.len means you are using a const value against something that has a constant size. With that setup, you can do compile time checks.

2 Likes

There are ways to do this by checking the index before you pass it to the array subscripts, but they are external checks. Such as if (i >= N) ... kind of checks.

You could also write member functions that check sizes in that manner and return an error if that’s the case. Here’s the issue - you’ll have to use try statements or unpack errors constantly.

Basically, by making it a panic, you can test for out of bounds reads without changing anything about the existing code. Since Zig doesn’t have “throw/catch” semantics, you’d have to express that using “errors as values” which means that accessing your array could also return an error type. That’s going to change the structure of the existing code.

There’s quite a few resources here about error handling so I’d recommend reading into some of that if you haven’t yet.

1 Like

This is all really helpful and makes sense, thank you.

1 Like