Updated Destructors, 2nd edition (rest)

This commit is contained in:
Andreas Rumpf 2019-06-05 18:05:58 +02:00
parent eaa3fba25f
commit 3d847d40b7
1 changed files with 54 additions and 4 deletions

View File

@ -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(...))``.