Hi I am trying to make the following short snippet compile without crashing or
leaking memory with `--gc:arc`.
import macros
type
Edge = object
neighbor: Node
Node = ref NodeObj
NodeObj = object
neighbors: seq[Edge]
label: string
visited: bool
Graph = object
nodes: seq[Node]
proc `=destroy`(e: var Edge) =
if e.neighbor != nil:
echo "ref ", e.neighbor.label
dispose(e.neighbor)
else: echo "nil ref"
proc `=`(dest: var Edge; source: Edge) =
if dest.neighbor != source.neighbor:
`=destroy`(dest)
dest = source
proc `=destroy`(n: var NodeObj) =
echo "destroy ", n.label
`=destroy`(n.neighbors)
`=destroy`(n.label)
proc `=`(dest: var NodeObj; source: NodeObj) = # never called
echo "copying ", dest.label
# proc `=destroy`(g: var Graph) =
# for i in 0 ..< g.nodes.len:
# echo "destroying ", g.nodes[i].label
# `=destroy`(g.nodes[i])
proc addNode(self: var Graph; label: string): lent Node =
let node = Node(label: label)
self.nodes.add(node)
result = self.nodes[^1]
proc addEdge(self: Graph; source, neighbor: Node) {.nodestroy.} =
let edge = Edge(neighbor: neighbor)
source.neighbors.add(edge)
macro operateOn*(x: typed; calls: untyped) =
result = copyNimNode(calls)
expectKind calls, {nnkStmtList, nnkStmtListExpr}
# non-recursive processing because that's exactly what we need here:
for y in calls:
expectKind y, nnkCallKinds
var call = newTree(y.kind)
call.add y[0]
call.add x
for j in 1 ..< y.len: call.add y[j]
result.add call
proc main =
var graph: Graph
let nodeA {.cursor.} = graph.addNode("a")
let nodeB {.cursor.} = graph.addNode("b")
let nodeC {.cursor.} = graph.addNode("c")
let nodeD {.cursor.} = graph.addNode("d")
let nodeE {.cursor.} = graph.addNode("e")
let nodeF {.cursor.} = graph.addNode("f")
let nodeG {.cursor.} = graph.addNode("g")
let nodeH {.cursor.} = graph.addNode("h")
operateOn(graph):
addEdge(nodeA, neighbor = nodeB)
addEdge(nodeA, neighbor = nodeC)
addEdge(nodeB, neighbor = nodeD)
addEdge(nodeB, neighbor = nodeE)
addEdge(nodeC, neighbor = nodeF)
addEdge(nodeC, neighbor = nodeG)
addEdge(nodeE, neighbor = nodeH)
addEdge(nodeE, neighbor = nodeF)
addEdge(nodeF, neighbor = nodeG)
# addEdge(nodeF, neighbor = nodeF) # cycle
# addEdge(nodeG, neighbor = nodeF) # more cycles
for n in graph.nodes:
echo n.label
main()
echo getAllocStats() # does it always reports (0, 0) !?
assert getOccupiedMem() == 0
Run
The output I get is:
nil ref # lots of these
...
a
b
c
d
e
f
g
h
destroy a
ref b
ref c
destroy b
ref d
ref e
destroy c
ref f
ref g
SIGSEGV: Illegal storage access. (Attempt to read from nil?)
Run
I have tried many things (including the `acyclic` pragma) but I can't seem to
make it work without leaking. However I'm not sure if there 's a bug hiding or
I'm simply doing it wrong, any suggestions?