As part of bridging a set of bit-masks in a C API, I declared an enum and a set:
type
Flag = enum
A = 4
B = 6
# ... more flags...
Flags = set[Flag]
After about half an hour of debugging an mysterious EINVAL return value from the C API π I finally discovered that the set value have the wrong integer values:
check cast[int]({A}) == 16
check cast[int]({B}) == 64
The checks fail β turns out {A} is 1 and {B} is 4! π€―
I figured out I can work around this by adding a fake enum item whose value is 0. So it appears that Nim's set always assigns bits relative to the lowest enum value, instead of using the enum values as absolute bit positions.
Is this a bug, or intentional? If the latter, is there a cleaner workaround like a pragma?
βJens
PS: Don't ask me why this API doesn't use bits 0-3! I didn't write it. I suspect there are some private flags, or maybe there used to be other flags but they are obsolete.
Note that enums with holes may become deprecated in future
That would be silly IMHO β there are so many uses for enums that are non-consecutive. It seems like Nim's idea of enums is primarily "named ordered values you can make a set out of", and assigning specific numbers to them is considered a secondary use case. But that rules out too many of the real-world uses of enums, especially in bridging other APIs or implementing network protocols.
Araq sometimes recommends using distinct ints instead of enums
That doesn't work with bit-sets (which are a great feature), but I might consider it for non-set-related enums like lists of error codes.
Nim sets and C flags have a different byte representation, use another type to distinguish between both.
Example https://github.com/numforge/laser/blob/d1e6ae61/laser/photon_jit/photon_osalloc.nim#L50-L61
type MemProt* {.size: cint.sizeof.} = enum
# is *not* a flag on Win32!
ProtRead = 1 # Page can be read
ProtWrite = 2 # Page can be written
ProtExec = 4 # Page can be executed
type Flag*[E: enum] = distinct cint
func flag*[E: enum](e: varargs[E]): Flag[E] {.inline.} =
## Enum should only have power of 2 fields
# static:
# for val in E:
# assert (ord(val) and (ord(val) - 1)) == 0, "Enum values should all be power of 2, found " &
# $val & " with value " & $ord(val) & "."
var flags = 0
for val in e:
flags = flags or ord(val)
result = Flag[E](flags)
proc mmap*(
adr: pointer, len: int,
prot: Flag[MemProt], flags: Flag[MemMap],
file_descriptor: cint, # -1 for anonymous memory
offset: cint # Offset in the file descriptor, PageSize aligned. Return Offset
): pointer {.header: "<sys/mman.h>", sideeffect.}
## The only portable address adr is "nil" to let OS decide
## where to alloc
## Returns -1 if error
proc mprotect*(adr: pointer, len: int, prot: Flag[MemProt]) {.header: "<sys/mman.h>", sideeffect.}
## len should be a multiple of PageSize
## replace previously existing protection with a set of new ones
## If an access is disallowed, program will segfault
Also regarding low-level types, Nim offers bitfields with the same representation as C bitfields
https://nim-lang.org/docs/manual.html#implementation-specific-pragmas-bitsize-pragma
Is this a bug, or intentional? If the latter, is there a cleaner workaround like a pragma?
It's intentional but it's not part of the spec and we could change it.
The workaround is to add an enum field that is mapped to 0.