Reformatting comptime string at comptime

I am simply trying to make a helper function to turn multi-line strings from separating with \n to \r\n for an http header to be formatted properly.
Here is the function:

fn formatHeaderTemplate(comptime template: []const u8) []const u8 {
    comptime {
        const size = std.mem.replacementSize(u8, template, "\n", "\r\n");
        var new_template: [size]u8 = undefined;
        _ = std.mem.replace(u8, template, "\n", "\r\n", new_template[0..size]);
        return new_template[0..size];
    }
}

Here is the test:

test "Raw representation of multi-line-string" {
    const multi =
        \\I am on
        \\multiple lines!!
    ;
    const formatted_multi = comptime formatHeaderTemplate(multi);

    const formatted =
        "I am on\r\n" ++
        "multiple lines!!";
    //@compileLog(formatted_multi);
    //@compileLog(formatted);
    try std.testing.expect(std.mem.eql(u8, formatted_multi, formatted));
}

Here is my strange error:

$ zig test src/root.zig
src/root.zig:178:44: error: runtime value contains reference to comptime var
    try std.testing.expect(std.mem.eql(u8, formatted_multi, formatted));
                                           ^~~~~~~~~~~~~~~
src/root.zig:178:44: note: comptime var pointers are not available at runtime
src/root.zig:183:38: note: 'runtime_value.ptr' points to comptime var declared here
        var new_template: [size]u8 = undefined;
                                     ^~~~~~~~~

I understand that I am basically doing the same thing as returning a reference to a stack allocated array, but I’m not familiar with another way to do comptime manipulation of other comptime values.
I assume that I can use the stdlib functions like those in mem at comptime as well.
I just want to take the comptime string value of my template, replace its endings with different endings, and then leave it for my runtime code to use

Ok I realized this is the blk: {
var buffer …
break :blk buffer
}
Pattern
Here is a working rewrite:

fn formatHeaderTemplate(comptime template: []const u8) []const u8 {
    const finished = blk: {
        const size = std.mem.replacementSize(u8, template, "\n", "\r\n");
        var buffer: [size]u8 = undefined;
        _ = std.mem.replace(u8, template, "\n", "\r\n", &buffer);
        break :blk buffer;
    };
    return finished[0..finished.len];
}
1 Like

Copy the array to a const and then use that.

fn formatHeaderTemplate(comptime template: []const u8) []const u8 {
    comptime {
        const size = std.mem.replacementSize(u8, template, "\n", "\r\n");
        var new_template: [size]u8 = undefined;
        _ = std.mem.replace(u8, template, "\n", "\r\n", new_template[0..size]);
        const result = new_template;
        return &result;
    }
}

That way you have a reference to a const and don’t get that error.

3 Likes

Do this if you want the result to look exactly like a string literal:

const std = @import("std");

fn formatHeaderTemplate(comptime template: []const u8) rv_type: {
    const size = std.mem.replacementSize(u8, template, "\n", "\r\n");
    break :rv_type *const [size:0]u8;
} {
    const new_template = comptime init: {
        const size = std.mem.replacementSize(u8, template, "\n", "\r\n");
        var buffer: [size + 1]u8 = undefined;
        _ = std.mem.replace(u8, template, "\n", "\r\n", &buffer);
        buffer[size] = 0;
        break :init buffer;
    };
    return @ptrCast(&new_template);
}

test "Raw representation of multi-line-string" {
    const multi =
        \\I am on
        \\multiple lines!!
    ;
    const formatted_multi = comptime formatHeaderTemplate(multi);

    const formatted =
        "I am on\r\n" ++
        "multiple lines!!";
    try std.testing.expectEqual(formatted, formatted_multi);
}
4 Likes