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
Run
* 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
Run
* For const data that already exists on the C side, I can just use importc,
but sometimes it's not practical to move my data to the C side (especially for
cases where the relevant values are only available in Nim, such as procedure
lookup tables)
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:
1. an {.addressable.} pragma for consts which guarantees them to have a
location in the generated C code, and allows 'unsafeAddr' to work with them
2. a {.readonly.} pragma for variables which makes them fully immutable and
forces them to be const in C
3. a const modifier for types? This would be like the {.readonly.} pragma
above, but apply to any variable of that type. It could also make pointers to C
consts safer and eliminate the warnings from the C compiler.
Let me know what you think :)