nim-wiki/NEP-2-Catching-up-with-C---...

55 lines
2.9 KiB
Markdown
Raw Normal View History

In the `c++` model, there are four steps that each object has to go through it its live cycle.
1. Allocation
2. Initialization
3. Deinitialization
4. Deallocation
A `new` is allocation on the heap, and initialization, a delete is deinitialization and deallocation. A _placement new_ is only initialization, and a _placement delete_ is only deinitialization. `malloc` is old allocation and `free` is only deallocation. The programmer is resposible, that everything is called symmetrically for every object. Every allocation needs its deallocation, and every initialization needs it's deinitialization. Every wrong usage is undefined behaviour. Objects may never be initialized twice, or deallocated without deinitialization.
The Nim compiler ensures all object initialization by setting the entire object memory to zero before usage, and not using deinitialization (without experimental pragma). This works fine for POD (plain old data) types, and even for pointer members assuming that pointers don't express ownership, but it doesn't work, when the object have some sort of ownership to some kind of resources that need to be freed when the object is not used anymore.
My presumption is, that everybody agrees that the language should do everything possible to prevent an asymmetric calling of the four procedures by accident. I am not saying it should be impossible to shoot yourself in the foot, because sometimes people really want to shoot themself in the foot and they need to learn that it hurts by doing so. In current state the nim compiler with experimental features enabled, has some defaults I would not agree on:
```Nim
type
MyType = object
a,b: int
proc `=destroy`(v: var MyType) =
echo "destroy ", v
proc main() =
var v = MyType(a: 1, b:2)
discard MyType(a: 3, b:4)
v = MyType(a:5, b:6)
# let k = MyType(a: 18, b: 19)
echo "end of main"
main()
echo "end of program"
## output
# end of main
# destroy (a: 5, b: 6)
# end of program
```
The obvious problem is, that the discarded value is not deinitialized at all. This should not be possible so easily. The less obvious problem is, that there are two initializations for v. One is with the values (1,2) and the other one is with the values (5,6), but there is only one deinitialization.
My suggestion would be to trigger deinitialization of the left operand of the assignment operator, before assignment takes place. Meaning that `v = MyType(a:5, b:6)` compiles to something equivalent to this pseudocode: `destroy(v); init(v, MyType(...))`. This would also solve the problem, that current implementors of the `=` operator have to implement some logic that needs to determine weather the left operand is already initialized or not, because the compiler would ensure that the left operand is not initialized.
```Nim
type
MyType = object
resources: pointer
proc `=`(dst: var MyType; src: MyType) =
if dst.resources != nil:
freeResources(dst.resources)
dst.resources = src
```