Undefined Optionals

Hello, I have two questions about how optional values and undefined interact:

  1. Will @as(?u32, @as(u32, undefined)) != null always be true?
  2. Will @as(?u32, undefined) != null always be true?

I am pretty sure that Q.2 is not guaranteed, but how about Q.1?

The result of anything you interpret undefined as is undefined(not defined) and the equality of that value to any other value is also undefined(not defined).
1&2. It may always be true, always be false, and(sic) sometimes be true. The ?u32 you receive has no meaningful value and it is undefined to branch on.

3 Likes
  1. Yes. Only the u32 part is undefined. The ? part is defined not null.
test {
    try std.testing.expect(@as(?u32, @as(u32, undefined)) != null);
}
test {
    // will fail
    try std.testing.expect(@as(?u32, @as(u32, undefined)) == null);
}
  1. No. It is not == null either. It is Illegal to read undefined.
test {
    // will fail
    try std.testing.expect(@as(?u32, undefined) != null);
}
test {
    // will fail
    try std.testing.expect(@as(?u32, undefined) == null);
}

But if you make sure that optional is runtime-known you skip compile-time safety check:

test {
    var optional: ?u32 = undefined;
    try std.testing.expect(optional != null); // unchecked Illegal Behavior
    optional = 0; // suppress compiler error
}
2 Likes

Undefined is not the same as null. There are better explanations, but the way I understand it is that when the variable is set as undefined, the caller must guarantee that it had been set prior to using it.

2 Likes

You can think of ?u32 as Optional = struct { is_null: bool, val: u32 }

  1. is equivalent to Optional{ .is_null = false, .val = undefined }
  2. is equivalent to @as(Optional, undefined)
4 Likes

My understanding is that when you set a variable as undefined in Zig, you are basically saying “I’m creating a variable, but it has no meaningful value yet”, and you must give it a meaningful value before using it.

However, I’ve wondered how, if at all, you can represent JavaScript’s version of undefined in Zig, where undefined means “this value has not been explicitly set.”

This usually gets used for:

  1. unset variadic arguments in function calls.
  2. key accesses on objects, where the key isn’t present.
  3. As a subset of 2, when loading for example, a config file, any key you read as undefined can be presumed to not be set, and therefore you can use a default value instead, whereas if you see a null, you can assume the user wants it to be empty.

1 and 2 aren’t applicable to Zig, but I feel like 3 could be, although it’s a definite edge case. It is sometimes helpful to know whether foo.bar is null because the user set it to null, or whether it’s null because it was never configured.

There are frictionous ways to do this in Zig and perhaps that’s how it should be.

Anyways, sorry for hijacking the conversation :sweat_smile:

For that you could also use the elusive double optional:

const std = @import("std");

pub fn main() !void {
    var foo: ??u64 = 1;
    std.debug.print("{}\t{x}\n", .{ foo.?.?, std.mem.asBytes(&foo) });
    foo = null;
    std.debug.print("{??}\t{x}\n", .{ foo, std.mem.asBytes(&foo) });
    foo = 1;
    foo.? = null;
    std.debug.print("{?}\t{x}\n", .{ foo.?, std.mem.asBytes(&foo) });
}

But for real an optional tagged union might be better.

Wow, love the hack xD

I would never have considered that. But yeah, an optional tagged union or simply parsing manually probably makes better sense in most cases.

1 Like

It’s not much of a hack, it is pretty reasonable to have an optional value within a map, getting from that map would produce a nested optional.

1 Like

Yeah, that is a perfectly legitimate use of a double optional. But surely the question marks have to end sometime…

const std = @import("std");

pub fn main() !void {
    var cmon: ?????u64 = 1;
    std.debug.print("{}\t{x}\n", .{ foo.?.?.?.?.?, std.mem.asBytes(&foo) });

    std.debug.print("Just cause you can, doesn't mean you should XD\n");
}