I'm on nightly and learning to work with ARC.
I found that if an object (e.g. Leaf) has a destructor and is embedded in a containing object (e.g. Compound) then when Compound goes out of scope, the Leaf destructor will be called, which is as I expect. I presume a default destructor is created for Compound since I didn't specify one.
If I then create a destructor for Compound, the destructor for Leaf doesn't get called anymore by default. I have to explicitly add a call to the destructor for the field.
I suppose this makes sense, but it surprised me because I'm used to C++, where creating a destructor doesn't change that the default destructors for fields still get called.
Is this by design, or a not-yet-implemented?
If by design, is there a way I can get at the default destructor that would have been created and call it? This would be very convenient for the case where Compound has many embedded fields each with their own destructor, but I also want to do some extra work in the Compound destructor.
Even as I ask, it occurs to me that maybe my problem is bad design. Perhaps good design is that external resources that need a custom destructor should always in "leaf" types, and every containing type never needs an explicit destructor.
type
Leaf = object
x : int
Compound = object
leaf : Leaf
proc `=destroy`(t : var Leaf) =
echo "Destroying Leaf: ", t.x
#
# If this destructor is not defined, then
# Leaf destructor will be called when Compound
# goes out of scope.
#
proc `=destroy`(b : var Compound) =
#
# If the following line is missing, then Leaf
# destructor will not be called if this
# destructor is defined.
#
`=destroy`(b.leaf)
echo "Destroying Compound"
looks like it's inconsistent to me. the following code actually destroys one leaf but not the other.
type
Leaf = object
x : int
Compound = object
leaf : Leaf
proc `=destroy`(leaf : var Leaf) =
echo "Destroying Leaf: ", leaf.x
#
# If this destructor is not defined, then
# Leaf destructor will be called when Compound
# goes out of scope.
#
proc `=destroy`(compound : var Compound) =
#
# If the following line is missing, then Leaf
# destructor will not be called if this
# destructor is defined.
#
#`=destroy`(compound.leaf)
compound.leaf = Leaf(x: -1)
echo "Destroying Compound"
var x = Compound(leaf: Leaf(x: 2))
echo x
# If the following line is missing, then Leaf > # destructor will not be called if this > # destructor is defined.
Why would it? You didn't define an assignment operator. It's currently unclear how "partially" hooked types should behave, the spec doesn't really say anything. Sad, I know.
@jxy, I think the behavior makes sense. If you assign to a field with a destructor, the destructor gets called for the field. Otherwise assignment would always leak.
It seems like the rules are pretty simple. Destructors get called when they should be.
The only surprising thing is that creating a custom destructor loses the default behavior. You have to completely specify the behavior of your destructor if you implement your own. Fair enough!
Are you saying that if =destroy is defined then = needs to be defined for that type too?
That is what I tried to say, yes.
Is there a document with ARC rules? I mean this comes as a surprise for a user that knows c++ destructors
We have https://nim-lang.org/docs/destructors.html but as I said, it doesn't say what happens when you only override =destroy. :-)