from OS import sleep
type
B = ref object
id: int
i: int
proc bFinalizer(x: B) =
echo "called bFinalizer with id: ", x.id, " and address: ", cast[int](x)
proc newB(): B =
new(result, bFinalizer)
result.id = 1234
echo "called newB with address: ", cast[int](result)
proc main =
var b1: B
new b1
var b2: B
b2 = newB()
var b3: B
new b3
main()
GC_Full_Collect()
sleep(100)
GC_Full_Collect()
$ ./h
called newB with address: 139863036182632
called bFinalizer with id: 0 and address: 139863036182600
called bFinalizer with id: 0 and address: 139863036182664
called bFinalizer with id: 1234 and address: 139863036182632
So when we call at least for one time new() for a type with a finalizer, then GC will use that finalizer for all instances.
That behaviour is indeed documented:
https://nim-lang.github.io/Nim/system.html#new%2Cref.T%2Cproc%28ref.T%29
The finalizer refers to the type T, not to the object! This means that for each object of type T the finalizer will be called!
But it is a trap, not only for gintro.
Well, it took me 4 hours yesterday, and one this morning to find out :-(
Problem is the high level callback code.
For example the gtk "draw" signal passes a cairo-context to the user provided callback function. That cairo-context is owned by gtk, so user has not to free it inside the callback. My gintro macros creates a proxy context object, which is basically a ref object with a impl field containing a pointer to the the low level cairo context, using plain "new Context". That was working fine so far, as shown in cairo drawing example cairo_anim.nim from
https://github.com/StefanSalewski/gintro
Was working fine for other callbacks also, for example when GDK events are involved.
For other code, when we create widgets or other data structures, we have to free them when done, so for example buttons are created with newButton() proc, which uses a finalizer. So long all works fine. But yesterday I ported a more advanced drawing app from Ruby to Nim (http://ssalewski.de/PetEd-Demo.html.en) which contains code for creating a temporary drawing context created by proc newContext(). newContext() has to release the object instance when done, so a finalizer is used. But after first use of that finalizer it is used suddenly also inside the macro generated code calling the draw callback. So we get a crash when GC starts, as unowned callback parameter Cairo-Context is freed.
I wrote that gintro macro glue code about two years ago, so it took me some time to remember, and I also had to reread the new() Nim documentation.
I think I will be able to fix that issue, maybe I will apply global pragma to the proxy object inside the glue proc, so allocate it only one when it is nil and then reuse and never free.
But I have to think about it -- have to understand may macro code again first.
Is there a reason finalizers need to be set in new as opposed to following destructors?
proc `=finalize`(x: T) = ...