# Nullable Pointers

### Nullable Pointers

A pointer stores the address of a value in memory.

In Zig, a normal pointer is expected to point to something valid. It is not supposed to be `null`.

```zig
const ptr: *i32 = &value;
```

The type `*i32` means:

```text
a pointer to an i32
```

It does not mean:

```text
a pointer to an i32, or maybe nothing
```

When a pointer may be missing, you must write that in the type:

```zig
const maybe_ptr: ?*i32 = null;
```

The type `?*i32` means:

```text
either a pointer to an i32, or null
```

That is a nullable pointer.

#### Why Zig Separates Pointers from Nullable Pointers

In C, a pointer can usually be `NULL`.

```c
int *ptr = NULL;
```

That flexibility is convenient, but it also creates a common bug: code receives a pointer and forgets to check whether it is null.

Zig makes the difference visible.

```zig
*i32   // must point to an i32
?*i32  // may point to an i32, or may be null
```

This matters because the type tells the truth.

If a function receives `*User`, the caller must provide a real pointer.

If a function receives `?*User`, the caller may provide `null`, and the function must handle it.

#### A Simple Nullable Pointer

Here is a small example:

```zig
const std = @import("std");

pub fn main() void {
    var number: i32 = 42;

    const maybe_ptr: ?*i32 = &number;

    if (maybe_ptr) |ptr| {
        std.debug.print("value: {}\n", .{ptr.*});
    } else {
        std.debug.print("no pointer\n", .{});
    }
}
```

This line creates a normal integer:

```zig
var number: i32 = 42;
```

This line creates an optional pointer to that integer:

```zig
const maybe_ptr: ?*i32 = &number;
```

The value is not `null`. It contains the address of `number`.

Before using it, we unwrap it:

```zig
if (maybe_ptr) |ptr| {
    std.debug.print("value: {}\n", .{ptr.*});
}
```

Inside the block, `ptr` is a normal `*i32`.

To read the value pointed to by `ptr`, use:

```zig
ptr.*
```

This is called dereferencing.

#### The Null Case

A nullable pointer can also contain `null`.

```zig
const std = @import("std");

pub fn main() void {
    const maybe_ptr: ?*i32 = null;

    if (maybe_ptr) |ptr| {
        std.debug.print("value: {}\n", .{ptr.*});
    } else {
        std.debug.print("no pointer\n", .{});
    }
}
```

In this program, the `else` block runs.

The important part is that Zig does not allow this:

```zig
const maybe_ptr: ?*i32 = null;
std.debug.print("value: {}\n", .{maybe_ptr.*}); // error
```

`maybe_ptr` is optional. It might be `null`. You must unwrap it before dereferencing it.

#### Nullable Pointer Parameters

Nullable pointers are often used in function parameters.

Suppose a function can optionally receive a logger:

```zig
const std = @import("std");

const Logger = struct {
    prefix: []const u8,

    fn log(self: *Logger, message: []const u8) void {
        std.debug.print("{s}: {s}\n", .{ self.prefix, message });
    }
};

fn runTask(logger: ?*Logger) void {
    if (logger) |log| {
        log.log("starting task");
    }

    // task work here

    if (logger) |log| {
        log.log("finished task");
    }
}

pub fn main() void {
    var logger = Logger{ .prefix = "app" };

    runTask(&logger);
    runTask(null);
}
```

The function type says the logger is optional:

```zig
fn runTask(logger: ?*Logger) void
```

That is clearer than passing a fake logger, a special flag, or an invalid pointer.

#### Normal Pointers Are Non-Null

A normal pointer should not be used for a missing value.

```zig
fn printNumber(ptr: *const i32) void {
    std.debug.print("{}\n", .{ptr.*});
}
```

This function expects a real pointer.

Calling it with `null` is not allowed:

```zig
printNumber(null); // error
```

That is useful. The compiler protects the function from a whole class of null pointer bugs.

If the function should accept no pointer, say so explicitly:

```zig
fn printNumber(ptr: ?*const i32) void {
    if (ptr) |p| {
        std.debug.print("{}\n", .{p.*});
    } else {
        std.debug.print("no number\n", .{});
    }
}
```

The type tells callers what is allowed.

#### Optional Pointer to Const

You will often see this form:

```zig
?*const T
```

For example:

```zig
const maybe_name: ?*const User = null;
```

Read it as:

```text
maybe a pointer to a const User
```

The `?` applies to the pointer. The `const` applies to the value being pointed to.

So:

```zig
?*const User
```

means:

```text
the pointer may be null, and if it is present, it points to a User that should not be modified through this pointer
```

This is different from:

```zig
*const User
```

which means:

```text
a non-null pointer to a const User
```

#### Nullable Pointers and Mutation

If the pointer is present and points to mutable data, you can change the value through it.

```zig
const std = @import("std");

fn increment(ptr: ?*i32) void {
    if (ptr) |p| {
        p.* += 1;
    }
}

pub fn main() void {
    var count: i32 = 10;

    increment(&count);
    increment(null);

    std.debug.print("count: {}\n", .{count});
}
```

After `increment(&count)`, `count` becomes `11`.

After `increment(null)`, nothing happens.

Inside the function, this line unwraps the optional pointer:

```zig
if (ptr) |p| {
```

Then this line modifies the original integer:

```zig
p.* += 1;
```

#### Nullable Pointers vs Optional Values

These two types are different:

```zig
?i32
?*i32
```

`?i32` means:

```text
maybe an i32 value
```

`?*i32` means:

```text
maybe a pointer to an i32 value somewhere else
```

Use `?i32` when the optional value itself is small and easy to copy.

Use `?*T` when you need to refer to an existing object, avoid copying, or allow mutation through the pointer.

Example:

```zig
const maybe_age: ?u8 = 30;
```

This stores the value directly.

```zig
const maybe_user: ?*User = &user;
```

This stores an address pointing to an existing `User`.

#### Nullable Pointers and C Interop

Nullable pointers are especially important when working with C libraries.

Many C APIs use null pointers to mean “not provided,” “not found,” or “end of list.”

In Zig, the binding should express that.

A C function might look like this:

```c
User *find_user(int id);
```

If it can return `NULL`, the Zig type should be treated as optional:

```zig
const user: ?*User = find_user(42);
```

Then Zig forces you to check:

```zig
if (user) |u| {
    // use u
} else {
    // not found
}
```

This is one of Zig’s strengths. It can work with C, but it can still make nullability explicit in Zig code.

#### Do Not Use `.?` Carelessly

You can force unwrap a nullable pointer with `.?`.

```zig
const ptr = maybe_ptr.?;
ptr.* = 123;
```

This means:

```text
If maybe_ptr is present, use it.
If maybe_ptr is null, panic.
```

This is acceptable when null would mean a programming bug.

For example, after a previous check:

```zig
if (maybe_ptr == null) {
    unreachable;
}

const ptr = maybe_ptr.?;
```

But it should not be the default habit.

This is risky:

```zig
fn printUser(user: ?*User) void {
    std.debug.print("{s}\n", .{user.?.name});
}
```

If `user` can be null in normal use, this function can crash. Write the null case instead.

```zig
fn printUser(user: ?*User) void {
    if (user) |u| {
        std.debug.print("{s}\n", .{u.name});
    } else {
        std.debug.print("no user\n", .{});
    }
}
```

#### A Practical Pattern: Optional Parent Pointer

Nullable pointers are common in linked data structures.

For example, a tree node may have a parent, but the root node has no parent.

```zig
const Node = struct {
    value: i32,
    parent: ?*Node,
};
```

A child node can point to its parent:

```zig
var root = Node{
    .value = 1,
    .parent = null,
};

var child = Node{
    .value = 2,
    .parent = &root,
};
```

The root has no parent, so it uses `null`.

The child has a parent, so it stores `&root`.

When walking upward through the tree, unwrap the pointer:

```zig
if (child.parent) |parent| {
    std.debug.print("parent value: {}\n", .{parent.value});
}
```

This is a natural use of nullable pointers because absence is part of the data model.

#### The Main Idea

A nullable pointer is an optional pointer.

```zig
?*T
```

means:

```text
a pointer to T, or null
```

A normal pointer:

```zig
*T
```

means:

```text
a pointer to T
```

This distinction is important. Zig does not make every pointer nullable by default. If a pointer may be missing, the type must say so.

That gives you clearer APIs, fewer null pointer bugs, and code that states its assumptions directly.

