Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Disallow the use of the comptime keyword before an expression or statement when it is superfluous #8364

Closed
alexnask opened this issue Mar 26, 2021 · 5 comments
Labels
accepted This proposal is planned. proposal This issue suggests modifications. If it also has the "accepted" label then it is planned.
Milestone

Comments

@alexnask
Copy link
Contributor

alexnask commented Mar 26, 2021

Currently, comptime expressions can be superfluously added and even chained.

var foo: comptime comptime u8 = comptime comptime comptime 0;

This allows people to insert the keyword even in explicit comptime contexts, for example the length of an array type or a type expression.
While this is not necessarily bad, in my opinion it can only reinforce the confusion about which blocks/expressions are comptime by default and which are not.

Furthermore, with the introduction of #1717, the following will be allowed:

const foo = comptime fn () void {
    // ...
};

This is especially bad since it is easily confusable with functions of .Comptime calling convention.

@alexnask alexnask added the proposal This issue suggests modifications. If it also has the "accepted" label then it is planned. label Mar 26, 2021
@Vexu Vexu added this to the 0.8.0 milestone Mar 26, 2021
@rohlem
Copy link
Contributor

rohlem commented Mar 27, 2021

I think I disagree with the premise, that is the issue this is trying to solve.
I don't think people are confused by the fact you can write comptime before an expression or statement that is, in fact, executed at comptime.
But then again, I'm not a newcomer, and I'm not involved in any communities of newcomers, so I maybe can't judge that by my own experience.

To elaborate however, from my perspective, if someone wrote var foo: comptime u8 and expected it to mean comptime var foo: u8,
then to me this is a fundamental misunderstanding of the modularity of Zig, and that types are expressions.

It is about distinguishing what these concepts mean, and not the grammatical details of "where do I put the keyword".
Once they understand this, I think they would necessarily see how var foo: comptime u8 couldn't possibly declare a comptime var,
because it is already equivalent to var foo: u8.

(That being said, I also don't know any other language to feature the concept of comptime var as fluently as Zig does,
so I'm not sure where newcomers would have gotten the idea to try something like this in the first place.)

Edit: I may have gone off-the-rails a bit, because I didn't understand the code examples as what was the initial concern:

in my opinion it can only reinforce the confusion about which blocks/expressions are comptime by default and which are not

I don't think this is an issue either:

  • If someone tries to write runtime-only logic in a comptime-context (like a type expression),
    it will necessarily fail to compile because comptime effects on runtime state are ill-formed.
  • Thinking that the context is evaluated at runtime, when it isn't, can never lead to a observable difference either.
  • And if they want it to be executed at comptime and use the keyword, well, that's what's happening regardless, they just made it more explicit.

Besides this uninformed opinion, here are some practical counterpoints to consider:

  • While disallowing comptime in certain contexts can inform the programmer while writing code,
    it requires them to know these language details when reading code to correctly understand it.
    (This goes against both the Zen and the advertised "debug the program, not your knowledge of the language".)
  • Restricting the use of comptime in certain places results in grammatical asymmetry, making code less friendly to copy-pasting.
    • In particular, for scenarios where runtime and comptime evaluation are both valid options, I posit comptime is always the safer choice.
      (Less pertinent side note, as I hope this will still change,
      but the current concept of comptime allocations are expressed in a way that can lead to a dangling stack pointer at runtime.
      So that's a particularly inconvenient footgun currently.)
  • While it's easy for a compiler to verify whether a particular context is implicitly comptime,
    I suspect that for external tooling (say, syntax highlighting, maybe template-based code generation) the task is more complex.

I think this change would overall hinder explicitness and introduce a new complexity that is avoided by status-quo.

@Airtz
Copy link

Airtz commented Mar 27, 2021

This is especially bad since it is easily confusable with functions of .Comptime calling convention.

Wasn't #425 scrapped (sadly)?

@SpexGuy
Copy link
Contributor

SpexGuy commented Mar 27, 2021

I think it would definitely be reasonable to disallow the comptime keyword in locations that are lexically known (like based on position in the AST) to be implicitly run at comptime. So return types, variable types, parameter types, global decl/var initializers, field defaults, etc. Since these locations are implicitly forced to be comptime, the comptime keyword is fully redundant here. This would catch the biggest problems with ambiguity:

// compile error: redundant comptime on decl initializer
const foo = comptime fn () void { };
// compile error: redundant comptime on return type expression
const bar = fn () comptime u8 { };

const baz = blk: {
  // allowed, this is an execution scope.
  const x = comptime fn () void {};
  // compile error: redundant comptime on return type expression
  const y = fn () comptime void {};
  // compile error: redundant comptime on type expression
  const z: comptime fn () void = fn() void {};
  // allowed but weird, neither of these are at top-level in a type expression
  const w: (comptime fn () (comptime void)) = fn() void {};
  break :blk w;
};

We could also safely disallow comptime comptime x everywhere (but not comptime (comptime x)).


this is a fundamental misunderstanding of the modularity of Zig, and that types are expressions

This is true, but if we can have the compiler identify these misunderstandings and point people towards the right path, IMO that's worth doing. I don't think anyone who does understand this distinction would want to put comptime in these places.

@ratfactor
Copy link
Contributor

I enthusiastically support this, especially from a learning perspective.

Also, allowing me to throw the comptime keyword around like confetti even where the compiler knows it is useless feels sloppy and actually makes me trust the compiler less.

It's like if I were cooking right next to an expert chef and he knows I need to just melt a little butter for the sauce, but I start throwing whole sticks of butter into a pan.

I say, "Can I use this much butter? This feels like a lot."

And he just raises an eyebrow and says, "Sure, you could use that much butter."

@andrewrk
Copy link
Member

This is implemented in self-hosted thanks to @g-w1 and as of 31c49ad, the error will be found even when using the stage1 backend.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
accepted This proposal is planned. proposal This issue suggests modifications. If it also has the "accepted" label then it is planned.
Projects
None yet
Development

No branches or pull requests

7 participants