The issue here is that with `ListNode.prev` and `LinkedList.last` being 
cursors, `LinkedList.first` is the only location keeping the first node alive. 
With the `list.first = node` assignment, there's no more reference that keeps 
the node previously pointed to by `first` alive and the cell is thus 
immediately destroyed -- both `next` and `list.prev` are now dangling pointers.

In order to verify this, you can add the following code after the type 
definitions:
    
    
    # note that this requires splitting the `object` definition into it's own
    # `ListNodeObj` type first
    proc ` =destroy`[T](x: var ListNodeObj[T]) =
      echo "destroying: ", x.val
      writeStackTrace() # log the current call hierarchy
      # the `next` field needs to be destroyed manually
      `=destroy`(x.next)
    
    
    Run

This allows you to trace where and when a `ListNodeObj` cell gets destroyed.

Note that passing a `ref` value to a parameter does _not_ keep the referenced 
location alive (that is, the reference counter is not incremented by one for 
the lifetime of the parameter).

For fixing the issue, you need to swap the two `if`-statements. That way, a 
strong reference to the `prev` node exists (via `node.next`) when assigning to 
`list.first`, preventing it from being freed prematurely.

You likely know this already, but the `.cursor` annotation only has an effect 
on locations with a type having an implicit or explicit destructor (for object 
fields, it also only applies when they're of `ref` or closure type). Since with 
`ref`s are not using destructors with `--mm:refc` (or anything else besides 
`--mm:arc|orc`), the `.cursor` annotation has no effect there.

Reply via email to