# Sentinel-Terminated Arrays

### Sentinel-Terminated Arrays

A sentinel-terminated array is an array with a special value at the end.

That special value is called the sentinel.

The sentinel marks where the useful data stops.

The most common sentinel is `0`. C strings use this idea. A C string is a sequence of bytes ending with a zero byte.

```text
h e l l o 0
```

The useful text is:

```text
hello
```

The final `0` is not part of the text. It marks the end.

Zig supports this idea directly in its type system.

#### Normal Arrays

A normal array type looks like this:

```zig
[N]T
```

Read it as:

```text
array of N T values
```

For example:

```zig
[4]u8
```

means:

```text
array of 4 u8 values
```

Example:

```zig
const data: [4]u8 = .{ 10, 20, 30, 40 };
```

This array has exactly four items.

#### Sentinel-Terminated Array Types

A sentinel-terminated array type looks like this:

```zig
[N:S]T
```

Read it as:

```text
array of N T values, followed by sentinel S
```

For example:

```zig
[5:0]u8
```

means:

```text
array of 5 u8 values, followed by a 0 sentinel
```

The array has 5 logical items, but there is also a sentinel value after them.

Example:

```zig
const name: [5:0]u8 = .{ 'h', 'e', 'l', 'l', 'o' };
```

The logical items are:

```text
h e l l o
```

The sentinel is automatically present after them:

```text
h e l l o 0
```

The type records that fact.

#### String Literals Have Sentinels

Zig string literals are sentinel-terminated.

This means a string literal like:

```zig
"hello"
```

has bytes for the text, plus a zero byte after the text.

That is why this is valid:

```zig
const message: [*:0]const u8 = "hello";
```

The type:

```zig
[*:0]const u8
```

means:

```text
many item pointer to const u8, terminated by 0
```

The string literal can provide that because it has a zero sentinel.

#### Sentinel Is Not Counted in `.len`

For a sentinel-terminated array, `.len` gives the number of logical items.

It does not count the sentinel.

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

pub fn main() void {
    const name: [5:0]u8 = .{ 'h', 'e', 'l', 'l', 'o' };

    std.debug.print("len = {}\n", .{name.len});
    std.debug.print("sentinel = {}\n", .{name[name.len]});
}
```

Output:

```text
len = 5
sentinel = 0
```

The valid logical indexes are:

```text
0, 1, 2, 3, 4
```

The sentinel is at:

```text
index 5
```

This is unusual compared with normal arrays. For a normal `[5]u8`, index `5` would be out of bounds. For `[5:0]u8`, index `5` is the sentinel.

#### Sentinel-Terminated Slices

Zig also has sentinel-terminated slices:

```zig
[:S]T
```

For example:

```zig
[:0]const u8
```

means:

```text
slice of const u8 values, terminated by 0
```

A normal slice carries a pointer and a length.

A sentinel-terminated slice carries a pointer, a length, and a guarantee that the sentinel exists at the end.

Example:

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

pub fn main() void {
    const message: [:0]const u8 = "hello";

    std.debug.print("len = {}\n", .{message.len});
    std.debug.print("{s}\n", .{message});
}
```

Output:

```text
len = 5
hello
```

The text has length 5. The zero sentinel exists after it.

#### Sentinel-Terminated Pointers

A sentinel-terminated many item pointer looks like this:

```zig
[*:S]T
```

For example:

```zig
[*:0]const u8
```

This means:

```text
many item pointer to const u8, ending with 0
```

Unlike a slice, this pointer does not store a length.

It only says that if you keep reading forward, eventually there is a sentinel.

This is exactly the model used by many C APIs.

#### Why Sentinels Are Useful

Sentinels are useful when a sequence does not carry its length separately.

A normal Zig slice solves this by carrying length:

```zig
[]const u8
```

A C string solves this by ending with zero:

```c
const char *name;
```

The pointer alone does not say the length. The program reads bytes until it finds `0`.

Zig can represent that more precisely than a plain pointer:

```zig
[*:0]const u8
```

This type tells the reader:

The pointer does not carry a length, but the sequence is expected to end with zero.

#### C Strings

C strings are the main reason beginners meet sentinels.

Many C functions expect strings like this:

```c
int puts(const char *s);
```

The function receives a pointer to the first character. It keeps reading until it finds a zero byte.

In Zig, a compatible type is often:

```zig
[*:0]const u8
```

Example:

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

pub fn main() void {
    const s: [*:0]const u8 = "hello";

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

The string literal `"hello"` can be used because it has a zero sentinel.

#### Sentinel Values Can Be Other Values

The sentinel does not have to be `0`.

For example, you could use `255` as a sentinel for a byte sequence:

```zig
const data: [3:255]u8 = .{ 10, 20, 30 };
```

This represents:

```text
10 20 30 255
```

The logical length is 3.

The sentinel is `255`.

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

pub fn main() void {
    const data: [3:255]u8 = .{ 10, 20, 30 };

    std.debug.print("len = {}\n", .{data.len});
    std.debug.print("sentinel = {}\n", .{data[data.len]});
}
```

Output:

```text
len = 3
sentinel = 255
```

The type records the sentinel value.

#### Creating a Sentinel Slice from a Range

You can create a sentinel-terminated slice when Zig can prove the sentinel exists.

String literals make this easy:

```zig
const s: [:0]const u8 = "hello";
```

For arrays, the source must actually have the sentinel.

```zig
const data: [3:0]u8 = .{ 1, 2, 3 };
const slice: [:0]const u8 = data[0..];
```

The array type `[3:0]u8` guarantees that `0` exists after the three items.

So the slice can preserve that guarantee.

#### Sentinel-Terminated Slice vs Normal Slice

A normal slice:

```zig
[]const u8
```

says:

```text
pointer plus length
```

A sentinel-terminated slice:

```zig
[:0]const u8
```

says:

```text
pointer plus length, with 0 after the last item
```

That extra guarantee matters when passing data to code that expects a sentinel.

For ordinary Zig APIs, `[]const u8` is usually enough.

For C string APIs, `[:0]const u8` or `[*:0]const u8` is often needed.

#### A Function That Accepts a C-Style String

Here is a function that walks through a sentinel-terminated pointer:

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

fn printCString(s: [*:0]const u8) void {
    var i: usize = 0;

    while (s[i] != 0) : (i += 1) {
        std.debug.print("{c}", .{s[i]});
    }

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

pub fn main() void {
    printCString("zig");
}
```

Output:

```text
zig
```

The function does not receive a length.

It stops when it sees the sentinel value `0`.

#### A Function That Accepts a Zig String

Now compare that with a normal Zig function:

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

fn printString(s: []const u8) void {
    for (s) |byte| {
        std.debug.print("{c}", .{byte});
    }

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

pub fn main() void {
    printString("zig");
}
```

This function receives a slice.

It does not need a sentinel because it has `s.len`.

Both styles work, but they express different contracts.

The first says:

```text
I need a zero-terminated sequence.
```

The second says:

```text
I need a slice with a known length.
```

For Zig code, prefer the second style.

For C interop, use the first style when required.

#### Common Mistake: Confusing Length and Sentinel

For this value:

```zig
const s: [:0]const u8 = "hello";
```

The length is:

```text
5
```

The memory contains:

```text
h e l l o 0
```

The sentinel is present, but it is not part of the length.

So this loop prints only the text:

```zig
for (s) |byte| {
    std.debug.print("{c}", .{byte});
}
```

It does not print the sentinel.

To inspect the sentinel directly:

```zig
std.debug.print("{}\n", .{s[s.len]});
```

That prints:

```text
0
```

#### Common Mistake: Assuming Every Slice Has a Sentinel

This is wrong:

```zig
fn needsCString(s: []const u8) void {
    const p: [*:0]const u8 = s.ptr; // not generally safe
    _ = p;
}
```

A normal slice does not guarantee there is a zero byte after its last item.

The memory might look like this:

```text
h e l l o X Y Z
```

There may be no zero byte where C expects one.

So you cannot freely treat every `[]const u8` as a C string.

If a C function needs a zero-terminated string, you must provide one.

#### Making a Zero-Terminated Copy

Sometimes you have a normal slice:

```zig
[]const u8
```

but need a zero-terminated string for C.

Then you often allocate a new buffer with space for the sentinel.

Conceptually:

```zig
const result = try allocator.alloc(u8, s.len + 1);
```

Copy the bytes, then add zero at the end:

```zig
@memcpy(result[0..s.len], s);
result[s.len] = 0;
```

Then pass the pointer as a zero-terminated sequence.

In real programs, prefer standard library helpers when available. The main idea is that the sentinel must really exist in memory.

#### Sentinel-Terminated Arrays Are About Guarantees

The value is not just the bytes.

The type carries a guarantee.

```zig
[5:0]u8
```

guarantees a `0` after 5 logical items.

```zig
[:0]u8
```

guarantees a `0` after the slice length.

```zig
[*:0]u8
```

guarantees the sequence eventually ends with `0`.

These guarantees help Zig express low-level contracts that are common in C and systems programming.

#### When to Use Sentinels

Use sentinel-terminated types when:

You are working with C strings.

You are calling APIs that expect zero-terminated data.

You are representing a format that uses a sentinel value.

You want the type to record the end marker.

Avoid them when:

A normal slice is enough.

You already have a length.

The sentinel has no real meaning.

You are writing ordinary Zig application code.

Most Zig functions should take slices, not sentinel pointers.

#### The Main Idea

A sentinel-terminated array stores a special end value after its logical items.

The type:

```zig
[N:S]T
```

means an array of `N` items of type `T`, followed by sentinel `S`.

The type:

```zig
[:S]T
```

means a slice with sentinel `S` after its last item.

The type:

```zig
[*:S]T
```

means a many item pointer to a sequence that ends with sentinel `S`.

Sentinels are especially important for C strings. In normal Zig code, slices are usually simpler because they carry their length directly.

