Why "inline" and not "comptime"?

I don’t care if the function is actually inlined or not (this is an optimisation decision best done by the compiler) but I do care if it is comptime or not (and functions like string formatting care, too). Why using the keyword “inline” for functions instead of “comptime”?

I do care if it is comptime or not

What does it mean for a function to be “comptime” in your mind?

Would it mean that the function must be executed at compile time?

Would it mean that it should be executed at compile where possible? This was already proposed and rejected for being difficult to implement and there being no good use cases for it that aren’t covered by other language features: ability to mark functions as wanting to be comptime if possible · Issue #425 · ziglang/zig · GitHub

Or do just want to rename the keyword in this case? I think “inline” is the most accurate name for what’s being done, especially in the context how inline works in the rest of the language.

2 Likes

That I can call it in a context where the result must be comptime-known.

I don’t know if it is even possible for the compiler to reliably check that.
Consider for example the Allocator interface. Its functions cannot be marked “callable at comptime” since some allocators do have runtime side effects. But at the same time it would be fine to call it when we use a comptime allocator.

Generally a function can be called at compile time if it has no side effects and all inputs are compile time known. There was a rejected proposal for pure functions like this: Proposal: Pure functions · Issue #520 · ziglang/zig · GitHub

1 Like

The problem is that it is often seen as a promise of efficiency (no overhead for the function call) which is strange today, where compilers know when to inline a function.

The problem is that it is often seen as a promise of efficiency (no overhead for the function call) which is strange today, where compilers know when to inline a function.

I generally agree with you here. However there are a few cases where it can make a difference.

  • a well-placed inline could help improve performance of the hot path in debug builds.
  • if the result of the function is comptime-known, then dependent code could be computed at comptime as well.
  • since Zig does forced function inlining in the frontend instead of the backend(where the optimization you mention happens) it can also improve (or worsen) compilation speed, because less code may be sent through the compilation pipeline.

@bortzmeyer I can understand where you’re coming from. Zig is more aggressive about making certain things comptime, and we’ve had threads about this in the past that I’d be happy to dig up for you.

Have you considered using the comptime keyword infront of function calls? This can be used to address the issue you are referring to. For instance, const x = comptime foo(y) will attempt invoke the function at compile time. This also works with if statments… if (comptime x == y)

Let me know if your concerns span further than this - I’m trying to understand your issue here. Inline is not nearly as overloaded as it is in some other languages, so I’d like to see some examples where you find the two ideas are getting crossed and creating ambiguity.

This is not directly related to the original question, but I find it a bit strange that inline for is inline and not unrolled (not a real keyword in Zig) or perhaps comptime. Or comptime unrolled.

Ditto inline while.

For me it kinda implies unrolled if you consider what inline does with functions.

unrolled would be problematic semantically with switch:

switch (@typeInfo(T)) {
    unrolled .Struct, .Union => |info| {
       // ...
    }
}
1 Like

The use of inline in this instance makes sense, at least to me. functions, when inlined, are replaced at the call site with their implementation. So for something like a for or while loop, the loop is iterated (at comptime) and the generated iterated code replaces that of the inlined loop. How the compiler does that is, of course, implementation-defined.

Well, I was talking specifically about loops -inline for and inline while.

For me these are two distinctly different things. Consider:

inline fn ham() void
{
    for (0..10) |i|
        std.debug.print("{} ", .{i});
}

Calling ham() means your code will have a for loop embedded into it, but this loop is not going to be unrolled into 10 std.debug.print calls.

Unless, of course, compiler decides to optimize by silently unrolling the loop behind your back, but let’s not go there, because if we do, any function can potentially be inlined and this conversation is meaningless.

If, on the other hand, we decide to make our for loop an inline for loop instead, the compiler is asked to duplicate its body 10 times, hardcoding i values into std.debug.print calls.

This is why I think inline and unrolled / unwound are two distinct things.
Then again I may easily be missing something, in which case I would be glad to learn about my mistake.

Correct.

I don’t necessarily agree with the use of the words here. I can’t remember seeing any mentions of “inlined loops” outside of the Zig community. Unless they were actually meaning an inlined function, which predominantly consists of a loop, so, effectively, inlining a loop, which would still be not the same as unrolling it.

I don’t claim to posses a great amount of knowledge on the subject, but aren’t loops “unrolled” or “unwound”, rather than “inlined”? They’re inline (in the body of the function that contains them) already, after all.

Sure, I can see the point of reusing a keyword (inline) instead of introducing a new one (unrolled, unwound), but it still sounds kinda strange.

Just my 2 cents.
I could be completely wrong, of course, please correct me if you feel I am.

1 Like