String Operations at comptime: convert @embedFile(windows_file) to unix_file

The general answer is:

  • Create an array with the required size or an upper bound of the size
  • Populate the array
  • Assign the populated array to a const, and return a reference to that (see Comptime-Mutable Memory Changes)

Here’s a simple version that just strips \r characters from a comptime-known string:

const std = @import("std");

fn crlfToLf(comptime str: []const u8) []const u8 {
    comptime {
        var buf: [str.len]u8 = undefined;
        var i: usize = 0;
        for (str) |c| {
            if (c == '\r') continue;
            buf[i] = c;
            i += 1;
        }
        // since `i` is comptime-known,
        // `buf[0..i]` results in a pointer-to-array,
        // so we dereference that to get a copy
        // of the array with the correct length
        const final = buf[0..i].*;
        return &final;
    }
}

const some_data = "abc\r\ndef\r\n";
const some_data_lf = crlfToLf(some_data);

pub fn main() !void {
    std.debug.print("before: {}\n", .{std.fmt.fmtSliceEscapeLower(some_data)});
    std.debug.print("after: {}\n", .{std.fmt.fmtSliceEscapeLower(some_data_lf)});
}

However, note that the above implementation loses the null-terminator and the “pointer-to-array-ness” of the type of string literals (and the type returned by @embedFile). The null-terminator is easy enough to add back in if you want, but if you also want to keep the “pointer-to-array-ness”, then one way to go is to also calculate the final length upfront and then use the calculated length in the return type. An example of that can be found in the standard library:

3 Likes