Memory Management: ARC, ORC and Destructors

31.1 Memory Management Options (--mm:)

orc (default in Nim 2) ARC + cycle collector: deterministic, works for cyclic data

Pure reference counting with move semantics. Cycles leak unless broken manually. Best for hard real-time and embedded.

Legacy deferred reference counting GC (default in Nim 1.x) Legacy tracing GC Boehm conservative GC Go's GC (for interop) No memory management (manual)

31.2 How ARC/ORC Work

The compiler injects destructor calls (=destroy), copies (=copy), and moves (=sink) based on static analysis of variable lifetimes.

Reference counts are not atomic by default. Threads share data via Isolated, channels, or --threads:on with atomic refcounts... see Module 33.

Last-read optimization: the last use of a variable becomes a move instead of a copy.

ORC adds a trial-deletion cycle collector that only examines potentially cyclic types. Mark types {.acyclic.} to skip it.

--expandArc:procName shows the injected memory operations:

nim c --mm:orc --expandArc:main app.nim

31.3 Lifetime-Tracking Hooks

Disable copying: proc =copy(dst: var Buffer, src: Buffer) {.error.} makes the type move-only (like Rust's non-Copy or C++ unique_ptr).

type
  Buffer = object
    data: ptr UncheckedArray[byte]
    len: int

proc `=destroy`(b: Buffer) =                     # Nim 2 signature (non-var)
  if b.data != nil: dealloc(b.data)

proc `=wasMoved`(b: var Buffer) =
  b.data = nil

proc `=copy`(dst: var Buffer, src: Buffer) =
  if dst.data == src.data: return
  `=destroy`(dst)
  `=wasMoved`(dst)
  dst.len = src.len
  if src.data != nil:
    dst.data = cast[ptr UncheckedArray[byte]](alloc(src.len))
    copyMem(dst.data, src.data, src.len)

proc `=sink`(dst: var Buffer, src: Buffer) =     # move: steal resources
  `=destroy`(dst)
  dst.data = src.data
  dst.len = src.len

proc `=dup`(src: Buffer): Buffer = ...           # Nim 2: optimized copy for construction
proc `=trace`(b: var Buffer; env: pointer) = ... # ORC: trace refs inside custom containers

proc newBuffer(n: int): Buffer =
  Buffer(data: cast[ptr UncheckedArray[byte]](alloc0(n)), len: n)

31.4 RAII Patterns

Files, sockets, locks, and GPU handles are released automatically by destructors when the owner goes out of scope.

defer for ad-hoc cleanup.

31.5 Breaking Cycles Under ARC

The {.cursor.} pragma for non-owning back-references (parent pointers):

type Node = ref object
  children: seq[Node]
  parent {.cursor.}: Node   # doesn't increment the refcount

31.6 Memory Debugging

-d:useMalloc (use the system malloc, which helps with Valgrind/ASan), --passC:-fsanitize=address --passL:-fsanitize=address, getOccupiedMem(), -d:nimArcDebug, GC_fullCollect() (ORC cycle collection), GC_disableOrc.