From some previous discussion (I don't remember where) I had a strong impression that converters in Nim are generally not reliable and frowned upon. I'd like to move them to a separate file in my wrapper bindings library instead of including them directly into the main file. I'd like to know whether it makes sense to include some of them and move to separate file others, or move ALL the converters to a separate file.
I have 3 kinds of converters:
1. Converters to keep sanity, just 2:
converter toCint*(self: int): cint = self.cint
converter toInt*(self: cint): int = self.int
My understanding is that without them it is necessary to constantly type 0.cint because int literals are not automatically converted to cint.
2. Converters for enum values, examples:
converter ConfigFlagsToInt*(self: ConfigFlags): cuint = self.cuint
converter MouseCursorToInt*(self: MouseCursor): cint = self.cint
These allow almost seamless interaction with C enums, convenience to not write ConfigFlags.FullscreenMode.cuint
3. Converters for lazy typers, examples:
converter tupleToColor*(self: tuple[r,g,b: int]): Color =
Color(r: self.r.uint8, g: self.g.uint8, b: self.b.uint8, a: 255)
converter tupleToRectangle*(self: tuple[x,y,width,height: float]): Rectangle =
Rectangle(x: self.x.cfloat, y: self.y.cfloat, width: self.width.cfloat, height: self.height.cfloat)
These allow to use tuples as they are shorter to type than full type names and their field names.
Could you please advise me on which of them are safe to use and whether it is okay to include all of them by default? I will appreciate any technical, design or other suggestions.
The library for reference: https://github.com/greenfork/nimraylib_now
toCint and toInt looks dangerous because int.sizeof and cint.sizeof can be different. You can pass int literals to cint parameter without .cint.
This code compiles without error.
proc foo(x: cint) = discard
foo(0x7fffffff)
foo(0)
If you have procs that has enum type parameter, you don't need to convert arguments if the parameter type is enum like this.
proc setMouseCursor*(cursor: MouseCursor)
type
Config {.size: sizeof(cuint), pure} = enum
FLAG_DUMMY = 0
FLAG_FULLSCREEN_MODE = 1
FLAG_WINDOW_RESIZABLE = 2
FLAG_VSYNC_HINT = 6
ConfigFlags {.size: sizeof(cuint).} = set[Config]
proc setConfigFlags*(x: ConfigFlags) = echo cast[int](x)
setConfigFlags({FLAG_VSYNC_HINT})
setConfigFlags({FLAG_VSYNC_HINT, FLAG_FULLSCREEN_MODE})
setConfigFlags({FLAG_VSYNC_HINT, FLAG_WINDOW_RESIZABLE})
setConfigFlags({})
Thanks for the suggestions!
About your first example, yes, it also works with objects
type
Vector2 = object
x, y: cint
echo Vector2(x: 3, y: 4)
The problem is that it doesn't work with variables, this won't compile:
type
Vector2 = object
x, y: cint
var x = 3 # must be 3.cint
echo Vector2(x: x, y: 4)
About safety, I wonder if anyone had problems with using SDL bindings by Vladar4, there are a couple of converters similar to mine.
About second example, it's true and nimterop also does some magic to allow uint operators like | or & for enums. But it is not always a seamless option, e.g. when this is a return value of a C function or when there are duplicated enums, for this case a duplicated value has to be a const which has to also co-operate with existing types (it could be possible but I'm afraid it's a good amount of work).
There's also an added complexity with setting proper enum types for procs because it can't be guessed from the implementation. I tried to make my wrapper as much automatically generated as possible, but I might reconsider it.
Though I really like your example with enums where enum values are actually the bit position with base 2. I might try it in the wrapper if I figure out how to automate it.