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 useinit
and let the compiler auto-select the correctinit
method 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!