Optionals
The question mark symbolizes the optional type. You can convert a type to an optional type by putting a question mark in front of it, like this:
Now the variable could be an i32
, or null
.
Instead of integers, let’s talk about pointers. Null references are the source of many runtime exceptions, and even stand accused of being .
Zig does not have them.
Instead, you can use an optional pointer. This secretly compiles down to a normal pointer, since we know we can use 0 as the null value for the optional type. But the compiler can check your work and make sure you don’t assign null to something that can’t be null.
Task: call malloc, if the result is null, return null.
C code
// malloc prototype included for reference
void *malloc(size_t size);
struct Foo *do_a_thing(void) {
char *ptr = malloc(1234);
if (!ptr) return NULL;
// ...
}
Zig code
// malloc prototype included for reference
extern fn malloc(size: size_t) ?*u8;
fn doAThing() ?*Foo {
const ptr = malloc(1234) orelse return null;
// ...
}
Here, Zig is at least as convenient, if not more, than C. And, the type of “ptr” is *u8
not ?*u8
. The orelse
keyword unwrapped the optional type and therefore is guaranteed to be non-null everywhere it is used in the function.
The other form of checking against NULL you might see looks like this:
// do some stuff
if (optional_foo) |foo| {
doSomethingWithFoo(foo);
}
// do some stuff
}
Once again, the notable thing here is that inside the if block, foo
is no longer an optional pointer, it is a pointer, which cannot be null.
One benefit to this is that functions which take pointers as arguments can be annotated with the “nonnull” attribute - __attribute__((nonnull))
in GCC. The optimizer can sometimes make better decisions knowing that pointer arguments cannot be null.
An optional is created by putting ?
in front of a type. You can use compile-time reflection to access the child type of an optional:
test.zig
const expect = @import("std").testing.expect;
test "optional type" {
// Declare an optional and coerce from null:
var foo: ?i32 = null;
// Coerce from child type of an optional
foo = 1234;
// Use compile-time reflection to access the child type of the optional:
}
Just like , null
has its own type, and the only way to use it is to cast it to a different type:
const optional_value: ?i32 = null;
test.zig
const expect = @import("std").testing.expect;
test "optional pointers" {
// Pointers cannot be null. If you want a null pointer, use the optional
// prefix `?` to make the pointer type optional.
var ptr: ?*i32 = null;
var x: i32 = 1;
ptr = &x;
try expect(ptr.?.* == 1);
// Optional pointers are the same size as normal pointers, because pointer
// value 0 is used as the null value.
}