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:
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.