I'm playing with finalizers and find them problematic, consider the following example:
type
T800 = object
name: string
Terminator = ref T800
var
JohnTalked, SarahTalked: bool
proc John(arnie: Terminator) =
if not JohnTalked:
echo "Bye bye, ", arnie.name
JohnTalked = true
proc Sarah(arnie: Terminator) =
if not SarahTalked:
echo "Noooooo! ", arnie.name
SarahTalked = true
proc newTerminator(name: string): Terminator =
new(result, John)
result.name = name
proc tester() =
var a = newTerminator("Arnie")
var b: Terminator
new(b, Sarah)
b.name = "Araq"
proc skynet() =
for i in 0..100000: tester()
echo "Terminations done:"
echo "John ", JohnTalked
echo "Sarah ", SarahTalked
when isMainModule:
skynet()
The output I get for the example is:
Noooooo! Arnie
Terminations done:
John false
Sarah true
John is not being called, a finalizer is a runtime property of the type and the new call overwrites any previous finalizers. This is weak: you cannot write code for a library and depend on finalizers, because the user can new a different finalizer for your type. Of course you can wrap creation of objects behind opaque procs for the end user, but suddenly new is a dangerous API which can break stuff behind your back.
Also, a single finalizer might not be enough, maybe I want to add a finalizer inside my code, but that prevents the user from adding his own finalizers unless he wraps the types. I'd like to have an official API for finalizers, like:
proc addFinalizer(type, proc)
proc hasFinalizer(type, proc): bool
iterator finalizers(type): proc
The hasFinalizer at least can be asserted agains in library code. A removeFinalizer could be provided, but I think addFinalizer should skip existing entries so that most code only has to worry about adding finalizers. It may be even worth adding a bool to addFinalizer so that the finalizer can't be removed during the rest of the runtime due to safety reasons. And for that you need user chainable finalizers.Chaining would be a wanted feature only with the current behaviour where a new overrides the previous finalizer. I think it would be fine if attempting to change a previous finalizer crashed. Then a library creator can be sure his finalizer will be called no matter what and can't be overriden.
With regards to the compiletime, I would also prefer if this was not possible to change finalizers at runtime. A finalizer seems to be an integral part of a type, and as such looks seems ok to be specified at compilation.
But the recursive question of "does compile time code need finalizers for types" may come, and then you can argue the API distinction between compile time and runtime is blurred, since it would work during the runtime of the compile time phase. Or maybe we can throw the DLL argument here, you need to be able to specify finalizers for DLL imported types, which are only available at runtime.