Zig NEWS

Cover image for Cool Zig Patterns - Paths in Build Scripts
Felix "xq" Queißner
Felix "xq" Queißner

Posted on

Cool Zig Patterns - Paths in Build Scripts

I'm using a pattern to have paths in build files that are relative to the build file instead of the build root folder.

This pattern looked like this:

/// DEPRECATED, DO NOT USE
fn sdkRoot() []const u8 {
    return std.fs.path.dirname(@src().file) orelse ".";
}

...
{
  var path = sdkRoot() ++ "/src/main.zig"; // get a nice path
}
...
Enter fullscreen mode Exit fullscreen mode

The problem with this is that with the new stage2 compiler, the ++ operator now has new semantics that make the operation above a runtime operation, and thus path a pointer-to-temporary, not a pointer-to-comptime-memory.

But there's a nice alternative one can use:

fn sdkPath(comptime suffix: []const u8) []const u8 {
    if (suffix[0] != '/') @compileError("relToPath requires an absolute path!");
    return comptime blk: {
        const root_dir = std.fs.path.dirname(@src().file) orelse ".";
        break :blk root_dir ++ suffix;
    };
}

...
{
  var path = sdkPath("/src/main.zig"); // get a nice path
}
...
Enter fullscreen mode Exit fullscreen mode

This way, it can trivially be a comptime return value, removing the need for a lot of comptime sdkRoot() calls.

Top comments (11)

Collapse
 
eliax1996 profile image
Elia Migliore • Edited

I'm new to the zig language so my knowledge isn't huge. But I see a lot the pattern of returning something at comptime using the break keyword + a label + a return object.

Wouldn't be in this case more readable writing something like:

fn sdkPath(comptime suffix: []const u8) []const u8 {
    if (suffix[0] != '/') @compileError("relToPath requires an absolute path!");
    return comptime {
        const root_dir = std.fs.path.dirname(@src().file) orelse ".";
        return root_dir ++ suffix;
    };
}
Enter fullscreen mode Exit fullscreen mode

Edit: TIL, Isn't duable, was working only because the return was referring to the function. The only way to return a value from a block is by labeling it and using the break syntax, doc. I think that the only way to get rid of the break labeled if we want is to do something like that:

fn concatenate(comptime suffix: []const u8) []const u8 {
    const root_dir = std.fs.path.dirname(@src().file) orelse ".";
    return root_dir ++ suffix;
}

fn sdkPath(comptime suffix: []const u8) []const u8 {
    if (suffix[0] != '/') @compileError("relToPath requires an absolute path!");
    const my_val = comptime concatenate(suffix);
    return my_val;
}
Enter fullscreen mode Exit fullscreen mode
Collapse
 
xq profile image
Felix "xq" Queißner

Yeah, it's not the only way, but the one i came up with. I guess we can return from comptime blocks, but i honestly never tried to do so, because it kinda means i'm returning at comptime.

Your second solution requires a second function to be put into the scope which is something i want to avoid, as sdkPath isn't portable between files.

Collapse
 
eliax1996 profile image
Elia Migliore

Wdym with “isn’t portable between files“?

Thread Thread
 
xq profile image
Felix "xq" Queißner

It will always return paths to the file the function is contained in. Calling it from another file will then return a path relativ to the imported file, wherever it may be

Thread Thread
 
eliax1996 profile image
Elia Migliore

Ok that’s clear, but I do not understand why creating two functions go against that intent. If they aren’t public what’s the difference between one or two?

I think is only a matter of style (and yeah, I also prefer the break, is more concise for that specific case). Please let me know if I’m wrong and is also semantically different.

Sorry for the questions I’m just trying to fill the gaps in my zig knowledge

Thread Thread
 
xq profile image
Felix "xq" Queißner

Ok that’s clear, but I do not understand why creating two functions go against that intent. If they aren’t public what’s the difference between one or two?

You will have another symbol in your scope that isn't general purpose, so you pollute your namespace. That's all. Keep symbol count low unless you really need the stuff twice is what i'm trying to do

Thread Thread
 
eliax1996 profile image
Elia Migliore

Super cool! Thanks for the reply. But by not being public couldn’t the compiler inline the function and avoid producing a symbol? By the end the compiler should know that the only point where the function is called is inside that source (if you place the pub keyword that’s a different story)
Isn’t it?

Thread Thread
 
xq profile image
Felix "xq" Queißner

I'm talking about a semantic symbol/name in a scope, not what's emitted in the binary. You cannot call something else in that file concatenate for example, even if you might need it

Thread Thread
 
eliax1996 profile image
Elia Migliore

Got it! Thank you a lot for your kind replies

Collapse
 
nairou profile image
Nairou

Very useful! Since you're using ++ in both examples, can you explain how they differ? Does the stage2 compiler require ++ use within an explicit comptime block for the result to be comptime?

Collapse
 
xq profile image
Felix "xq" Queißner

++ in the first example returns a pointer-to-temporary, whereas the second example returns a comptime known value that is guaranteed to put into the .rodata section and has a lifetime of the whole program life time