Every now and then i tried to re read the destructors documentation at but i always find it confusing. The most confusing part is the =sink documentation where basically it's explained that resources are stealed from the source and injected in the destination. That makes perfect sense. However the actual code in the examples seems to do the opposite
proc `=sink`(dest: var T; source: T) =
`=destroy`(dest)
wasMoved(dest)
dest.field = source.field
I obviously understand the last assignment and "maybe" the first destroy. In C++ i would expected something like
proc `=sink`(dest: var T; source: var T) =
dest.field = move(source.field)
Further questions are:
1) What happen in case of two types A and B where B extends A and both define a =destroy hook. Something like
type A = object
....
proc `=sink`(dest: var A; source: A) = ...
type B = object of A
...
proc `=destroy`(dest: var B; source: B) = ...
Should B =destroy: Option 1) Statically e manually call A =destroy Option 2) Copy&Paste what A does (basically inlining A =destroy Option 3) Destroy only B stuff because an automatic call to A =destroy is injected by the compiler automatically
I would also like to ask if the answer to this question applies to all the other hooks
Another question: 1) Should a type =destroy hook call its fields =destroy hooks?
type A = object
field1: string
proc `=destroy`(dest: var A; source: A) =
// ....
// here call to field1 `=destroy`
// .....
=sink performs half of a move, this differs significantly from C++ and is more efficient than C++ does it, on some benchmarks we have seen a 10% performance difference. The compiler produces a pair of =sink(a, b); wasMoved(b) calls, if necessary. The wasMoved is in many cases not required and in other cases the pair wasMoved(b); destroy(b) is optimized out.
All the other questions can be answered by a single rule. Once you override a =hook, you're on your own and you need to call other hooks yourself. So you need to call the destructor of a base class yourself. And you must destroy fields inside your object yourself. I consider this is the most inelegant part of the design but I don't really know how to do better -- in order to fight stack overflows one has to be able to override the object traversals completely.
Sorry it's my fault but i still don't quite understood it...maybe i can grasp it more with a C++ example. I don't get what do you mean with half a move and also the pair of sink/wasMoved.
Regarding the fact that overriding a hook implies being on your own: is there a reason why C++ destructors don't suffer for the same thing? maybe because you cannot call directly the destructor of an object (like =destroy) for stack based allocations?
The main reason why you need to call the destructors yourself in a destructor is that I consider code like
virtual ~node() noexcept {
while (next) {
next = std::move(next->next);
}
}
too subtle.