RFCs
RFCs copied to clipboard
module level global variables ({.global.}, {.threadvar.}) should also work at CT
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
- more consistent, {.threadvar.} and {.global.} would behave similarly whether at module or proc scope.
- more code that works at RT can now work at CT without needing patching
- would fix issues like: https://github.com/nim-lang/Nim/issues/10129
workarounds
var a {.threadvar, compileTime.}: intandvar a {.global, compileTime.}: intseem to work, as doesvar a {.compileTime.}: intwhich IIRC is same asvar a {.global, compileTime.}: int, but this goes against the spec:
``compileTime`` variables are available at runtime too. This simplifies certain
idioms where variables are filled at compile-time (for example, lookup tables)
but accessed at runtime:
furthermore, as this RFC suggests, compileTime shouldn't even be needed in the 1st place, for consistency with both global/threadvar at proc scope, as well as for sake of making RT code work at CT without needing patching.
- the workarounds without
{.compileTime.}are quite bad, and made even worse because of https://github.com/nim-lang/Nim/issues/14674
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
- a top-level
var a: intis implicitly{.global.}, so the rules would apply to that too (making it available at CT) - there is no cross-over between CT at RT, consistently with how {.threadvar.} and {.global.} at proc scope work:
when true:
var a1 {.threadvar.}: int
proc main()=
echo (a1, "before")
a1 += 10
echo (a1, "after")
static: main()
main()
will be: at CT: (0, "before") (10, "after") at RT: (0, "before") (10, "after")
instead of: (10, "before") (20, "after")
- when cross-over between CT and RT is needed, use
const
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.compileTimevar at runtime, convert it to a aconstorlethelper 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.
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).
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.
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.