# String Literals

### String Literals

A string literal is text written directly in your source code.

```zig
const name = "Zig";
```

The text between the double quotes is the string literal:

```zig
"Zig"
```

In Zig, strings are bytes. More precisely, a string literal is a constant sequence of UTF-8 bytes.

That means `"Zig"` contains these three visible bytes:

```text
Z i g
```

Their byte values are:

```text
90 105 103
```

Zig does not have a special built-in `String` type like many high-level languages. Most Zig code represents text as:

```zig
[]const u8
```

Read this as:

```text
read-only slice of bytes
```

That is the most common type for text passed to functions.

#### A Basic String Literal

Here is a complete program:

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

pub fn main() void {
    const message = "Hello, Zig!";

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

Output:

```text
Hello, Zig!
```

The `{s}` formatter tells Zig to print the bytes as a string.

This line:

```zig
const message = "Hello, Zig!";
```

creates a string literal and gives it the name `message`.

You can pass it to a function that expects `[]const u8`:

```zig
fn printMessage(message: []const u8) void {
    std.debug.print("{s}\n", .{message});
}
```

Usage:

```zig
printMessage("Hello");
```

This works because a string literal can be used as a read-only byte slice.

#### Strings Are Read-Only

String literals are constant data.

This is valid:

```zig
const name = "Zig";
```

This is not valid:

```zig
name[0] = 'B';
```

You cannot modify a string literal.

The reason is simple: string literals are stored as read-only program data. Many parts of the program may refer to the same literal. Allowing mutation would be unsafe and confusing.

Use a mutable array if you want text that can change:

```zig
var name = [_]u8{ 'Z', 'i', 'g' };

name[0] = 'B';
```

Now the bytes contain:

```text
B i g
```

This is not a string literal anymore. It is a mutable array of bytes.

#### String Literals and `[]const u8`

Most functions that read text should accept this type:

```zig
[]const u8
```

Example:

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

fn greet(name: []const u8) void {
    std.debug.print("Hello, {s}!\n", .{name});
}

pub fn main() void {
    greet("Zig");
    greet("Ada");
    greet("C");
}
```

Output:

```text
Hello, Zig!
Hello, Ada!
Hello, C!
```

The function does not care where the bytes come from. They may come from a string literal, an array, a slice, or allocated memory.

That is why `[]const u8` is the standard input type for text.

#### A String Literal Has a Sentinel

A string literal in Zig has a sentinel value at the end.

For example:

```zig
const name = "Zig";
```

The visible text has 3 bytes:

```text
Z i g
```

But Zig also stores a zero byte after them:

```text
Z i g 0
```

This final zero is called a sentinel.

It is useful for C interop because C strings usually end with a zero byte.

The visible length is still 3:

```zig
const name = "Zig";
std.debug.print("{}\n", .{name.len});
```

Output:

```text
3
```

The sentinel is not counted in `.len`.

For beginners, remember this:

A string literal behaves like a read-only byte slice, but it also has a hidden zero byte after the visible text.

#### Escape Sequences

Some characters are written with escape sequences.

A newline is written as:

```zig
"\n"
```

Example:

```zig
std.debug.print("line one\nline two\n", .{});
```

Output:

```text
line one
line two
```

A tab is written as:

```zig
"\t"
```

Example:

```zig
std.debug.print("name\tage\n", .{});
```

A double quote inside a string is written as:

```zig
"She said \"hello\""
```

A backslash is written as:

```zig
"C:\\Users\\zig"
```

Common escapes:

| Escape | Meaning |
|---|---|
| `\n` | newline |
| `\t` | tab |
| `\"` | double quote |
| `\\` | backslash |
| `\r` | carriage return |
| `\0` | zero byte |

#### Multiline String Literals

Zig has multiline string literals.

They begin each line with two backslashes:

```zig
const text =
    \\line one
    \\line two
    \\line three
;
```

This is useful for long text, generated code, help messages, SQL, JSON examples, and test data.

Example:

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

pub fn main() void {
    const help =
        \\usage: demo [options]
        \\
        \\options:
        \\  --help      show help
        \\  --version   show version
    ;

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

Output:

```text
usage: demo [options]

options:
  --help      show help
  --version   show version
```

The multiline form avoids escaping every quote and newline manually.

#### Strings Are UTF-8 Bytes

Zig string literals are UTF-8.

That means non-ASCII text is stored as one or more bytes per character.

Example:

```zig
const text = "é";
```

The character `é` is one visible character, but in UTF-8 it uses 2 bytes.

So this:

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

prints:

```text
2
```

Another example:

```zig
const text = "你好";
```

Each Chinese character uses 3 bytes in UTF-8, so the length is 6 bytes.

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

Output:

```text
6
```

This is important.

In Zig, `.len` on text usually means byte length, not character count.

#### Iterating Over Bytes

When you loop over a string, you get bytes.

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

pub fn main() void {
    const text = "Zig";

    for (text) |byte| {
        std.debug.print("{}\n", .{byte});
    }
}
```

Output:

```text
90
105
103
```

Those are byte values.

You can print them as characters:

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

Output:

```text
Z
i
g
```

This works well for ASCII text.

For full Unicode text, one visible character may use multiple bytes. You should not assume one byte equals one character.

#### Comparing Strings

Use the standard library to compare strings.

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

pub fn main() void {
    const a = "zig";
    const b = "zig";
    const c = "Zig";

    std.debug.print("{}\n", .{std.mem.eql(u8, a, b)});
    std.debug.print("{}\n", .{std.mem.eql(u8, a, c)});
}
```

Output:

```text
true
false
```

This function compares byte sequences.

```zig
std.mem.eql(u8, a, b)
```

means:

```text
compare two slices of u8 values for equality
```

Do not compare strings by comparing pointers. You usually want to compare contents, not addresses.

#### Finding Text

The standard library has functions for searching byte slices.

Example:

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

pub fn main() void {
    const text = "hello zig";

    if (std.mem.indexOf(u8, text, "zig")) |index| {
        std.debug.print("found at {}\n", .{index});
    } else {
        std.debug.print("not found\n", .{});
    }
}
```

Output:

```text
found at 6
```

The result is optional. If the search succeeds, you get an index. If it fails, you get `null`.

#### Slicing Strings

Since strings behave like byte slices, you can slice them.

```zig
const text = "hello zig";

const first = text[0..5];
const second = text[6..9];
```

Now:

```text
first  = hello
second = zig
```

Example:

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

pub fn main() void {
    const text = "hello zig";

    std.debug.print("{s}\n", .{text[0..5]});
    std.debug.print("{s}\n", .{text[6..9]});
}
```

Output:

```text
hello
zig
```

Be careful with UTF-8. Slicing at arbitrary byte positions can split a character.

For ASCII text, byte indexes and character positions match. For non-ASCII text, they may not.

#### Mutable Text

A string literal cannot be changed, but a byte array can.

```zig
var text = [_]u8{ 'z', 'i', 'g' };

text[0] = 'b';
```

Now it contains:

```text
b i g
```

You can print it as a string by slicing the array:

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

Complete example:

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

pub fn main() void {
    var text = [_]u8{ 'z', 'i', 'g' };

    text[0] = 'b';

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

Output:

```text
big
```

Use mutable byte arrays or allocated buffers when text must change.

#### Building Text with a Buffer

A common Zig pattern is: give a function a buffer, and let it write text into that buffer.

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

pub fn main() !void {
    var buffer: [64]u8 = undefined;

    const name = "Zig";
    const message = try std.fmt.bufPrint(buffer[0..], "Hello, {s}!", .{name});

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

Output:

```text
Hello, Zig!
```

Here:

```zig
var buffer: [64]u8 = undefined;
```

creates a fixed array of 64 bytes.

This line:

```zig
const message = try std.fmt.bufPrint(buffer[0..], "Hello, {s}!", .{name});
```

writes formatted text into the buffer and returns a slice containing the part that was used.

The returned `message` points into `buffer`. It does not allocate.

#### Building Text with an Allocator

When the output size is not known in advance, you can allocate.

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

pub fn main() !void {
    const allocator = std.heap.page_allocator;

    const name = "Zig";
    const message = try std.fmt.allocPrint(allocator, "Hello, {s}!", .{name});
    defer allocator.free(message);

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

This creates a new allocated byte slice.

Because it allocates, you must free it:

```zig
defer allocator.free(message);
```

This pattern is common:

```text
bufPrint: writes into caller-provided memory
allocPrint: allocates new memory
```

Use `bufPrint` when you already have a buffer. Use `allocPrint` when you need the function to create the result.

#### Common Mistake: Expecting `.len` to Count Characters

This surprises many beginners:

```zig
const text = "é";
std.debug.print("{}\n", .{text.len});
```

Output:

```text
2
```

The visible character count is 1. The byte count is 2.

Zig reports the byte count.

For ASCII text, byte count and visible character count are usually the same. For Unicode text, they often differ.

#### Common Mistake: Modifying a String Literal

This is wrong:

```zig
const text = "hello";
text[0] = 'H';
```

String literals are read-only.

Use a mutable array:

```zig
var text = [_]u8{ 'h', 'e', 'l', 'l', 'o' };
text[0] = 'H';
```

Or allocate mutable memory if the size is dynamic.

#### Common Mistake: Using `{}` Instead of `{s}`

This is usually wrong for printing strings:

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

Use `{s}`:

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

The `{s}` formatter means “print this byte slice as a string.”

Use `{}` for many ordinary values, such as integers and booleans. Use `{s}` for strings.

#### Complete Example

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

fn startsWithHello(text: []const u8) bool {
    return std.mem.startsWith(u8, text, "hello");
}

pub fn main() !void {
    const a = "hello zig";
    const b = "goodbye zig";

    std.debug.print("{s}: {}\n", .{ a, startsWithHello(a) });
    std.debug.print("{s}: {}\n", .{ b, startsWithHello(b) });

    var buffer: [64]u8 = undefined;
    const message = try std.fmt.bufPrint(buffer[0..], "language = {s}", .{"Zig"});

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

Output:

```text
hello zig: true
goodbye zig: false
language = Zig
```

This example shows the main string literal habits:

```zig
const a = "hello zig";
```

A string literal is read-only text.

```zig
fn startsWithHello(text: []const u8) bool
```

A function accepts text as `[]const u8`.

```zig
std.mem.startsWith(u8, text, "hello")
```

String operations usually work on byte slices.

```zig
std.fmt.bufPrint(buffer[0..], "language = {s}", .{"Zig"})
```

Formatted text can be written into a caller-provided buffer.

#### Summary

A string literal is read-only UTF-8 text stored in the program.

Most Zig APIs represent text as:

```zig
[]const u8
```

That means a read-only slice of bytes.

String length is byte length, not character count.

String literals cannot be modified. Use a mutable byte array or allocated buffer when you need editable text.

Use `{s}` to print strings. Use `std.mem` functions to compare, search, and inspect text.

Zig treats text honestly: it is bytes. That may feel low-level at first, but it gives you clear control over memory, encoding, and allocation.

