diff --git a/doc/destructors.rst b/doc/destructors.rst index a3fd7514eb..28d8fa5f9f 100644 --- a/doc/destructors.rst +++ b/doc/destructors.rst @@ -222,7 +222,14 @@ The prototype of this hook for a type `T` needs to be: `env` is used by ORC to keep track of its internal state, it should be passed around to calls of the built-in `=trace` operation. -Usually there will only be a need for a custom `=trace` when a custom `=destroy` that deallocates manually allocated resources is also used, and then only when there is a chance of cyclic references from items within the manually allocated resources when it is desired that `--gc:orc` be able to break and collect these cyclic referenced resources. Currently however, there is a mutual use problem in that whichever of `=destroy`/`=trace` is used first will automatically create a version of the other which will then conflict with the creation of the second of the pair. The work around for this problem is to forward declare the second of the "hooks" to prevent the automatic creation. +Usually there will only be a need for a custom `=trace` when a custom `=destroy` that deallocates +manually allocated resources is also used, and then only when there is a chance of cyclic +references from items within the manually allocated resources when it is desired that `--gc:orc` +be able to break and collect these cyclic referenced resources. Currently however, there is a +mutual use problem in that whichever of `=destroy`/`=trace` is used first will automatically +create a version of the other which will then conflict with the creation of the second of the +pair. The work around for this problem is to forward declare the second of the "hooks" to +prevent the automatic creation. The general pattern in using `=destroy` with `=trace` looks like: @@ -696,43 +703,22 @@ should eventually be replaced by a better solution. Copy on write ============= -String literals are implemented as [copy-on-write](https://en.wikipedia.org/wiki/Copy-on-write). +String literals are implemented as "copy on write". When assigning a string literal to a variable, a copy of the literal won't be created. Instead the variable simply points to the literal. The literal is shared between different variables which are pointing to it. The copy operation is deferred until the first write. -.. code-block:: nim - var x = "abc" # no copy - var y = x # no copy - -The string literal "abc" is stored in static memory and not allocated on the heap. -The variable `x` points to the literal and the variable `y` points to the literal too. -There is no copy during assigning operations. +For example: .. code-block:: nim var x = "abc" # no copy var y = x # no copy y[0] = 'h' # copy -The program above shows when the copy operations happen. -When mutating the variable `y`, the Nim compiler creates a fresh copy of `x`, -the variable `y` won't point to the string literal anymore. -Instead it points to the copy of `x` of which the memory can be mutated -and the variable `y` becomes a mutable string. -.. Note:: The abstraction fails for `addr x` because whether the address is going to be used for mutations is unknown. - -Let's look at a silly example demonstrating this behaviour: - -.. code-block:: nim - var x = "abc" - var y = x - - moveMem(addr y[0], addr x[0], 3) - -The program fails because we need to prepare a fresh copy for the variable `y`. -`prepareMutation` should be called before the address operation. +The abstraction fails for `addr x` because whether the address is going to be used for mutations is unknown. +`prepareMutation` needs to be called before the "address of" operation. For example: .. code-block:: nim var x = "abc" @@ -741,18 +727,3 @@ The program fails because we need to prepare a fresh copy for the variable `y`. prepareMutation(y) moveMem(addr y[0], addr x[0], 3) assert y == "abc" - -Now `prepareMutation` solves the problem. -It manually creates a fresh copy and makes the variable `y` mutable. - -.. code-block:: nim - var x = "abc" - var y = x - - prepareMutation(y) - moveMem(addr y[0], addr x[0], 3) - moveMem(addr y[0], addr x[0], 3) - moveMem(addr y[0], addr x[0], 3) - assert y == "abc" - -No matter how many times `moveMem` is called, the program compiles and runs. diff --git a/doc/manual.rst b/doc/manual.rst index 7976ffe0fb..af163bceb2 100644 --- a/doc/manual.rst +++ b/doc/manual.rst @@ -2400,121 +2400,38 @@ describe the type checking done by the compiler. Type equality ------------- + Nim uses structural type equivalence for most types. Only for objects, -enumerations and distinct types name equivalence is used. The following -algorithm, *in pseudo-code*, determines type equality: - -.. code-block:: nim - proc typeEqualsAux(a, b: PType, - s: var HashSet[(PType, PType)]): bool = - if (a,b) in s: return true - incl(s, (a,b)) - if a.kind == b.kind: - case a.kind - of int, intXX, float, floatXX, char, string, cstring, pointer, - bool, nil, void: - # leaf type: kinds identical; nothing more to check - result = true - of ref, ptr, var, set, seq, openarray: - result = typeEqualsAux(a.baseType, b.baseType, s) - of range: - result = typeEqualsAux(a.baseType, b.baseType, s) and - (a.rangeA == b.rangeA) and (a.rangeB == b.rangeB) - of array: - result = typeEqualsAux(a.baseType, b.baseType, s) and - typeEqualsAux(a.indexType, b.indexType, s) - of tuple: - if a.tupleLen == b.tupleLen: - for i in 0..a.tupleLen-1: - if not typeEqualsAux(a[i], b[i], s): return false - result = true - of object, enum, distinct: - result = a == b - of proc: - result = typeEqualsAux(a.parameterTuple, b.parameterTuple, s) and - typeEqualsAux(a.resultType, b.resultType, s) and - a.callingConvention == b.callingConvention - - proc typeEquals(a, b: PType): bool = - var s: HashSet[(PType, PType)] = {} - result = typeEqualsAux(a, b, s) - -Since types are graphs which can have cycles, the above algorithm needs an -auxiliary set `s` to detect this case. - - -Type equality modulo type distinction -------------------------------------- - -The following algorithm (in pseudo-code) determines whether two types -are equal with no respect to `distinct` types. For brevity the cycle check -with an auxiliary set `s` is omitted: - -.. code-block:: nim - proc typeEqualsOrDistinct(a, b: PType): bool = - if a.kind == b.kind: - case a.kind - of int, intXX, float, floatXX, char, string, cstring, pointer, - bool, nil, void: - # leaf type: kinds identical; nothing more to check - result = true - of ref, ptr, var, set, seq, openarray: - result = typeEqualsOrDistinct(a.baseType, b.baseType) - of range: - result = typeEqualsOrDistinct(a.baseType, b.baseType) and - (a.rangeA == b.rangeA) and (a.rangeB == b.rangeB) - of array: - result = typeEqualsOrDistinct(a.baseType, b.baseType) and - typeEqualsOrDistinct(a.indexType, b.indexType) - of tuple: - if a.tupleLen == b.tupleLen: - for i in 0..a.tupleLen-1: - if not typeEqualsOrDistinct(a[i], b[i]): return false - result = true - of distinct: - result = typeEqualsOrDistinct(a.baseType, b.baseType) - of object, enum: - result = a == b - of proc: - result = typeEqualsOrDistinct(a.parameterTuple, b.parameterTuple) and - typeEqualsOrDistinct(a.resultType, b.resultType) and - a.callingConvention == b.callingConvention - elif a.kind == distinct: - result = typeEqualsOrDistinct(a.baseType, b) - elif b.kind == distinct: - result = typeEqualsOrDistinct(a, b.baseType) +enumerations and distinct types and for generic types name equivalence is used. Subtype relation ---------------- -If object `a` inherits from `b`, `a` is a subtype of `b`. This subtype -relation is extended to the types `var`, `ref`, `ptr`: -.. code-block:: nim - proc isSubtype(a, b: PType): bool = - if a.kind == b.kind: - case a.kind - of object: - var aa = a.baseType - while aa != nil and aa != b: aa = aa.baseType - result = aa == b - of var, ref, ptr: - result = isSubtype(a.baseType, b.baseType) +If object `a` inherits from `b`, `a` is a subtype of `b`. -.. XXX nil is a special value! +This subtype relation is extended to the types `var`, `ref`, `ptr`. +If `A` is a subtype of `B` and `A` and `B` are `object` types then: +- `var A` is a subtype of `var B` +- `ref A` is a subtype of `ref B` +- `ptr A` is a subtype of `ptr B`. +**Note**: In later versions of the language the subtype relation might +be changed to *require* the pointer indirection in order to prevent +"object slicing". Convertible relation -------------------- + A type `a` is **implicitly** convertible to type `b` iff the following algorithm returns true: .. code-block:: nim proc isImplicitlyConvertible(a, b: PType): bool = - if isSubtype(a, b) or isCovariant(a, b): + if isSubtype(a, b): return true if isIntLiteral(a): return b in {int8, int16, int32, int64, int, uint, uint8, uint16, @@ -2540,7 +2457,12 @@ algorithm returns true: result = b == pointer of string: result = b == cstring + of proc: + result = typeEquals(a, b) or compatibleParametersAndEffects(a, b) +We used the predicate `typeEquals(a, b)` for the "type equality" property +and the predicate `isSubtype(a, b)` for the "subtype relation". +`compatibleParametersAndEffects(a, b)` is currently not specified. Implicit conversions are also performed for Nim's `range` type constructor. @@ -2563,7 +2485,9 @@ algorithm returns true: proc isExplicitlyConvertible(a, b: PType): bool = result = false if isImplicitlyConvertible(a, b): return true - if typeEqualsOrDistinct(a, b): return true + if typeEquals(a, b): return true + if a == distinct and typeEquals(a.baseType, b): return true + if b == distinct and typeEquals(b.baseType, a): return true if isIntegralType(a) and isIntegralType(b): return true if isSubtype(a, b) or isSubtype(b, a): return true