nim-lang / RFCs

A repository for your Nim proposals.
136 stars 26 forks source link

module level global variables ({.global.}, {.threadvar.}) should also work at CT #283

Open timotheecour opened 3 years ago

timotheecour commented 3 years ago

this works:

when true:
  proc bar()=
    var a1 {.threadvar.}: int # ditto with {.global.}
    echo a1
    a1.inc
  proc main() =
    for i in 0..<2: bar()
  static: main()
  main()

prints: at CT: 0 1 at RT: 0 1

this doesn't, giving: Error: cannot evaluate at compile time: a1

when true:
  var a1 {.threadvar.}: int
  proc bar()=
    echo a1 #  Error: cannot evaluate at compile time: a1
    a1.inc
  proc main() =
    for i in 0..<2: bar()
  static: main()
  main()

proposal

make {.threadvar.} and {.global.} at module level work at CT, just like {.threadvar.} and {.global.} at proc scope.

benefits

workarounds

when true:
  var a1RT {.threadvar.}: int
  var a1CT {.compileTime.}: int

  template a1(): untyped =
    when nimvm: a1CT
    else: a1RT
  # template `a1=`(b): untyped = # won't work: no module level setters (https://github.com/nim-lang/Nim/issues/14674)
  template `a1Set`(b): untyped =
    when nimvm: a1CT = b
    else: a1RT = b
  proc bar()=
    echo a1
    # a1.inc # won't work
    # a1 = a1 + 1 # won't work
    a1Set(a1 + 1) # ugly workaronud
  proc main() =
    for i in 0..<2: bar()
  static: main()
  main()

notes

instead of: (10, "before") (20, "after")

Araq commented 3 years ago

but this goes against the spec

This part of the spec is highly controversial anyway.

more code that works at RT can now work at CT without needing patching

So code that uses global variables which most likely was written in a hurry (cannot call this code from a func then) "can now work at CT"? What a bold claim. More likely: "The code now compiles". But I still don't long for a language where everything compiles, I want error detection.

This is how the spec used to be:

A global var that are accessed via a compile-time mechanism needs to be annotated via .compileTime. To use a .compileTime var at runtime, convert it to a a const or let helper variable.

One big problem was when to turn it into this helper variable. It needs to be done after the full compilation. This is hard to implement properly but we require the same mechanism for IC and "method" code generation so there is no way around it. So since conversion from .compileTime vars to runtime vars is hard to do with const, we can say that a compileTime variable can be used at runtime and it's initial value is that after the full compilation. And maybe in order to use a .compleTime variable at runtime you need a special accessing mode like runtime(ctVar) so that it's clear what happens.

timotheecour commented 3 years ago

One big problem was when to turn it into this helper variable. It needs to be done after the full compilation. This is hard to implement properly but we require the same mechanism for IC and "method" code generation so there is no way around it. So since conversion from .compileTime vars to runtime vars is hard to do with const, we can say that a compileTime variable can be used at runtime and it's initial value is that after the full compilation

@araq you're missing an important part of this RFC:

there is no cross-over between CT at RT, consistently with how {.threadvar.} and {.global.} at proc scope work

there is no cross-over, both RT and CT see a fresh default initialized value, so that:

var a: int
static: a = 1
doAssert a == 0 # succeeds

This is the default correct behavior; it's as if a (CT) and a(RT) lived in different namespaces.

When you want cross-over (and don't want to use const), is where things get more complicated, and annotation (eg {.compileTime.} or other) must be used, this is out of scope for this RFC (but we can discuss this here too if needed).

Araq commented 3 years ago

This is the default correct behavior; it's as if a (CT) and a(RT) lived in different namespaces.

Really? The correct behavior? Seems much too subtle for my taste.

github-actions[bot] commented 7 months ago

This RFC is stale because it has been open for 1095 days with no activity. Contribute a fix or comment on the issue, or it will be closed in 30 days.