Hello, and thank you for this awesome language.
I’m new to Zig, I just started learning the language from the official documents, thus I come across the undefined
value in Zig. I have a Javascript background, and in fact I still use it pretty much everyday, in Javascript there was a problem with undefined
as a nullable value, to have both null
and undefined
in the language will result in ambiguity in the language. I’m new to Zig, and I believe there’s an explination for that decission.
1 Like
Welcome @mbougarne !
This is a common question. null
and undefined
represent two different concepts. null
is a well defined value that means “The Null case of an optional value”. Optionals in zig (syntax: ?T
) just mean the value is either Type T or Null. When you work with an optional you have to deal with the null
path (or assert that it isn’t null with .?
syntax)
undefined
is a way to create an uninitialized variable. It is not a well defined value. From the docs it says:
Translated into English, undefined
means “Not a meaningful value. Using this value would be a bug. The value will be unused, or overwritten before being used.”
When you set something to undefined you are making the commitment that you will either eventually set it to a meaningful value, or never use the variable again.
11 Likes
I welcome to Ziggit!. Both are protection systems. Unlike JavaScript, both null
and undefined
are treated as illegal behaviors in debug builds. null
expresses that there are no values, while undefined
is used by the memory sanitizer or compiler to warn or trap when reading or using memory that hasn’t been initialized. In this context, undefined
serves as a signal indicating that a variable was deliberately left without a meaningful initial value. In languages like C/C++ which allow leaving a variable uninitialized the easy path is simply to declare a variable without carefully considering its initial value. In Zig, however, you must explicitly use undefined
if you want a variable to remain uninitialized.
There are cases when setting a variable to undefined
is justified because there isn’t any meaningful value to assign; however, in most C/C++ scenarios, this is merely the result of laziness. Zig enforces explicit behavior by requiring that you mark a variable as undefined
if you intend to leave it uninitialized. This approach is beneficial because if you later try to use the uninitialized variable, the compiler will alert you or, at runtime, your program will crash due to the improper read.
Meanwhile, null
operates similarly to how optionals are treated: using a value that might not be present is acceptable as part of normal logic. In Zig, it’s acceptable to use optionals, but using undefined
is never acceptable unless it’s explicitly intended.
6 Likes
Thank you @Calder-Ty and @pierrelgol The C/C++ mention helped me to see the safety approach that Zig took over the C/C++ approach that in many cases leads to undefined behavior.
int x;
x+=2; // This is not safe and and it doesn't throw
// an error in the most cases
// We cannot do it in Zig
//we need to init x before use it
Thank you
1 Like
I don’t think this is quite right:
var x: i32 = undefined;
x += 2;
std.debug.print("{d}", .{x});
is possible and not safety checked. The only difference is that you have to use the undefined keyword.
3 Likes
You are welcome, even if Zig did’t emit errors.
const std = @import("std");
pub fn main() !void {
const a: i32 = undefined;
const b = a + 2;
std.debug.print("{d}", .{b});
}
Which it does most of the time.
❯ zig run main.zig
main.zig:5:15: error: use of undefined value here causes undefined behavior
const b = a + 2;
^
the visual cue is enough that it’s easy to narrow it down.
4 Likes
Most C compilers with any reasonable level of warnings enabled will catch this case too:
#include <stdio.h>
int main(void) {
int a;
a += 2;
printf("a: %d\n", a);
}
> gcc -Wall -Werror uninit.c -o uninit
uninit.c: In function ‘main’:
uninit.c:5:7: error: ‘a’ is used uninitialized [-Werror=uninitialized]
5 | a += 2;
| ~~^~~~
uninit.c:4:9: note: ‘a’ was declared here
4 | int a;
| ^
cc1: all warnings being treated as errors
5 Likes
I play with it a little bit (the undefined value), and it’s pretty much like C/C++ the only difference that I can see is that Zig makes undefined
explicit not implicit as in C, and with -O ReleaseFast
it gives x the value 0 in my machine.
I may be wrong again, but it’s up to the developer how they write their Zig code and how they build it.
I think it’s worth mentioning that you can’t check if a variable is undefined in Zig. Accessing the value in any way (including comparison) is illegal. In JS, it’s very common as far as I remember.
2 Likes
This is a coincidence and may change run-to-run.
5 Likes
It is illegal behavior and is caught by Valgrind:
andy@bark ~/tmp> cat test.zig
const std = @import("std");
pub fn main() void {
var x: i32 = undefined;
x += 2;
std.debug.print("{d}", .{x});
}
andy@bark ~/tmp> zig build-exe test.zig -target x86_64-linux
andy@bark ~/tmp> valgrind ./test
==1375503== Memcheck, a memory error detector
==1375503== Copyright (C) 2002-2024, and GNU GPL'd, by Julian Seward et al.
==1375503== Using Valgrind-3.23.0 and LibVEX; rerun with -h for copyright info
==1375503== Command: ./test
==1375503==
==1375503== Conditional jump or move depends on uninitialised value(s)
==1375503== at 0x1039022: test.main (test.zig:4)
==1375503== by 0x10389E6: callMain (start.zig:647)
==1375503== by 0x10389E6: callMainWithArgs (start.zig:616)
==1375503== by 0x10389E6: start.posixCallMainAndExit (start.zig:571)
==1375503== by 0x103861D: (below main) (start.zig:271)
However Zig’s safety check currently misses it because 0xaaaaaaaa + 2 does not overflow. Often times safety checks will catch it however if you multiply or if you use it to index a slice.
Related: runtime safety for branching on undefined values and other undefined behavior caused by undefined values · Issue #63 · ziglang/zig · GitHub
8 Likes