RFCs
RFCs copied to clipboard
All let {.compileTime.} constructs can now be const instead
Compile time let and var are currently buggy but due to a recent fix all the following constructs:
let x {.compileTime.} = ...
let (a,b) {.compileTime.} = ...
can now be replaced with:
const x = ...
const (a,b) = ...
They are also available in a static block after declaration:
const x = ...
static:
echo x
The only thing const disallows vs. let is annotating it with a pragma, so the following does not work:
const x {.myCustomPragma.} = ...
In light of this it may be a good idea to just deprecate let x {.compileTime.} = ....
This doesn't help the var situation but IMO toplevel compile time mutable state should also be disallowed.
I don't mind removing let x {.compileTime.} but removing toplevel compile-time var would break many of my libraries because I would have no way to do identifier hashing at compile-time:
- Compiletime hashtables of NimNodes: https://github.com/nim-lang/RFCs/issues/131
- It is the only way of mangling for C FFI in rare cases (OpenMP) because const generated in templates are not visible for exportc so I need a top-level compile-time var: https://github.com/nim-lang/Nim/issues/9365#issuecomment-429613318
I'm also using those for quick prototyping of an embedded DSL (https://github.com/mratsim/compute-graph-optim/blob/023ddf6d07df0deb1c55722cb30627052bed4b1b/e11_DSL_compiler_seq.nim#L956-L970), due to symbol mangling templates don't work, dirty templates and blocks don't work (https://github.com/nim-lang/Nim/issues/11091#issuecomment-485826614). The ideal solution is proc but that is my second step after quick prototyping.
I must be missing something with your C FFI example because:
const foo = "foo"
let notfoo {.exportc: "someprefix" & foo .} = 0
generates:
...
N_LIB_PRIVATE N_NIMCALL(void, NimMainModule)(void) {
{
nimfr_("constexportc", "constexportc.nim");
nimln_(2, "constexportc.nim");
someprefixfoo = ((NI) 0);
popFrame();
}
Edit: Got it. The const has to be in a template to repro your issue.
IMO {.compileTime.} should be merged with static, and the scope issue (https://github.com/nim-lang/Nim/issues/9970#issuecomment-452226661) be resolved like this:
Static without introducing a scope:
static let foo = "ha"
echo foo #At runtime
works because static without a : wouldn't introduce a new scope.
Similarily this would work:
static let
foo = "ha"
bar = "ho"
echo foo, bar #At runtime
but this would not (already the case):
static:
let foo = "ha"
let bar = "ho"
echo foo, bar #Doesnt work at runtime
and this would (also already the case):
static:
let foo = "ha"
echo foo #At compiletime
const foo = "ha" could then be an alias for static let = "ha"
This would give a semantic representation of wether a compiletime/static value can "leak" into runtime via scope.
EDIT: All of the above should also apply for var, as compile-time var are very useful/necessary in some situations.
I am 100% on board. I made exactly this argument on chat about 8 months ago I think and it was ignored/rejected. If the mood is more in favor now I'm happy to turn this into an RFC.
After the examples posted above I agree that today compile time vars are useful but I do think we should work toward finding alternatives for each use case as they come up.
As an example I'm working on having const bindings visible to pragmas in templates to address the C FFI issue.
I see little value, lots of changes required but nothing that can't be done today will be made possible.
I forgot about one key use case that const does not cover compared to let {.compileTime.}: ref object at compileTime for macro input.
Test case:
import macros
type Foo = ref object
val: int
# const x = Foo(val: 1) # invalid
let x {.compileTime.} = Foo(val: 1) # OK
Edit: Also, for that case I'm sure that {.compileTime.} ref objects will not be present in the binary. I'm not sure what a const ref objects would mean regarding code generation, and also assuming I use a const ref object only for VM operations I don't want it to end up in my binary.
After thinking a bit more about it I think it's fine as is, only that {.compileTime.} is a bit clunky syntactically.
constserves to bridge compile time and runtime essentially, by embedding its value in the binary{.compileTime.}only exists at compiletime/in the vm- declarations inside a
static:block are essentially the same as{.compileTime.}but with a scope
So const a = someexpr is more like the equivalent of let a = static: someexpr, except that a is also accessible at compile time.
So only the syntax is a bit inconsistent/confusing.
One could rename {.compileTime.} to {.static.}
or introduce the static (without a :) prefix for let and var sections.
{.static.} sounds good to me. Plus a paragraph about the distinction between what is run in the VM and never present in the binary (static) and what can end up in the binary if it is used (otherwise it is optimised away.
For starter we can proceed on this PR: https://github.com/nim-lang/Nim/issues/9817 (this will break some of my libraries in CI but should be an easy fix).
I'm still unsure about the incremental compilation interaction as mentionned by @krux02 here and in this {.compileTime.} bugfix https://github.com/nim-lang/Nim/issues/9817#issuecomment-447869197.
Tagging also the RFC on compileTime functions https://github.com/nim-lang/Nim/issues/8051
@mratsim Didn't know about that use of let. Interesting.
@Clyybber Using a pragma to indicate which construct should be evaluated at compile vs. run time is also problematic because mixing them in the same syntactic construct is very confusing and makes things more complex than necessary, eg:
let (a,b {.compileTIme.}, c) = (1,2,b+1)
It's not clear that c relies on a compile time value, vs. eg.
static let b = 2
let (a,c) = (1, b+1)
One compromise could be to mandate that {.compileTime.} cannot be interleaved with runtime bindings.
Another problem with leaving it as a pragma is that {.compileTime.} values and runtime value share the same namespace when they are clearly separate phases, eg. we can't do:
let x {.compileTIme.} = ...
let x = ...
In any case my goal isn't to break people's existing code and invalidate documentation just for the sake of it, I just think the static introspection and compile time features of Nim are enough of a differentiator that it's worth getting the UX and VM bulletproof as possible before Nim gets wide adoption.
@deech I agree; I'd prefer the syntax without the pragma; but there doesn't seem to be consensus; and it would probably require an RFC first.
Anyways, I have a proof-of-concept implementation here: https://github.com/Clyybber/Nim/commit/af2676de160566482b8c25ba88457c982b11fa36
So far it works for static let static proc and static func. (not sure why not for static var)