I'm studying the various implementation of get_password in this file:
# minimal.nim
type Wrapper = object
password: int
proc `=destroy`*(self: var Wrapper) =
echo "=destroy(", self.password, ")"
proc `=sink`*(dest: var Wrapper, source: Wrapper) =
echo "=sink(", dest.password, ", ", source.password, ")"
proc `=copy`(dest: var Wrapper, source: Wrapper) =
echo "=copy(", dest.password, ", ", source.password, ")"
dest.password = source.password
func get_password*(): Wrapper =
when defined(zero):
var b: Wrapper
return b
elif defined(one):
{.emit: "void tamper(int *x) { *x = 11; }".}
proc tamper(x: ptr int): void {.importc.}
var b: Wrapper
tamper(b.password.addr)
return b
elif defined(two):
var b: Wrapper
b.password = 212
return b
elif defined(three):
var b: Wrapper = Wrapper(password: 333)
return b
elif defined(four):
var b: ref Wrapper
new(b)
b[].password = 5505
return b[]
elif defined(five):
result
elif defined(six):
result.password = 6006
elif defined(seven):
var b: ref Wrapper
new(b)
b[].password = 7
result = b[]
else:
{.fatal: "bad".}
block:
var w: Wrapper = get_password()
echo "The final password is ", $w.password
And the output of running each variant:
user@user:~$ nim c --forceBuild:on --hints:off --gc:arc --expandArc:get_password --run -d:zero minimal.nim
--expandArc: get_password
var b_cursor: Wrapper
return `=copy`(result, b_cursor)
-- end of expandArc ------------------------
=copy(0, 0)
The final password is 0
=destroy(0)
user@user:~$ nim c --forceBuild:on --hints:off --gc:arc --expandArc:get_password --run -d:one minimal.nim
--expandArc: get_password
{.emit: "void tamper(int *x) { *x = 11; }".}
proc tamper(x: ptr int): void {.importc.}
var b_cursor: Wrapper
tamper(addr(b_cursor.password))
return `=copy`(result, b_cursor)
-- end of expandArc ------------------------
=copy(0, 11)
The final password is 11
=destroy(11)
user@user:~$ nim c --forceBuild:on --hints:off --gc:arc --expandArc:get_password --run -d:two minimal.nim
--expandArc: get_password
var b
try:
b.password = 212
return
result = b
wasMoved(b)
finally:
`=destroy`(b)
-- end of expandArc ------------------------
=destroy(0)
The final password is 212
=destroy(212)
user@user:~$ nim c --forceBuild:on --hints:off --gc:arc --expandArc:get_password --run -d:three minimal.nim
--expandArc: get_password
var b
try:
b = Wrapper(password: 333)
return
result = b
wasMoved(b)
finally:
`=destroy`(b)
-- end of expandArc ------------------------
=destroy(0)
The final password is 333
=destroy(333)
user@user:~$ nim c --forceBuild:on --hints:off --gc:arc --expandArc:get_password --run -d:four minimal.nim
--expandArc: get_password
var b
try:
`=destroy`(b)
new(b)
b[].password = 5505
return `=copy`(result, b[])
finally:
`=destroy`(b)
-- end of expandArc ------------------------
=copy(0, 5505)
=destroy(5505)
The final password is 5505
=destroy(5505)
user@user:~$ nim c --forceBuild:on --hints:off --gc:arc --expandArc:get_password --run -d:five minimal.nim
--expandArc: get_password
-- end of expandArc ------------------------
The final password is 0
=destroy(0)
user@user:~$ nim c --forceBuild:on --hints:off --gc:arc --expandArc:get_password --run -d:six minimal.nim
--expandArc: get_password
result.password = 6006
-- end of expandArc ------------------------
The final password is 6006
=destroy(6006)
user@user:~$ nim c --forceBuild:on --hints:off --gc:arc --expandArc:get_password --run -d:seven minimal.nim
--expandArc: get_password
var b
`=destroy`(b)
new(b)
b[].password = 7
`=copy`(result, b[])
`=destroy`(b)
-- end of expandArc ------------------------
=copy(0, 7)
=destroy(7)
The final password is 7
=destroy(7)
user@user:~$ nim c --forceBuild:on --hints:off --gc:arc --expandArc:get_password --run -d:eight minimal.nim
/home/user/minimal.nim(47, 12) Error: bad
And this raised some questions.
It would appear that the reason -d:zero invokes =copy but -d:two and -d:three have a = + wasMoved + =destroy sequence is because only -d:two and -d:three modify the return value before returning.
Should -d:one also have a = + wasMoved + =destroy rather than using a =copy? This seems like a bug because the C function could have been, for example, calling malloc and storing the pointer into Wrapper but since =destroy(b) is never called (and since a standard implementation of =copy(a, b) invokes =destroy(a) but not =destroy(b)), that memory might never get freed if =copy does what it is supposed to and performs a "deep copy" of the pointer and its contents. Does the C function need to be marked with something special to indicate to Nim that it might alter the contents?
Why does -d:zero call =copy instead of = + wasMoved + =destroy? The latter is more efficient if =copy performs deep copies.
-d:zero and -d:five do the same thing but the --expandArc of -d:five is simpler. Same with -d:two / -d:six and -d:four / -d:seven (-d:seven has an extra try block).
Is that just a quirk of how --expandArc outputs things, or is -d:five truly more efficient than -d:zero?
--expandArc outputs `=destroy`(b) no matter if b is of type Wrapper or ref Wrapper. See -d:three vs. -d:four.
Is something that can be improved? Either by marking the =destroy differently or explicitly including the type of b in the --expandArc output?