Optional slice declaration

Hi. In the code below, I don’t fully understand the last line
var args_ptrs: [max_args:null]?[*:0]u8 = undefined;
I see the question mark and understand that this is a variable declaration of a ptr to sentinel array of bytes which could be undefined (i.e. optional). My confusion is what is the part preceding the question mark [max_args:null]? ?

Thanks.

fn shellLoop(stdin: std.fs.File.Reader, stdout: std.fs.File.Writer) !void {
    while (true) {
        const max_input = 1024;
        const max_args = 10;

        // Prompt
        try stdout.print("> ", .{});

        // Read STDIN into buffer
        var input_buffer: [max_input]u8 = undefined;
        var input_str = (try stdin.readUntilDelimiterOrEof(input_buffer[0..], '\n')) orelse {
            // No input, probably CTRL-d (EOF). Print a newline and exit!
            try stdout.print("\n", .{});
            return;
        };

        // Don't do anything for zero-length input (user just hit Enter).
        if (input_str.len == 0) continue;

        // The command and arguments are null-terminated strings. These arrays are
        // storage for the strings and pointers to those strings.
        var args_ptrs: [max_args:null]?[*:0]u8 = undefined;
        ... 

As I read it, args_ptr is an array of ?[*:0]u8 pointers, ie., optional string pointers. So it’s like in C you could have an array like this:

const char* const* args_ptr = {
    "some arg",
    "another arg",
    NULL
};

As the args_ptr slice is null-sentineled, the string pointers need to be optional so that they can encode the null terminator too.

[l:t]T is an array of T, of lenght l, terminated by t. So in this case, it’s an array of optional many-pointers terminated by 0 (the many-pointers) terminated by null (the array), of lenght max_args.

Also, don’t confuse undefined with null, they’re not the same thing! undefined means “I’m not defining this memory yet, I’ll do it later”. Accessing it is undefined behaviour, checked only when safety is on. null is a perfectly legal value that must be checked by the programmer, otherwise it won’t compile.

2 Likes

In C idiomatic definition would be

const char** args_ptr = {
    "some arg",
    "another arg",
    NULL
};

I don’t think reading undefined value is actually undefined behaviour. It isn’t mentioned as such in the documentation, just that the memory is undefined and might not even make sense in the context of the type (like with enums?). It also doesn’t trigger panic, not even in ReleaseSafe mode, so it isn’t checked.

You’re right, it’s not checked, unlike I thought. Only detectable in Debug build, but this not even the specification. But I mean if you do this:

const condition: bool = undefined;
if (condition) {
    doSomething();
} else {
    doSomethingElse();
}

It can’t be “defined behaviour”, so what should we call it?

I guess it’s not so much undefined behavior from the compiler’s POV but of course reading uninitialized memory and branching based on it is… uhh, unpredictable.

Hmm, you were right. This is undefined behaviour. Same for

const a: i32 = undefined;
const b: i32 = 2 * a;

and program in Debug and ReleaseSafe panics accordingly. Weird that the docs don’t mention this.

Since the array type allows optionals, there is no need to use undefined at all. Just initialize it to all null.

var arg_ptrs = [_:null]?[*:0]u8{null} ** max_args;
2 Likes

Presumably, the code initialized it afterwards. Zeroing it adds unnecessary overhead.

1 Like

Thank you for all your responses. But I think my question may have been misinterpreted (bad wording on my part). My confusion is in the syntax.
I understand this syntax (see below) …

// Optional null sentinel array of size 2
var x = a: ?[2:0]u8 = .{'h','i'}

But I don’t understand this syntax (what is n that precedes ?)

// Optional null sentinel array of size 2
// What the heck is 
var x = a: n?[2:0]u8 = .{'h','i'}

Thank you!

hello @kyp0717
“I don’t understand why you write ‘!void’ when you don’t have any error return or try.”

Oh. This is just a partial display of the code (notice the ... ) .

1 Like

It’s a null terminated array of max_args elements containing optional pointers to any amount of u8s terminated by 0.

2 Likes

It is documented in Documentation - The Zig Programming Language.

Blockquote
In Debug mode, Zig writes 0xaa bytes to undefined memory. This is to catch bugs early, and to help detect use of undefined memory in a debugger. However, this behavior is only an implementation feature, not a language semantic, so it is not guaranteed to be observable to code.

However langref does not document that this also happen with ReleaseSafe.

The discussion that followed was about accessing undefined values. I would take slight inefficiencies over Undefined Behavior any day of the week.

That doesn’t say that it will panic when the undefined memory is used, only that it will write 0xaa there. That’s what surprised me.

0xaa is only written in Debug and Safe modes. In Release mode undefined remains undefined and accessing it can result in UB.

Sorry, I’m still don’t understand. Please note that I am new to Zig and C programming.
So let me ask the question another way.
Given the statement, what is the purpose of the [2] preceding the ? ?

var x: [2]?*[2]u8 = undefined;

It’s a size of the array of ?*[2]u8.
See my answer above

from your original

1 Like