#comptime conditional existence of variables

1 messages · Page 1 of 1 (latest)

hybrid jetty
#

Can I make the existence of a variable foo dependent on the value of a comptime variable want_foo? As I would in C:

#if WANT_FOO
int foo;
#endif
deft cave
#

var foo: if (want_foo) u32 else void = undefined;

hybrid jetty
#

not as nice conceptually, but that would have the same effect, right?

deft cave
#

If you want it to actually not exist you can use rs usingnamespace if (want_foo) struct { var foo: int = undefined; } else struct {};

#

But that means you need to access it via namespace

#

Alternatively you can just define it unconditionally and never use it :)

hybrid jetty
#

also, sorry to ask, but I'm slightly confused on how to make a variable comptime in the first place, would this work?

var want_foo: bool = comptime if(builtin.mode == .Debug) true else false;
deft cave
#

As in you want the variable to be mutable at comptime or you want it to have an initial value defined at comptime?

hybrid jetty
#

I want the variable to exist only at comptime

#

as through it were a comptime function argument

deft cave
#

Well, a comptime argument is a const, not a var

#

So const want_foo = builtin.mode == .Debug;

#

(it's comptime-only because its value is comptime-known)

hybrid jetty
deft cave
#

If you want it to be mutable at comptime, comptime var want_foo = builtin.mode == Debug;

hybrid jetty
#

hmm that was giving me an error with zls earlier

deft cave
#

What error exactly?

hybrid jetty
#

hold on

#

expected block or field, found var

deft cave
#

Ah, it can't be a global

hybrid jetty
#

...

deft cave
#

global mutability is forbidden at comptime because it would make execution order observable

hybrid jetty
#

How am I supposed to go about doing this? I just want a user-modifiable comptime variable that detemines whether foo is available

deft cave
#

Does std.meta.globalOption do what you want?

hybrid jetty
#

not sure what it does since it's not doumented yet

#

hold on

deft cave
#

If you use std.meta.globalOption("foo", bool) in your code, it allows a user to define pub const foo = true; in their main file

#

Which will then be returned from that globalOption call

#

If no such option is defined it'll return null so you can use orelse to set a default

#

(or throw a compile error if it's required)

hybrid jetty
#

sounds like a hack but should work

#

thanks

deft cave
#

It's a bit weird coming from other languages but it's a common pattern in zig :)

#

eg. it's used in std to set the IO mode

hybrid jetty
#

why go through all this effort just to be able to sue the common want_thing idiom :/

#

but fair, thank you for the help!

deft cave
#

Do you have any suggestions for a better alternative?

hybrid jetty
deft cave
#

You can't allow that and still have reproducible builds

#

Because if you have two comptime blocks they could execute in any order

#

And you can't define an order for it without severely limiting parallelism opportunities

hybrid jetty
deft cave
#

(in fact, allowing global mutability at all would already limit parallelism)

hybrid jetty
#

(but tbf we're already doing single-TU builds, we don't seem to care about parallelism )

deft cave
#

single-TU doesn't mean single threaded

hybrid jetty
#

sure but that very very heavily limits parallelism

deft cave
#

Not at all

#

Maybe it does within LLVM, but the zig frontend is very much geared for parallelism

hybrid jetty
#

if I have like 5 executables I want to build with lots of shared code and i build them with build-exe, it will take a ton of time instead of compiling a single source file and linking with previous objects, but this is kind of unavoidable with zig itself so

#

idk

#

just different programming paradigms ig

deft cave
#

And if LLVM is limited there (I'm not sure, haven't tested), it's probably because it's geared to building C and C++, where multiple-TU builds are extremely common so there's no point in multithreading it

#

Not because of some inherent serial property of single-TU builds :)

hybrid jetty
deft cave
#

Not really, you can easily parallelize the optimization of independent functions

hybrid jetty
#

but this is a different beast right

deft cave
#

And multi-TU builds still have a serial LTO step

#

(if they even do LTO, which many don't)

hybrid jetty
#

that is true

#

it's still not as bad as single-TU builds because of shared libraries though

deft cave
#

Ah sorry I missed the bit about multiple executables - Zig will eventually cache most (all?) of the frontend stuff there, and possibly some of the backend stuff, not sure

#

So it should be about as fast as multi-TU builds, if not faster

hybrid jetty
deft cave
#

Also once selfhosted backends are done and binary patching lands in master, Zig's debug build speed will be way faster than any other compiler in existence, multi-TU or not :)

hybrid jetty
#

binary patching seems

#

very cursed

#

how does that even work

deft cave
#

Everything goes through the GOT

#

So you can just add more functions on the end and patch the GOT

hybrid jetty
#

smart

deft cave
#

At the cost of a tiny bit of runtime perf (which we don't care about anyway because yknow, debug build)

hybrid jetty
#

by the way, can I turn off the status output specifically without turning off errors? this seems to screw up my output a bit

deft cave
#

Does adding 2>&1 help at all?

#

Or 2>&1 | cat

hybrid jetty
#

that would just redirect stderr to stdout

#

and these messages are printed to stderr as well as errors

#

oh, I get what you mean

#

it uses isatty

deft cave
#

Yeah

hybrid jetty
#

nope

#

:c

deft cave
#

rip :(

#

idk then, there doesn't seem to be a --quiet option or anything

hybrid jetty
#

meh, thanks

deft cave
#

Might be worth an issue :)

hybrid jetty
#

unfortunately I'm going through some issues with github rn so that probably won't be me

#

And, sorry to have so many, but another question, is there a line-buffered BufferedWriter?

deft cave
#

Not that I'm aware of

#

You could write one very easily though

hybrid jetty
#

yeah

#

thank you

hybrid jetty
#

Hey, I hate to ask again, but now I have a struct using that optional variable with usingnamespace and functions using that conditionally:

usingnamespace if (getWantChecking()) struct {
        var initialized: bool = false;
    } else struct {};

    pub fn init(allocator: std.mem.Allocator) void {
        if(comptime getWantChecking())
            initialized = true;
        a = allocator;
    }

but the compiler complains about initialized being undeclared, even though it's only ever used when it's actually declared. this is probably a misunderstanding on my part, could you help me fix it? (also please don't complain about the globals, it's for a C interface)

deft cave
#

usingnamespace doesn't modify the current namespace, so you have to access it externally

#

You can use @This().initialized, or just put it in a struct instead of using usingnamespace

hybrid jetty
#

wait, wot

deft cave
#

But honestly there's nothing wrong with just having the variable exist always; if it's never used it won't end up in the binary

hybrid jetty
#

I do prefer the nicest approach though :)

#

in any case, thank you, I'll just put it in its own struct...

humble quiver
hybrid jetty
#

huh? how does that help here?

#

I don't want the build to fail whenever the user doesn't request checking

deft cave
#

That's actually a better solution yeah

hybrid jetty
#

or am I missing something here

deft cave
#

It'll only fail if you reference the variable

hybrid jetty
#

that's such a hack

deft cave
#

It's basically an assert

#

"assert this variable is never used"

humble quiver
# hybrid jetty that's such a hack

it's not a hack though, that's just a way things are done in zig. When a function is deprecated, it's replaced with const name = @compileError("DEPRECATED: ...");

#

in this situation, you functionally get the same behaviour, in that you get a compilation error if you try to reference it when it's been disabled, just without all the extra code using the namespace solution

hybrid jetty
#

k

visual hawk
# hybrid jetty that's such a hack

I think it would be interesting to consider why you think this, only, since

  1. ifs with comptime conditions only compile the true branch, setting a constant and then branching on it is quite simple and achieves what you want.
    [This is how Zig achieves conditional compilation.]
  2. File-scope declarations are only compiled if they are referenced.
    [This is also part of how Zig achieves conditional compilation.]
  3. The usingnamespace trick is quite a bit harder to read and understand.
    [More on this below.]

It may seem like there's a lot of moving parts here that are being used to make this mechanism work -- and that's kinda true, there's several moving parts.
The point though is that Zig tries to achieve everything with as little as possible, rather than adding thousands of little features to cover everything that's wanted.

As a result, these 'moving parts' are ubiquitous, and therefore familiar when reading other people's code.

// neither are present in binary if not referenced, and former is a constant
// so can be inlined into its usage sites trivially.
const want_check = std.meta.globalOption("want_check") orelse false;
var check_data = ...;

fn foo(input: u32) !u32 {
    // compiled away when 'want_check' is false
    if (want_check) {
        try check_data.validate(input);
    }
    
    return input * 2 + 1; // do something normal here
}

In this way, this reads very similarly to #defines as used in C, and isn't that hard to understand; you're merely stating where the info comes from, and then branching on it.
The fact that there's 'moving parts' here is essentially secondary.