# Mutable Strings

### Mutable Strings

Zig does not have a separate built-in mutable `String` type.

Text is usually stored as bytes. If the bytes are read-only, you use `[]const u8`. If the bytes can change, you use `[]u8`.

```zig
[]const u8  // read-only bytes
[]u8        // mutable bytes
```

A string literal is read-only:

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

You cannot change it:

```zig
name[0] = 'Z'; // error
```

To change text, you need mutable storage.

#### Mutable Text with an Array

The simplest mutable text is a byte array:

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

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

    name[0] = 'Z';

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

Output:

```text
Zig
```

The array owns the bytes. Since it is declared with `var`, its bytes can change.

This line:

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

changes the first byte.

This line:

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

prints the whole array as a slice.

#### Mutable Text with a Buffer

A buffer is a block of memory used to store data temporarily.

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

This creates 64 bytes on the stack. The bytes are uninitialized because of `undefined`.

You should write valid data before reading from it.

Example:

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

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

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

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

Output:

```text
Hello, Zig
```

`bufPrint` writes into the buffer and returns a slice of the part it used.

The returned `message` does not own memory. It points into `buffer`.

#### The Buffer May Be Larger Than the Text

This is important.

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

The buffer has 64 bytes. The message has 2 bytes.

```text
buffer capacity: 64 bytes
message length: 2 bytes
```

You should print `message`, not the whole buffer.

Correct:

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

Wrong:

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

The rest of the buffer may contain uninitialized garbage.

#### Mutable Slice Parameters

A function can accept mutable text as `[]u8`.

```zig
fn uppercaseAscii(text: []u8) void {
    for (text) |*byte| {
        if (byte.* >= 'a' and byte.* <= 'z') {
            byte.* = byte.* - 32;
        }
    }
}
```

Usage:

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

fn uppercaseAscii(text: []u8) void {
    for (text) |*byte| {
        if (byte.* >= 'a' and byte.* <= 'z') {
            byte.* = byte.* - 32;
        }
    }
}

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

    uppercaseAscii(name[0..]);

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

Output:

```text
ZIG
```

The function takes a mutable slice:

```zig
[]u8
```

That means it can modify the bytes.

The loop uses:

```zig
|*byte|
```

This captures each byte by pointer. Without the `*`, the loop variable would be a copy, and changing it would not modify the original slice.

#### ASCII vs UTF-8

The `uppercaseAscii` function only works correctly for ASCII letters.

ASCII letters use one byte each:

```text
a b c ... z
A B C ... Z
```

But UTF-8 text may use multiple bytes for one visible character.

For example:

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

The visible character is one character, but the UTF-8 encoding uses 2 bytes.

Changing arbitrary bytes in UTF-8 text can corrupt the text.

For beginner code, it is fine to write simple ASCII examples. Just remember the boundary:

```text
byte operations are simple
Unicode text rules are more complex
```

#### Replacing Bytes

You can write a function that replaces one byte with another.

```zig
fn replaceByte(text: []u8, old: u8, new: u8) void {
    for (text) |*byte| {
        if (byte.* == old) {
            byte.* = new;
        }
    }
}
```

Usage:

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

fn replaceByte(text: []u8, old: u8, new: u8) void {
    for (text) |*byte| {
        if (byte.* == old) {
            byte.* = new;
        }
    }
}

pub fn main() void {
    var text = [_]u8{ 'h', 'e', 'l', 'l', 'o' };

    replaceByte(text[0..], 'l', 'x');

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

Output:

```text
hexxo
```

This is safe because the replacement keeps the same length. Each `l` byte becomes one `x` byte.

#### Changing Length Is Different

Changing bytes is easy when the length stays the same.

Changing the length is harder.

For example, replacing:

```text
cat
```

with:

```text
tiger
```

needs more space.

A fixed array cannot grow:

```zig
var text = [_]u8{ 'c', 'a', 't' };
```

This array has exactly 3 bytes. It cannot hold 5 bytes.

For variable-length text, you need one of these:

```text
a larger caller-provided buffer
an ArrayList
an allocated slice
```

You will usually use `std.ArrayList(u8)` for text that grows over time.

#### Building Mutable Text with `ArrayList`

An `ArrayList` is a growable array from the standard library.

For mutable text, use:

```zig
std.ArrayList(u8)
```

Example:

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

pub fn main() !void {
    var gpa = std.heap.GeneralPurposeAllocator(.{}){};
    defer _ = gpa.deinit();

    const allocator = gpa.allocator();

    var list = std.ArrayList(u8).init(allocator);
    defer list.deinit();

    try list.appendSlice("Hello");
    try list.append(' ');
    try list.appendSlice("Zig");

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

Output:

```text
Hello Zig
```

The important field is:

```zig
list.items
```

It is a slice of the currently used bytes.

The `ArrayList` owns its memory. When it needs more space, it can allocate a larger buffer and copy the old bytes into it.

Because it owns memory, you must call:

```zig
defer list.deinit();
```

This frees the memory when the list is no longer needed.

#### Appending Text

Use `appendSlice` to append a string or byte slice:

```zig
try list.appendSlice("hello");
```

Use `append` to append one byte:

```zig
try list.append('!');
```

Complete example:

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

pub fn main() !void {
    var gpa = std.heap.GeneralPurposeAllocator(.{}){};
    defer _ = gpa.deinit();

    const allocator = gpa.allocator();

    var text = std.ArrayList(u8).init(allocator);
    defer text.deinit();

    try text.appendSlice("file");
    try text.appendSlice(".");
    try text.appendSlice("zig");

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

Output:

```text
file.zig
```

This is a common Zig pattern for building strings dynamically.

#### Formatting into an ArrayList

You can format text into an `ArrayList`.

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

pub fn main() !void {
    var gpa = std.heap.GeneralPurposeAllocator(.{}){};
    defer _ = gpa.deinit();

    const allocator = gpa.allocator();

    var text = std.ArrayList(u8).init(allocator);
    defer text.deinit();

    try text.writer().print("x = {}, y = {}", .{ 10, 20 });

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

Output:

```text
x = 10, y = 20
```

The `writer()` method gives a writer interface that appends formatted output to the list.

This is useful for logs, generated code, file contents, HTTP responses, and command output.

#### Taking Ownership of the Built Text

Sometimes you want to return the built text from a function.

You can use `toOwnedSlice`.

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

fn makeGreeting(allocator: std.mem.Allocator, name: []const u8) ![]u8 {
    var text = std.ArrayList(u8).init(allocator);
    errdefer text.deinit();

    try text.writer().print("Hello, {s}", .{name});

    return try text.toOwnedSlice();
}

pub fn main() !void {
    var gpa = std.heap.GeneralPurposeAllocator(.{}){};
    defer _ = gpa.deinit();

    const allocator = gpa.allocator();

    const greeting = try makeGreeting(allocator, "Zig");
    defer allocator.free(greeting);

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

Output:

```text
Hello, Zig
```

`toOwnedSlice` transfers ownership of the list’s memory to the caller.

After that, the caller must free the returned slice:

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

Notice the use of:

```zig
errdefer text.deinit();
```

If an error happens before ownership is transferred, the list is cleaned up.

#### Mutable Text Does Not Mean Owned Text

This distinction matters:

```zig
[]u8
```

means mutable bytes.

It does not automatically mean owned bytes.

A mutable slice may point into:

```text
a stack array
a heap allocation
an ArrayList's internal buffer
a memory-mapped file
caller-owned memory
```

The type tells you that you can write through the slice. It does not tell you who must free the memory.

Ownership is part of the API design.

#### Common Mistake: Mutating a String Literal

This is wrong:

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

The variable `text` is mutable, but the bytes of the string literal are not.

`var` lets you assign a new value to the variable. It does not make read-only memory writable.

Use a byte array:

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

Or copy the string into mutable memory.

#### Common Mistake: Printing the Whole Buffer

This is wrong when the buffer is larger than the text:

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

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

Print the returned slice:

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

The returned slice says which part of the buffer contains valid text.

#### Common Mistake: Keeping `list.items` After the List Changes

`ArrayList` can reallocate when it grows.

This means a slice pointing to `list.items` may become invalid after more appends.

Risky:

```zig
const before = list.items;

try list.appendSlice("more text");

// before may no longer point to valid memory
```

After an append, use `list.items` again instead of keeping an old slice for too long.

This is an important lifetime rule. A growable list may move its storage.

#### Complete Example

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

fn replaceSpaces(text: []u8) void {
    for (text) |*byte| {
        if (byte.* == ' ') {
            byte.* = '_';
        }
    }
}

fn makePath(allocator: std.mem.Allocator, name: []const u8) ![]u8 {
    var path = std.ArrayList(u8).init(allocator);
    errdefer path.deinit();

    try path.appendSlice("/tmp/");
    try path.appendSlice(name);
    try path.appendSlice(".txt");

    replaceSpaces(path.items);

    return try path.toOwnedSlice();
}

pub fn main() !void {
    var gpa = std.heap.GeneralPurposeAllocator(.{}){};
    defer _ = gpa.deinit();

    const allocator = gpa.allocator();

    const path = try makePath(allocator, "hello zig");
    defer allocator.free(path);

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

Output:

```text
/tmp/hello_zig.txt
```

This example shows three ideas:

```text
ArrayList builds text that can grow
[]u8 lets a function mutate text in place
toOwnedSlice transfers ownership to the caller
```

#### Summary

Mutable strings in Zig are mutable byte sequences.

Use a byte array when the size is fixed:

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

Use a mutable slice when a function should modify caller-provided text:

```zig
fn edit(text: []u8) void
```

Use `std.ArrayList(u8)` when text needs to grow.

Use `std.fmt.bufPrint` to write into an existing buffer. Use `std.fmt.allocPrint` or `ArrayList` when you need allocated text.

The main rule is simple: Zig treats text as bytes, and memory ownership stays visible.

