From 1296e5632184289a553269dc80c72ebbc30bded6 Mon Sep 17 00:00:00 2001 From: Andreas Rumpf Date: Wed, 14 Aug 2019 20:24:05 +0200 Subject: [PATCH] Updated Destructors, 2nd edition (rest) --- Destructors,-2nd-edition.rest | 526 +--------------------------------- 1 file changed, 1 insertion(+), 525 deletions(-) diff --git a/Destructors,-2nd-edition.rest b/Destructors,-2nd-edition.rest index e43470c..62dbe83 100644 --- a/Destructors,-2nd-edition.rest +++ b/Destructors,-2nd-edition.rest @@ -1,525 +1 @@ -================================== -Nim Destructors and Move Semantics -================================== - -.. contents:: - - -About this document -=================== - -This document describes the upcoming Nim runtime which does -not use classical GC algorithms anymore but is based on destructors and -move semantics. The new runtime's advantages are that Nim programs become -oblivious to the involved heap sizes and programs are easier to write to make -effective use of multi-core machines. As a nice bonus, files and sockets and -the like will not require manual ``close`` calls anymore. - -This document aims to be a precise specification about how -move semantics and destructors work in Nim. - - -Motivating example -================== - -With the language mechanisms described here a custom seq could be -written as: - -.. code-block:: nim - - type - myseq*[T] = object - len, cap: int - data: ptr UncheckedArray[T] - - proc `=destroy`*[T](x: var myseq[T]) = - if x.data != nil: - for i in 0..= x.cap: resize(x) - x.data[x.len] = y - inc x.len - - proc `[]`*[T](x: myseq[T]; i: Natural): lent T = - assert i < x.len - x.data[i] - - proc `[]=`*[T](x: myseq[T]; i: Natural; y: sink T) = - assert i < x.len - x.data[i] = y - - proc createSeq*[T](elems: varargs[T]): myseq[T] = - result.cap = elems.len - result.len = elems.len - result.data = cast[type(result.data)](alloc(result.cap * sizeof(T))) - for i in 0.. 0: - yield it - - -Lent type -========= - -``proc p(x: sink T)`` means that the proc ``p`` takes ownership of ``x``. -To eliminate even more creation/copy <-> destruction pairs, a proc's return -type can be annotated as ``lent T``. This is useful for "getter" accessors -that seek to allow an immutable view into a container. - -The ``sink`` and ``lent`` annotations allow us to remove most (if not all) -superfluous copies and destructions. - -``lent T`` is like ``var T`` a hidden pointer. It is proven by the compiler -that the pointer does not outlive its origin. No destructor call is injected -for expressions of type ``lent T`` or of type ``var T``. - - -.. code-block:: nim - - type - Tree = object - kids: seq[Tree] - - proc construct(kids: sink seq[Tree]): Tree = - result = Tree(kids: kids) - # converted into: - `=move`(result.kids, kids) - - proc `[]`*(x: Tree; i: int): lent Tree = - result = x.kids[i] - # borrows from 'x', this is transformed into: - result = addr x.kids[i] - # This means 'lent' is like 'var T' a hidden pointer. - # Unlike 'var' this cannot be used to mutate the object. - - iterator children*(t: Tree): lent Tree = - for x in t.kids: yield x - - proc main = - # everything turned into moves: - let t = construct(@[construct(@[]), construct(@[])]) - echo t[0] # accessor does not copy the element! - - - -Owned refs -========== - -Let ``W`` be an ``owned ref`` type. Conceptually its hooks look like: - -.. code-block:: nim - - proc `=destroy`(x: var W) = - if x != nil: - assert x.refcount == 0, "dangling unowned pointers exist!" - `=destroy`(x[]) - x = nil - - proc `=`(x: var W; y: W) {.error: "owned refs can only be moved".} - - proc `=move`(x, y: var W) = - if x != y: - `=destroy`(x) - bitwiseCopy x, y # raw pointer copy - y = nil - -Let ``U`` be an unowned ``ref`` type. Conceptually its hooks look like: - -.. code-block:: nim - - proc `=destroy`(x: var U) = - if x != nil: - dec x.refcount - - proc `=`(x: var U; y: U) = - # Note: No need to check for self-assignments here. - if y != nil: inc y.refcount - if x != nil: dec x.refcount - bitwiseCopy x, y # raw pointer copy - - proc `=move`(x, y: var U) = - # 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(...))``. +The most recent version of this document can be found here: https://github.com/nim-lang/Nim/blob/devel/doc/destructors.rst \ No newline at end of file