# zig-string gives Zig a growable UTF-8 string with an explicit allocator

> zig-string is a small Zig library for UTF-8 strings, built around an arena or general purpose allocator the caller supplies, with around forty named methods covering concatenation, searching, splitting and case conversion. It is one source file, MIT licensed, and its author describes it as a learning exercise rather than a production dependency.

**JakubSzark/zig-string** — A String Library made for Zig

- Repository: https://github.com/JakubSzark/zig-string
- Stars: 580 · Forks: 38
- Language: Zig
- License: MIT
- Published: 2026-10-22 · Updated: 2026-10-22 · Language: en
- Canonical page: https://hysenlabs.com/projects/jakubszark-zig-string

## One file, one type, and an allocator you own

zig-string is a UTF-8 compatible string library for the Zig programming language, and its structure is minimal. The repository holds zig-string.zig as the implementation, zig-string-tests.zig for the tests, build.zig and build.zig.zon for the build, plus the README, the licence and the CI configuration. The design decision that shapes everything else is that the string does not own an allocator implicitly. A String is created with an allocator you pass in, and the README example uses an arena allocator over the page allocator, with a deferred deinit on both the arena and the string itself. The type is a growable buffer with a length, and the working feature list gives you the whole API surface: allocate sets the internal buffer size, capacity returns it, init creates a String with an Allocator, init_with_contents creates one with specified contents, and deinit de-allocates it. Because the allocation strategy is the caller's, a long-lived application can use one arena for a whole request scope, while a program that builds many short strings can pass a general purpose allocator and free them individually.

## The method names read like JavaScript, not like Zig

The feature table is the most useful part of the documentation because it is the entire public API, and the naming convention is worth calling out before you adopt it. The methods use camelCase names borrowed from JavaScript string methods, with charAt returning the character at an index, concat appending a string literal to the end, find and rfind locating the first and last appearance of a literal, includesLiteral and includesString answering the two variants of the containment question, and count returning the number of occurrences. The editing operations mirror the same set, with insert inserting a character at an index, remove removing a character at an index, removeRange removing a range of characters, pop removing the last character, clear emptying the string, clone copying to a new one, and reverse reversing all the characters. The transformations include toLowercase and toUppercase, both documented as converting ASCII characters only, and toCapitalized converting the first ASCII character of each word to uppercase, which is the same ASCII caveat, and trim, trimStart and trimEnd removing a whitelist from both ends, the start or the end. The author flags the risk himself, writing that if there are issues with complexity readers should open an issue and that he is no expert when it comes to complexity.

## Splitting, trimming and the string-to-slice conversions

The splitting family is where a string library either earns its keep or falls apart, and zig-string offers five distinct shapes of it. split returns a slice based on delimiters and an index, splitAll returns a slice of slices based on delimiters, splitToString returns a String based on delimiters and an index, and splitAllToStrings returns a slice of Strings based on delimiters. There is also lines, which returns a slice of Strings split by newlines. The distinction between returning slices and returning owned Strings is the interesting part, because the former borrows from the original buffer and the latter allocates, and a reader who does not track that will introduce a lifetime bug. Two conversions let the string leave its own representation, with str returning the String as a slice and toOwned creating an owned slice of the String, so a caller can move to a plain Zig slice when the String type is in the way. The reverse direction exists too, with setStr setting the buffer value from a string literal.

## Building it into a Zig project with build.zig.zon

Installation follows the standard Zig dependency pattern, with the package declared in build.zig.zon.

```zig
.dependencies = .{
    .string = .{
        .url = "https://github.com/JakubSzark/zig-string/archive/refs/heads/master.tar.gz",
        //the correct hash will be suggested by zig
    }
}
```

The commented note inside the snippet is worth reading, because it explains that the correct hash is suggested by zig itself, so you point at a branch tarball and let the toolchain fill in the integrity value. The dependency is then wired into build.zig.

```zig
    const string = b.dependency("string", .{
        .target = target,
        .optimize = optimize,
    });
    exe.root_module.addImport("string", string.module("string"));
```

Note that the package name on the dependency line is string, which differs from the repository name, and that the module is registered under the same name. Importing it in your own code is then a single line.

```zig
const String = @import("string").String;
```

## Using the type: an arena, a String and a few operations

The README example shows the intended usage pattern from beginning to end, and it is short enough to take in as a whole. You import the standard library and the library under test, create an arena allocator over the page allocator with a deferred deinit, create the String with the arena's allocator and a deferred deinit of its own, and then call the methods you need. The example appends a string literal with concat, removes the last character with pop, appends again, and finishes by asserting the result with cmp against a literal.

```zig
const std = @import("std");
const String = @import("./zig-string.zig").String;
// ...

// Use your favorite allocator
var arena = std.heap.ArenaAllocator.init(std.heap.page_allocator);
defer arena.deinit();

// Create your String
var myString = String.init(arena.allocator());
defer myString.deinit();
```

Three things in that setup are worth pointing out. The concat call in the fuller example returns an error and is handled with try, so the API treats a failed allocation as an error the caller must deal with rather than something to assert away. The pop call is assigned to an underscore, which suggests it returns a value the example does not use, in this case the removed character. And the comparison at the end of the example is against a literal rather than against another String, which is the cmp function documented as comparing to a string literal.

## Contributing, iterating and the project that is still a learning exercise

The contribution flow is a six step list in the README: fork, clone, add features using Zig FMT, make a test, pull request, and then success. The presence of a make a test step in that sequence says more about the project than any description does, because it is the same convention the Zig community uses for its own standard library, where a change without a test is not finished. The test file in the repository root, zig-string-tests.zig, is the counterpart to that convention, and the CI badge points at a GitHub Actions workflow that runs it. The author's stated motivation is equally plain. He made this for the sole purpose to further his experience and understanding of zig, and adds that it may be useful for some people who need it, including himself, with future projects, and that the project is open for people to add to and improve, with the issues used to view requested features. That framing is honest and useful for a reader. It tells you the code is likely readable and well intentioned, and it tells you the API will keep changing as the author learns, so a project depending on it should vendor it rather than follow the master branch tarball, which is exactly what the installation snippet points at.

## Conclusion

Adopt zig-string if you are learning Zig and want a worked example of allocator-driven string handling, since the code is one readable file with a test file next to it. Skip it if you need a battle-tested string library for production, because the author states the project exists to further his own experience and understanding of Zig, and the method names are JavaScript shaped rather than Zig shaped. Before you depend on it, check the Zig version it builds against and read the complexity caveat, since the author asks people to open an issue when they hit complexity problems.

## FAQ

### What is zig-string and how is it installed?

It is a UTF-8 compatible string library for the Zig language, delivered as a dependency. You add a .string entry to .dependencies in build.zig.zon pointing at the master branch tarball, let zig suggest the hash, then register the module in build.zig with b.dependency and import it with const String = @import("string").String.

### How does zig-string handle memory allocation?

The caller supplies the allocator. The README example creates an arena allocator over the page allocator, passes arena.allocator() to String.init, and defers deinit on both the arena and the string, so allocation strategy stays with the application rather than the library.

### What operations does the zig-string API provide?

Around forty named methods covering allocation, capacity, character access, clearing, cloning, comparing, concatenation, finding first and last occurrences, counting, containment checks, insertion, emptiness, length, iteration, popping, removal by index and by range, repeating, reversing, four splitting variants plus a lines helper, conversions to a slice or an owned slice, ASCII case conversion, trimming, truncation, a writer, startsWith and endsWith checks, and replacement.

### Is zig-string production ready?

The author describes the project as made for the sole purpose of furthering his own experience and understanding of Zig, and asks readers to open an issue if they hit problems with complexity, noting he is no expert there. The project is open to contributions, with a fork, clone, feature, test and pull request flow.

## Sources

- [Issues](https://github.com/JakubSzark/zig-string/issues)
- [JakubSzark/zig-string on GitHub](https://github.com/JakubSzark/zig-string)
- [License: MIT](https://github.com/JakubSzark/zig-string/blob/master/LICENSE)
- [README](https://github.com/JakubSzark/zig-string/blob/master/README.md)

---

Hysen Labs editorial analysis, written from the project's own repository and release notes. Cite the canonical page: https://hysenlabs.com/projects/jakubszark-zig-string
