More efficient way to substitue values in 'string'

I just discovered bufPrint, and added a string to ‘format’ with a couple of values before passing over to javascript (WASM) (The 2 ints are runtime values.)

const canvasStyle = "position: absolute; top: 0px; left: 0px; width: {d}px; height: {d}px; {s}";

I created a small buffer for use with bufPrint:

var fmtBuffer: [256]u8 = undefined;

But my wasm binary jumps from 9KB to 93KB!

Is there an alternative approach other than writing my own?

Or some string style functions like indexOf(), replace(), regex()? PS I know there is no plans to add strings as a type and I am fully behind that, but a bit of documentation on how to work around that would be good.

zig 0.8.1

Using formatting functions pulls in a whole bunch of things that you would otherwise trim from the compilation (I/O writers, memory/slice operation, …). I don’t think you can avoid this short of rewriting your own stripped-down code for your use case (which is perfectly okay!).

I am loving that the generated wasm binaries are so small, and it seems a shame to bloat it for a single statement. So I have been trying to follow the bufPrint code, and I think I might just have a go at a stripped-down version :slight_smile: Thanks for the explanation and encouragement.

A couple extra bits of info; you can find several functions in std.mem that probably do what you wan in terrms of manipulating strings. indexOf, trim, startsWith, etc. They’re all there.

Also, I don’t know if this applies to WASM, but I have seeen dramatic reductions in binary size by using exe.strip = true in the build.zig file. In one case, the file went from ~700 KiB to ~150 KiB. Note this should be for the final release binary since it takes away the debugging traces that you normally get when an error occurs.

1 Like

Thanks :slight_smile: I ended up with the following functions (below) based upon the zig sources, but constrained by my limited understanding of Zig thus far. The ‘replace’ works, and my code is back under 10KB :slight_smile: but then I thought to explore zig testing support…

Besides being very confused about how to run tests ( I expected it to be along the lines of fmt, i.e. zig fmt src/*.zig would become zig test src/*.zig) but that gives:

zig test src/*.zig
error: found another zig file 'src/js-animator.zig' after root source file 'src/format.zig'

I have no idea what that is telling me, so I ended up having to create a shell script with each file listed, which feels like I missed something :slight_smile:

zig test src/random.zig
zig test src/format.zig

Anyway my tests fail even though the code works, but I suspect it is relating to sentinels. I found myself wanting to add the ‘0’ myself aka C style strings, but from reading the zig sourcs it feels like these are automatic… So I am confused about what type my replace should be returning, and ended up just modifying the passed in result buffer. But the ‘0’ poke feels very wrong! and a print on the result buffer shows a whole bunch of ‘?’ chars in the unused part of the buffer, so clearly that is not the way to indicate the new end of buffer. I cant set the slice len param myself, so how does the caller know where the result buffer now ‘ends’?

var itoaBuf = [_:0]u8{0} ** 6;

pub fn itoa(i: u16) [:0]u8 {
    var index: usize = itoaBuf.len;
    var a = i;
    while (true) {
        const digit = a % 10;
        index -= 1;
        itoaBuf[index] = @intCast(u8, digit) + '0';
        a /= 10;
        if (a == 0) break;
    return itoaBuf[index..];

pub fn eql(comptime T: type, a: []const T, b: []const T) bool {
    if (a.len != b.len) return false;
    if (a.ptr == b.ptr) return true;
    for (a) |item, index| {
        if (b[index] != item) return false;
    return true;

pub fn indexOf(haystack: []const u8, needle: []const u8) usize {
    var i: usize = 0;
    const end = haystack.len - needle.len;
    while (i <= end) : (i += 1) {
        if (eql(u8, haystack[i .. i + needle.len], needle)) return i;
    return 0;

pub fn replace(result: []u8, haystack: []const u8, needle: []const u8, replacement: []const u8) void { //[:0]u8
    var nPos = indexOf(haystack, needle);
    cpy(result, haystack[0..nPos]);
    cpy(result[nPos..], replacement[0..]);
    cpy(result[nPos + replacement.len ..], haystack[nPos + needle.len ..]);
    var newLen = nPos + replacement.len + (haystack[nPos + needle.len ..].len);
    result[newLen] = 0;
    //return result[0..newLen :0];

pub fn cpy(dest: []u8, source: []const u8) void {
    for (source) |s, i| {
        dest[i] = s;

[edit] adding the test:

test "replace" {
    var replaceResult: [100:0]u8 = undefined;
    var haystack = "position: absolute; top: 0px; left: 0px; width: {WIDTH}px; height: {HEIGHT}px; {USER}";
    replace(replaceResult[0..], haystack[0..], "{WIDTH}", itoa(42));
    replace(replaceResult[0..], replaceResult[0..], "{HEIGHT}", itoa(69));
    replace(replaceResult[0..], replaceResult[0..], "{USER}", "smith");
    print("\n\n>3.result: {s}\n", .{replaceResult});
    try testing.expect(eql(u8, replaceResult[0..], "position: absolute; top: 0px; left: 0px; width: 42px; height: 69px; smith"));


>3.result: position: absolute; top: 0px; left: 0px; width: 42px; height: 69px; smith????????????????????????