String handling in comptime functions

Hi!

I’m drawing blanks trying to write a comptime functions to return a zero-terminated string of a typename. For example, for a string "ldtk.Scene" it’d return just "Scene". But I don’t know how to do this. Here’s what I tried:

pub fn getMetaname(comptime T: type) [:0]const u8 {
    const fullTypeName = @typeName(T);
    var it = std.mem.splitBackwardsAny(u8, fullTypeName, ".");
    const last = it.first();
    var buffer: [128]u8 = undefined;
    @memcpy(buffer[0..last.len], last);
    buffer[last.len] = 0;
    return buffer[0..last.len :0];
}

pub const metaname = getMetaname(LdtkScene);

But I get the below error when I use this function:

src\lua_ldtk_bindings.zig:11:48: error: global variable contains reference to comptime var
    pub const metaname = getMetaname(LdtkScene);

Which is true, of course, but I don’t know how to avoid this.

Comptime-Mutable Memory Changes is a very good and thorough summary of what is and isn’t allowed to become runtime-known, but the relevant part is this:

So the easiest fix is probably something like this:

 pub fn getMetaname(comptime T: type) [:0]const u8 {
     const fullTypeName = @typeName(T);
     var it = std.mem.splitBackwardsAny(u8, fullTypeName, ".");
     const last = it.first();
     var buffer: [128]u8 = undefined;
     @memcpy(buffer[0..last.len], last);
     buffer[last.len] = 0;
-    return buffer[0..last.len :0];
+    const copy = buffer[0..last.len :0].*;
+    return ©
 }

 pub const metaname = getMetaname(LdtkScene);
5 Likes

Also note that in this particular case, you don’t need to make a copy, since results of @typeName are already null-terminated, and since you take the last characters, they will still be null-terminated. So, something like this should also work:

return last[0..last.len :0];
4 Likes

Very cool, thanks for the help! I did find that article but I guess I didn’t fully internalize it. I did try something similar to the above copy trick, but couldn’t get it to work.

As for my original problem, I did realize I can write a much simpler version that doesn’t have this problem:

pub fn getMetaname(comptime T: type) [:0]const u8 {
    const fullTypeName = @typeName(T);
    if (std.mem.lastIndexOf(u8, fullTypeName, ".")) |idx| {
        return fullTypeName[idx + 1 ..];
    }
    return fullTypeName;
}
3 Likes

One related useful trick worth remembering is that concatenating a []const u8 with ++ "" will return a 0-terminated result:

fn zeroTerminate(comptime name: []const u8) [:0]const u8 {
    return name ++ "";
}

test {
    const a: []const u8 = &.{ 'd', 'o', 'g' };
    const b: [:0]const u8 = zeroTerminate(a);
    try @import("std").testing.expectEqualSentinel(u8, 0, "dog", b);
}

This can be especially useful if you’re constructing types from strings via @Type, since things like field names require null terminators.

4 Likes

Oh wait @castholm, this doesn’t seem to build:

src\lua_common.zig:27:40: error: index syntax required for slice type '[:0]u8'
    const copy = buffer[0..last.len :0].*;
                 ~~~~~~~~~~~~~~~~~~~~~~^~

Dropping the .* part from the copy slice, I get a build error but the error is not shown:

Build Summary: 10/13 steps succeeded; 1 failed
run transitive failure
└─ run zig2d transitive failure
   └─ zig build-exe zig2d Debug native failure
error: the following build command failed with exit code 1:
C:\Users\janne\dev\zig2d\.zig-cache\o\e15bb8313dbb2af998d110a7f9162efc\build.exe C:\Users\janne\scoop\apps\zig-dev\0.14.0-dev.1820\zig.exe C:\Users\janne\scoop\apps\zig-dev\0.14.0-dev.1820\lib C:\Users\janne\dev\zig2d C:\Users\janne\dev\zig2d\.zig-cache C:\Users\janne\AppData\Local\zig --seed 0x38ebb4af -Z9a358dfc02c21192 run -freference-trace=15

I also tried to extend my other version to add a suffix to the returned string, but somehow I don’t seem to have a good grasp on nailing comptime functions:

pub fn getMetaname(comptime T: type) [:0]const u8 {
    const vec = @import("gfxutil").vec;
    const fullTypeName = @typeName(T);
    if (T == vec.Mat4.T) {
        return "L_Mat4";
    }

    if (std.mem.lastIndexOf(u8, fullTypeName, ".")) |idx| {
        return "L_" ++ fullTypeName[idx + 1 ..];
    }
    return fullTypeName;
}

src\lua_common.zig:28:36: error: unable to resolve comptime value
        return "L_" ++ fullTypeName[idx + 1 ..];
                       ~~~~~~~~~~~~^~~~~~~~~~~~
src\lua_common.zig:28:36: note: slice value being concatenated must be comptime-known

Sorry for spamming my own thread. The last version works, it’s just that it must be called like this in a run-time context:

fn newUserdataWithMetatable(comptime T: type, lua: *Lua) *T {
    const self = lua.newUserdata(ldtk.LdtkScene, 0);
    const meta = comptime lua_common.getMetaname(ldtk.LdtkScene);
    _ = lua.getMetatableRegistry(meta);
    lua.setMetatable(-2);
    return self;
}

I was confused because in other contexts, the comptime keyword is not accepted:

const LdtkScene = struct {
    pub const classname = "LdtkScene";
    pub const metaname = lua_common.getMetaname(ldtk.LdtkScene);
...