diff --git a/Destructors,-2nd-edition.rest b/Destructors,-2nd-edition.rest index 8b0cf82..d8547d7 100644 --- a/Destructors,-2nd-edition.rest +++ b/Destructors,-2nd-edition.rest @@ -2,6 +2,7 @@ Nim Destructors and Move Semantics ================================== + .. contents:: @@ -118,8 +119,9 @@ The general pattern in ``=destroy`` looks like: .. code-block:: nim proc `=destroy`(x: var T) = + # first check if 'x' was moved to somewhere else: if x.field != nil: - dealloc(x.field) + freeResource(x.field) x.field = nil @@ -172,7 +174,7 @@ The general pattern in ``=`` looks like: # protect against self-assignments: if dest.field != source.field: `=destroy`(dest) - dest.field = copy source.field + dest.field = duplicateResource(source.field) The ``=`` proc can be marked with the ``{.error.}`` pragma. Then any assignment @@ -202,7 +204,7 @@ as ``let tmp = move(a); b = move(a); a = move(tmp)``! This has further consequences: * Objects that contain pointers that point to the same object are not supported - by Nim's model. Objects can be swapped and end up in an inconsistent state. + by Nim's model. Otherwise swapped objects would end up in an inconsistent state. * Seqs can use ``realloc`` in the implementation. @@ -284,13 +286,15 @@ Const temporaries Constant literals like ``nil`` cannot be easily be ``=moved``'d. The solution is to pass a temporary location that contains ``nil`` to the sink location. +In other words, ``var T`` can only bind to locations, but ``sink T`` can bind +to values. For example: .. code-block:: nim var x: owned ref T = nil - # gets turned into: + # gets turned into by the compiler: var tmp = nil move(x, tmp) @@ -430,3 +434,49 @@ Let ``U`` be an unowned ``ref`` type. Conceptually its hooks look like: # Note: Moves are the same as assignments. `=`(x, y) + +Hook lifting +============ + +The hooks of a tuple type ``(A, B, ...)`` are generated by lifting the +hooks of the involved types ``A``, ``B``, ... to the tuple type. In +other words, a copy ``x = y`` is implemented +as ``x[0] = y[0]; x[1] = y[1]; ...``, likewise for ``=move`` and ``=destroy``. + +Other value-based compound types like ``object`` and ``array`` are handled +correspondingly. For ``object`` however, the compiler generated hooks +can be overridden. This can also be important to use an alternative traversal +of the involved datastructure that is more efficient or in order to avoid +deep recursions. + + + +Hook generation +=============== + +The ability to override a hook leads to a phase ordering problem: + +.. code-block:: nim + + type + Foo[T] = object + + proc main = + var f: Foo[int] + # error: destructor for 'f' called here before + # it was seen in this module. + + proc `=destroy`[T](f: var Foo[T]) = + discard + + +The solution is to define ``proc `=destroy`[T](f: var Foo[T])`` before +it is used. The compiler generates implicit +hooks for all types in *strategic places* so that an explicitly provided +hook that comes too "late" can be detected reliably. These *strategic places* +have been derived from the rewrite rules and are as follows: + +- In the construct ``let/var x = ...`` (var/let binding) + hooks are generated for ``typeof(x)``. +- In ``x = ...`` (assignment) hooks are generated for ``typeof(x)``. +- In ``f(...)`` (function call) hooks are generated for ``typeof(f(...))``.