I am a Nim n00b, and one of the hardest things I am having trouble with is dealing with arrays in Nim.
I have the following typedef
type GlyphArray* = array[100, NVGglyphPosition]
type
TextBuffer* = tuple
position : tuple[ x : float, y : float ]
cursor : tuple[ row : int, col : int ]
glyphPositions : GlyphArray
linePositions : seq[tuple[ start: int, stop: int]]
text : string
I would like to be able to instantiate a new TextBuffer tuple as follows:
var my_text : TextBuffer = (position: (25.0, 25.0), cursor: (0,0), glyphPositions: GlyphArray, linePositions: @[(0,0)], text:"")
But That gives me a type mismatch error because GlyphArray is a typeDesc, not an instantiated type.
I need to create separate variable for the program to compile correctly:
var glyphs : GlyphArray
var my_text : TextBuffer = (position: (25.0, 25.0), cursor: (0,0), glyphPositions: glyphs, linePositions: @[(0,0)], text:"")
This seems awkward to me.
Is there a cleaner way to do this? Is there a way to instantiate an array inside the construction of a tuple?
You don't need to instentiate an array because it is already instentiated. Try this
Var x :TextBuffer
echo repr (x.glyphPositions)
Which should print 100 element with the default value of NVGglyphPosition, and that because array[100, X] is a fixed size array, wich means that it will always contain 100 elements even without instentiation. For dynamicaly allocated you have to use a 'seq'. In addition to what BigEpsilon says, if you want to instantiate it that way, you may use a template for not creating an additional variable explicitly each time and to fit all in one initialization:
template ofType(typ: typedesc): auto =
var x: typ
x
# usage
var my_text : TextBuffer = (position: (25.0, 25.0), cursor: (0,0), glyphPositions: ofType(GlyphArray), linePositions: @[(0,0)], text:"")
echo my_text.repr
Or to name it, say, makeVar, for calling then GlyphArray.makeVar.
Of coarse you can make individual no-arguments templates to call them like instGlyphArray(), just you'll need then to define them per-type.
Yet you can define a constructor/initialization proc for whole the TextBuffer - maybe some initialization values (say, cursor) are usually the same, or can be calculated, then you won't need to provide them each time, that is you'll use:
var my_text: makeTextBuffer(onlyNeededArgsHere, inAnyDesiredOrder)
I think you should use object, it is the equivalent of struct from C.
type
Vec2i = object
x,y: int
Vec2f = object
x,y: float
NVGglyphPosition = int32
GlyphArray* = array[100, NVGglyphPosition]
TextBuffer* = object
position : Vec2f
cursor : Vec2i
glyphPositions : GlyphArray
linePositions : seq[Vec2i]
text : string
Thank you for your help everyone.
@BigEpsilon:
I understand the difference seq vs array. An array is a fixed size from compile time (a C array with the size information stored in the type system), and a seq is closer to a std::vector in C++. My question is about setting the default values of the struct.
You bring up a very good point that I didn't consider! I can just instantiate a variable of that type with no arguments, and set the tuple members that I need after. I like that better actually because it is more like C. I forgot that you could do that!
@LeuGim:
I am creating a constructor/initialization proc. This code is inside that function ;)
That template is a neat trick though! I have not wrapped my head around all the meta-programming available in nim, but I think it is one of the coolest parts of the language!
@Krux02
My understanding is that a Tuple is closer to a C struct than an Object. In fact, the C code that nim generates for TextBuffer Tuple is a C struct with nothing extra, which is exactly what I want!
Object has extra features like dynamic dispatch that I don't want.
Wow! I'm really lucky as far as finding bizzare language subtelties or compiler bugs but... I've actually encountered this behaviour before. I guess I just assumed you can use constructor syntax with any type. :-/
@rayman22201 array is pretty much std::array and seq is pretty much std::vector. I think I even read sometime ago that this is how they are compiled to C++ backend. Also: the only difference between tuple and object without of ... (e.x. of RootObj) is type safety --- a tuple behaves structurally (you can assign any compatible tuple to it), contrary to an object. It seems to me objects are widely preferred for most usecases.
Be careful when setting fields after you create a type value/instance. While tempting, it causes its fields, even the ones you change immediately after construction, to be initialized by default values. Which can be quite inefficient for large objects, actually. You can use {.noInit.} pragma to omit initialization of a variable and then initialize its fields without any performance penalty.
@Udiknedormin
+1 for finding weird edge cases! lol
Ideally I would like to be able to initialize an array in the constructor syntax directly, like any other type. It is strange that arrays are different. Even sequence has the special @[] syntax for this.
but it is such a minor thing that I am not bothered by it too much.
I would make a github issue if the people or the core devs think it is worth it? ¯\_(ツ)_/¯
I was avoiding object because I wanted to avoid things like extra magic pointers; if I can just avoid the using of to get the same effect then that's fine with me!
Good point about the cost of default initialization. In this case that is the exact behavior that I want, but it is definitely something to keep in mind. I will remember {.noinit.} for the future.
Arrays are not special, just having them of length of 100 you won't want to initialize them by a literal, the same for seqs.
type GlyphArray* = array[3, int]
type
TextBuffer* = tuple
position : tuple[ x : float, y : float ]
cursor : tuple[ row : int, col : int ]
glyphPositions : GlyphArray
linePositions : seq[tuple[ start: int, stop: int]]
text : string
var my_text : TextBuffer = (position: (25.0, 25.0), cursor: (0,0), glyphPositions: [3,5,7].GlyphArray, linePositions: @[(0,0)], text:"")
echo my_text.repr
@LeuGim
I think it is partially my lack of understanding. The documentation around arrays is not as clear as it could be around the ways to initialize and populate arrays.
Yes, large arrays are difficult to initialize or fill by literal. Though the core devs seem to be taking steps to fix this issue as seen in this recently merged PR: https://github.com/nim-lang/Nim/pull/2701
@Krux02
That's very good to know! Exactly what I was worried about. Thank you for pointing that out!