पाठ 6 / 25
Pointers and Optional Pointers
Use single-item, many-item and optional pointers and understand const.
Pointers without the footguns
Zig distinguishes kinds of pointers in the type system. *T points to exactly one item: take an address with &x and dereference with ptr.*. [*]T is a many-item pointer with unknown length (mainly for C interop), and slices []T are the safe, length-carrying alternative. *const T points to data you may not modify through it, and function parameters are immutable by default, so pass *T when a function must modify its argument. Ordinary pointers cannot be null: a pointer that may be absent is an optional pointer ?*T, which must be unwrapped (if (p) |ptr|, orelse, or .?, which panics if null) before use, eliminating a whole class of null dereference bugs at compile time. Struct field access auto-dereferences single pointers (ptr.field). @ptrCast, @intFromPtr and alignment annotations (*align(16) T) exist for low-level work. Zig does not have references or a borrow checker: you are responsible for lifetimes, so never return a pointer to a local variable, and be careful that slices into a growing ArrayList can be invalidated when it reallocates.
Pointers, const and optionals
Modifying through *T, read-only *const T and nullable ?*T.
const std = @import("std");
const Account = struct {
owner: []const u8,
balance_paise: u64,
};
fn deposit(account: *Account, amount: u64) void { // *T: may modify
account.balance_paise += amount; // auto-dereference for fields
}
fn describe(account: *const Account) void { // *const T: read-only
std.debug.print("{s}: {d} paise\n", .{ account.owner, account.balance_paise });
}
fn findAccount(accounts: []Account, owner: []const u8) ?*Account {
for (accounts) |*acc| { // capture a pointer to each element
if (std.mem.eql(u8, acc.owner, owner)) return acc;
}
return null;
}
pub fn main() void {
var accounts = [_]Account{
.{ .owner = "Asha", .balance_paise = 100_000 },
.{ .owner = "Ravi", .balance_paise = 5_000 },
};
if (findAccount(&accounts, "Ravi")) |ravi| {
deposit(ravi, 20_000);
describe(ravi); // Ravi: 25000 paise
}
const missing = findAccount(&accounts, "Meera");
const balance = if (missing) |m| m.balance_paise else 0;
std.debug.print("Meera balance: {d}\n", .{balance});
var x: u32 = 10;
const px = &x;
px.* += 5; // explicit dereference
std.debug.print("x={d}\n", .{x}); // 15
}Never return pointers to locals
A local variable lives on the stack frame of its function. Returning &local leaves a dangling pointer; Zig catches some cases at compile time, but not all. Return values by copy, or allocate with an allocator the caller frees.
त्वरित जाँच: How do you represent a pointer that may be null in Zig?
- With an optional pointer ?*T, which must be unwrapped before use
- *T can always be null
- With [*]T
- With undefined
Answer
With an optional pointer ?*T, which must be unwrapped before use — Plain pointers are non-null; ?*T makes nullability explicit and checked.