Hey! For several months I've been working on a Game Boy Advance game in Nim. This has been a great experience for the most part. For example I used macros to hugely reduce the amount of boilerplate required to load and draw sprites, preventing a lot of human errors with no performance cost.
However there is one place where I feel the language is really lacking, for which I've not yet found any good solution. That is: addressable constant data.
The GBA has very little RAM. So if you have any data that doesn't change (graphics, palettes, tile maps, level data, animation data, lookup tables, etc.), you'll want to store it in ROM. In C, you do this by marking your data as 'const', which will cause the compiler to put it in the .rodata section of the ELF.
The problem is, Nim has no concept of a 'pointer to constant data'. Nim consts only exist at compile time. You can get a pointer once the const has been copied into a variable, but there simply isn't enough memory to do that on embedded platforms.
Meanwhile if your constant data comes from the C side, you can use {.importc.} to access it. But Nim doesn't know that this data is supposed to be constant. If you take a pointer to it, Nim won't stop you from mutating it by accident. You'll also find yourself getting a lot of warnings such as "initialization discards 'const' qualifier from pointer target type".
I've tried a lot of different solutions, none of them are great:
For const cstrings and raw binary data in Nim, I can import the '&' operator from C and use it to get the address of the string. This is an awful hack, but it works because the string will be embedded in-place in the generated C code, and we can rely on the C compiler to optimise away any duplicate occurrences.
proc `&`*[T](x:T):ptr T {.importc:"&", nodecl.}
const data:cstring = staticRead("data.bin")
var p = &(data[39]) # get a pointer to the 40th byte
For const objects in Nim, I can use {.exportc.} on the type, {.emit.} the data as C code, and then import the object back into Nim. But this is an absolute pain to maintain and you have to jump through a lot of hoops to get it working. The following isn't very usable because nodecl means the variable inaccessible from other modules.
{.emit:"static const LevelData lvl1 = {...};".}
var lvl1: {.importc, nodecl.}: LevelData
var currentLevel = addr lvl1
In summary, I'm experiencing a lot of pain trying to express something that Nim doesn't know how to express. Addressable constants are really important for embedded programming. I think the language is in need of some additional construct to support this.
Some ideas:
Let me know what you think :)
That said I would like an {.addressable.} pragma for const because using let creates issue with {.noSideEffect.} inference (but you can now have {.noSideEffect.} blocks so not that urgent as well for internal adressable const).
Beyond my LUT table example, this would be useful for cryptography where you want to do modular operations vs a particular prime but instead of always inlining against that array, you want the prime to be stored only once in the BSS for code-size reason and more importantly you want that modulo prime to be part of the proc types (though that would need https://github.com/nim-lang/Nim/issues/11142 to be fixed first).
Var/let cannot help here as you need a const to be able to pass it as a static param, unless I use something like let myPrime {.compileTime.} = parseHex("0x1234567890ABCDEF") and unsafeAddr thanks to https://github.com/nim-lang/Nim/pull/12128 which allow runtime access to compileTime let.
Btw this can help your own issue, not sure if that was intended though, should we make const an alias to compile-time let?
let arr = [1,2,3,4,5]
let a = unsafeAddr(arr) # <------- Emphasis on unsafe
a[2] = 100
echo arr # outputs [1, 2, 100, 4, 5]
When you deal with raw memory and use unsafeAddr it's a bit unfair to complain that it compiles. unsafeAddr is an escape hatch, a contract that Nim defers all responsability of sfety to you because you know better. If you use addr with let Nim will not compile.