Sentinel Pointer ([*:0]u8)
In Zig, the sentinel pointer [*:0]u8 lets you work with data compatible with C's null-terminated strings (char *). Unlike a regular slice, it does not store a separate length — instead, it marks the end of the string by placing a sentinel value (a terminator marker) at the end. This is required when interoperating with C (FFI) or calling system APIs such as std.os.
Syntax
// -----------------------------------------------
// [*:S]T — sentinel pointer type
// -----------------------------------------------
// S is the sentinel value (terminator). T is the element type.
// The most common form is [*:0]u8 (null-terminated u8 pointer).
const ptr: [*:0]u8 = ...;
// -----------------------------------------------
// Getting a sentinel pointer from a string literal
// -----------------------------------------------
// The type of a string literal is *const [N:0]u8 (sentinel-terminated array pointer).
// It can be implicitly cast to [*:0]const u8.
const name: [*:0]const u8 = "Goku";
// -----------------------------------------------
// std.mem.span() — converting a sentinel pointer to a slice
// -----------------------------------------------
const std = @import("std");
const slice: []const u8 = std.mem.span(ptr);
// std.mem.span() scans for the sentinel value and returns a []const u8 slice.
// -----------------------------------------------
// Passing to a C function (extern declaration example)
// -----------------------------------------------
// Example declaration equivalent to C's puts()
extern fn puts(s: [*:0]const u8) c_int;
// _ = puts("Vegeta"); // You can pass a sentinel pointer directly to C's puts().
Syntax reference
| Syntax / keyword | Description |
|---|---|
[*:0]u8 | A null-terminated (ends with 0) u8 sentinel pointer type. Equivalent to C's char *. |
[*:0]const u8 | A read-only null-terminated sentinel pointer type. Equivalent to C's const char *. |
[*:S]T | A generic sentinel pointer type with sentinel value S and element type T. |
"string literal" | Has type *const [N:0]u8, which is implicitly castable to [*:0]const u8. |
std.mem.span(ptr) | Converts a sentinel pointer to a []const u8 slice. The length is determined by scanning for the sentinel. |
std.mem.len(ptr) | Returns the length (in bytes) of a sentinel pointer string, counting up to the null terminator. |
ptr[i] | Accesses a sentinel pointer by index. No bounds checking is performed. |
extern fn f(s: [*:0]const u8) | How to declare a C function that takes a null-terminated string argument in Zig. |
Sample code
dragonball_sentinel.zig
// dragonball_sentinel.zig — sample demonstrating sentinel pointer [*:0]u8
// Uses Dragon Ball character names to show how to obtain sentinel pointers,
// convert them to slices, get their lengths, and access them by index.
// Import the standard library.
const std = @import("std");
// -----------------------------------------------
// Helper function that receives a null-terminated string and prints its details.
// Takes [*:0]const u8 as the argument, equivalent to C's char *.
// -----------------------------------------------
fn printCStr(label: []const u8, ptr: [*:0]const u8) !void {
const stdout = std.io.getStdOut().writer();
// Use std.mem.span() to convert the sentinel pointer to a []const u8 slice.
// Once it is a slice, it can be printed with the {s} format specifier.
const slice: []const u8 = std.mem.span(ptr);
// Use std.mem.len() to get the number of bytes up to the null terminator.
const len: usize = std.mem.len(ptr);
try stdout.print(" {s}: \"{s}\" ({d} bytes)\n", .{ label, slice, len });
}
pub fn main() !void {
// Get a Writer for standard output.
const stdout = std.io.getStdOut().writer();
try stdout.print("=== Dragon Ball Sentinel Pointer Demo ===\n\n", .{});
// -----------------------------------------------
// String literals have type *const [N:0]u8.
// They can be implicitly cast to [*:0]const u8 and stored in a variable.
// -----------------------------------------------
// Declare each character name as [*:0]const u8.
const goku: [*:0]const u8 = "悟空";
const vegeta: [*:0]const u8 = "ベジータ";
const piccolo: [*:0]const u8 = "ピッコロ";
const trunks: [*:0]const u8 = "トランクス";
const frieza: [*:0]const u8 = "フリーザ";
try stdout.print("--- Character names and lengths ---\n", .{});
// Print the sentinel pointer info for each name using the helper function.
try printCStr("Goku", goku);
try printCStr("Vegeta", vegeta);
try printCStr("Piccolo", piccolo);
try printCStr("Trunks", trunks);
try printCStr("Frieza", frieza);
// -----------------------------------------------
// Index access — use ptr[i] to read individual bytes.
// Japanese characters are multi-byte in UTF-8.
// -----------------------------------------------
try stdout.print("\n--- Index access (first bytes of Goku) ---\n", .{});
// goku[0] is the u8 value of the first UTF-8 byte.
try stdout.print(" goku[0] = 0x{x}\n", .{goku[0]});
try stdout.print(" goku[1] = 0x{x}\n", .{goku[1]});
try stdout.print(" goku[2] = 0x{x}\n", .{goku[2]});
// -----------------------------------------------
// Converting to slices with std.mem.span() for comparison.
// Slices carry length information, so std.mem.eql() can compare contents.
// -----------------------------------------------
try stdout.print("\n--- Slice conversion and comparison ---\n", .{});
const goku_slice: []const u8 = std.mem.span(goku);
const frieza_slice: []const u8 = std.mem.span(frieza);
// Compare the contents of two slices with std.mem.eql().
const is_same = std.mem.eql(u8, goku_slice, frieza_slice);
try stdout.print(" Goku == Frieza : {}\n", .{is_same});
const goku2: [*:0]const u8 = "悟空";
const goku2_slice: []const u8 = std.mem.span(goku2);
const is_goku = std.mem.eql(u8, goku_slice, goku2_slice);
try stdout.print(" Goku == Goku : {}\n", .{is_goku});
// -----------------------------------------------
// Example of a mutable [*:0]u8.
// Prepare a var array as a buffer and obtain a sentinel pointer to it.
// -----------------------------------------------
try stdout.print("\n--- Mutable sentinel pointer ---\n", .{});
// Declare a mutable null-terminated buffer ([N:0]u8 type).
var name_buf: [32:0]u8 = undefined;
// Copy Frieza's name into the buffer as the initial value.
@memset(&name_buf, 0); // Zero-fill the buffer.
const frieza_bytes = std.mem.span(frieza);
std.mem.copyForwards(u8, &name_buf, frieza_bytes);
// Get a mutable sentinel pointer to the start of the buffer.
const mutable_ptr: [*:0]u8 = &name_buf;
const mutable_slice: []const u8 = std.mem.span(mutable_ptr);
try stdout.print(" Before: \"{s}\"\n", .{mutable_slice});
// Overwrite the buffer contents with Vegeta's name.
@memset(&name_buf, 0);
const vegeta_bytes = std.mem.span(vegeta);
std.mem.copyForwards(u8, &name_buf, vegeta_bytes);
const updated_slice: []const u8 = std.mem.span(mutable_ptr);
try stdout.print(" After: \"{s}\"\n", .{updated_slice});
try stdout.print("\nDone.\n", .{});
}
zig run dragonball_sentinel.zig === Dragon Ball Sentinel Pointer Demo === --- Character names and lengths --- Goku: "悟空" (6 bytes) Vegeta: "ベジータ" (12 bytes) Piccolo: "ピッコロ" (12 bytes) Trunks: "トランクス" (15 bytes) Frieza: "フリーザ" (12 bytes) --- Index access (first bytes of Goku) --- goku[0] = 0xe6 goku[1] = 0x82 goku[2] = 0x9f --- Slice conversion and comparison --- Goku == Frieza : false Goku == Goku : true --- Mutable sentinel pointer --- Before: "フリーザ" After: "ベジータ" Done.
Overview
The sentinel pointer [*:0]u8 is a pointer type that marks the end of data by placing a specific sentinel value at the end of an array. When 0 is the sentinel value, it becomes a null-terminated string, which has the same meaning as C's char * / const char *. The actual type of a string literal is *const [N:0]u8 (a sentinel-terminated array pointer), but it can be implicitly cast to [*:0]const u8, so it can be passed directly as an argument to C functions. When you need the length, use std.mem.len() to get the byte count up to the sentinel, or use std.mem.span() to convert it to a []const u8 slice. Slices carry length information, which makes them easier to use with standard functions like std.mem.eql(). The common Zig style is to use slices ([]u8) for general string handling and to use sentinel pointers only where C interoperability is needed. For more on slices, see Slice. For basic pointer operations, see Pointer *T.
If you find any errors or copyright issues, please contact us.