The document could be more clear on when to and not to use `comptime`

As an example, comptime could be used on expressions to enforce comptime calculation. But constant variables can also be implicitly comptime if their initialization is comptime-computable.

I think we could make a list on when an expression is implicitly comptime computed. In this way, we can know when there is no need to add a comptime keyword. AFAIK, you can add however many comptime as you wanted…

if (comptime comptime comptime hasTrait(...)) doSomething();

Maybe it adds more value to add a concrete example. Here is a function from std.meta.trait:

pub fn hasFn(comptime name: []const u8) TraitFn {
    const Closure = struct {
        pub fn trait(comptime T: type) bool {
            if (!comptime isContainer(T)) return false;
            if (!comptime @hasDecl(T, name)) return false;
            const DeclType = @TypeOf(@field(T, name));
            return @typeInfo(DeclType) == .Fn;
        }
    };
    return Closure.trait;
}

test "hasFn" {
    const TestStruct = struct {
        pub fn useless() void {}
    };

    try testing.expect(hasFn("useless")(TestStruct));
    try testing.expect(!hasFn("append")(TestStruct));
    try testing.expect(!hasFn("useless")(u8));
}

As you can see, hasFn is comptime, and there is no need to add a comptime there.

Hello @VoilaNeighbor, welcome to the forum!

I like your question because it forces us to think about what is helpful as an implicit contract and what should be specified.

In general, const has the qualification that comptime can be inferred. There was a recent discussion on this in this thread: Coercions and compile time known numbers - #5 by kristoff

The difficulty we face (in my opinion) is the following question: what can we expect our compiler to know about our code without saying it explicitly?

The value of not having to specify everything is that there are certain optimizations that will be picked up even where we do not intend them (as is the case with compile-time known numbers). However, to be more explicit means that we can accidentally opt-out of many of those same optimizations by simply not saying them out loud.

When you say “The document” do you mean the official documentation? Or do you mean something more general like “the language should require us to be more specific overall”?

1 Like

Hi thanks for your reply. By "the document’ I meant the official document.

Is it correct to say that it’s guaranteed to do comptime evaluation if possible in the initialization of a constant? That is, the comptime keyword in the following code is guaranteed to be redundant?

const my_var = comptime compute(1, "hehe");

Any other similar cases? I think we can add a subsection/list in the document link above with such code examples.

I would check out this thread to get more context on this issue: How to force runtime evaluation?

I believe @kristoff’s answer is still up-to-date:

Also, we need to make a distinction here between const and comptime. A good instance of this is in the typical declaration of strings [] const u8 which is a slice of constant unsigned 8-bit integers. The const there says nothing about comptime as it’s embedded in a non-const type.

Then there are certain positions where comptime must be implied. For instance, in the deduction of return types: Implementing Generic Concepts on Function Declarations - #13 by AndrewCodeDev

2 Likes

the compiler gives you an error when your use of comptime is redundant. I don’t know if the first example (with multiple comptime keywords were used in a row) is a regression or if you’re using an older version of Zig, but ideally comptime should only be allowed by the compiler in locations where it does influence execution.

For example when initializing a constant in the global scope, you’re already in a comptime context and so all uses of it are redundant and will be reported as an error.

Conversely, initializing a const variable in a function body doesn’t mean that the value is necessarily available at comptime and so sometimes using comptime will have an impact.

A function is called at comptime implicitly only when you’re already in a comptime context.

I think this is the key takeaway. Thanks for pointing me to those threads!

2 Likes

Yeah it seems to have been fixed… I saw the example the other day in an old PR.

I think it’s worth noting that even if you remove comptime, LLVM might still evaluate it at compile time as part of its optimization passes. You can check the generated assembly to see if it is actually just dropping the answer in the executable. This is something I encounter a lot when trying to use godbolt. If you have a function which doesn’t conform to the C ABI and therefore can’t export it, it’s difficult to get the generated assembly because if you have inputs known at compile time which are passed as arguments into your function it will often be completely optimized away. In order to see how it really compiles I have to read the function’s input in from an external file so the optimizer has no choice but to compile my code.

4 Likes