In my last post on Nim, I talked about how I was learning to use generics. This time, I’d like to talk a bit about pointers!
The first thing to know is that there are two different types of pointer – the “safe” and “unsafe” kind. The “safe” one is traced (in Nim parlance) and managed by a garbage collector. The untraced (“unsafe”) pointers are expected to be entirely managed by the user – these are very useful for accessing hardware, or interoping with non-Nim code.
The seocnd thing to know is that you really don’t need them unless you are, in fact, interoping with C code, accessing hardware, or have a very specific use-case. Of course I didn’t know that until after writing a few hundred lines of code and then writing up the first draft of this article…
In my search for some questions I came up with in the first draft, I came across this, which explained the folly of my ways (well, not directly, but I was able to think through the problems I was solving with a better understanding).
But, awesome! Now I have a much better grasp on when and when not to use Nim’s pointer constructs.
Here’s the equivalent of some code I was trying to write (note: this is the bad example of how to use pointers – not that this example won’t work, it’s just not a place you need to use pointers):
type TBaz = object y: int PBaz = ref TBaz TFoo = object baz: TBaz PFoo = ref TFoo proc init_new(foo: var PFoo; y: int) = foo = PFoo() new(foo.baz) foo.baz.y = y proc baz_accessor(f:var PFoo): var PBaz = return f.baz proc `baz_accessor=`(f:var PFoo, baz: PBaz) = f.baz.y = baz.y discard """and I was doing some other stuff here (which is why I had the 'baz_accessor' property at all""" var bar: PFoo PFoo.init_new 1 assert(bar.baz_accessor.y == 1) assert(bar.baz.y == 1) bar.baz_accessor = 10 assert(bar.baz_accessor.y == 10) assert(bar.baz.y == 10)
And here’s some code whose end result is the same, without pointers (and without the “old” syntax, which I also found out about from the aboved linked article…):
type Baz = object y: int Foo = object baz: Baz proc baz_accessor(f:var Foo): var Baz = result = f.baz proc `baz_accessor=`(f:var Foo, y: int) = f.baz.y = y discard """and I was doing some other stuff here (which is why I had the 'baz_accessor' property at all""" var bar = Foo(baz: Baz(y:1)) assert(bar.baz_accessor.y == 1) assert(bar.baz.y == 1) bar.baz_accessor = 10 assert(bar.baz_accessor.y == 10) assert(bar.baz.y == 10)
The first example (the one that uses pointers) uses 26 lines and 581 characters.
The second example (the seemingly correct one) uses 18 lines and 445 characters.
Not a huge difference for this particular example, but as code grows I have first hand knowledge that it becomes a bigger difference! The second, correct example is clearly more concise and readable. It also has no actual need for the
init_new method, like the pointer approach really does need. Certainly if initialization was a more important aspect of the
Foo type, then the
init_new method would have stayed in the second example.
On-wards and upwards! I have some code to clean up now… but this whole process has been enlightening.
2014-11-19 Addendum: After posting this article, I received a nice message from this guy regarding a couple of things:
- When there is a need for an init function for an object, the convention for much of the existing code has been something like
init_Somthing_, but a newer convention is to just use
initand let the compiler auto-select the correct
initmethod based on the arguments passed to it. The sets library is a good example.
- The other is on the subject of getters/setters. The article describes how to utilize Nim’s awesome macro system to automatically generate properties with specific actions for particuarly styled members of types. Lots of good information there about macros and AST’s!