Ergonomics, Resource Lifetimes, And Object Graphs

This is a smol thought on how resource acquisition and release strategies affect the ergonomics of object lifetimes and the complexity of object graphs. Resource generally means any or all of: memory, objects, files, locks, sockets, et c.

Whenever a program requires complex object graphs, complex lifetimes, or deterministic release of resources, but the language does not make those things ergonomic to express, bugs will inevitably ensue. Examples include (but are not limited to) trying to manage cyclic graphs with RAII (e.g. classic HTML DOM and JavaScript binding implementations); any language with explicit release (e.g. any C program that allocates on the heap, opens files, or holds locks); and programs for which deterministic resource release is critical to correctness but when automatic release is non-deterministic (e.g. Java’s finalize).

The kinds of bugs you might ‘enjoy’ if your language cannot ergonomically express the relationships between your resources vary according to the kinds of resources you need to acquire and release. Examples:

Space inefficiency; use-after-free bugs
Deadlocks; time inefficiency (holding a lock too long); forgetting to release a lock
File descriptor/handle exhaustion due to keeping files open too long; data loss due to failing to synchronize a write before program exit

Everyone needs secure usability, if we hope to consistently produce correct and safe software.

For simple examples of what I mean by these strategies (except for GC, which hopefully everyone is familiar with from experience in Go, Python, Java, et c.; and global static analysis, which you can read about as it is used in Rust), see These examples use memory as the resource, but keep in mind the same idea applies to other kinds of resources.

Allocation StrategyBrief CharacterizationErgonomic For LifetimesErgonomic for Object Graph ComplexitiesGuarantee Of Resource Release
caller acquires, caller releases implicitlyStatic or stack storage, released on termination or return (respectively)static, stack-localplain old data (no references); or possible references to objects of same or longer lifetimestypically deterministic
caller acquires, caller releases explicitlyHeap or other external resource released with an explicit call (free, delete, close, et c.)not ergonomicnot ergonomicdeterministic; subject to programmer error
callee acquires, caller releases implicitlyTypes (e.g. string, vector, scoped_lock, et c.) that manage resources internally and release them in their destructors, which are implicitly called at the end of the scopestatic, stack-localacyclic graph; possible references to objects of same or longer lifetimestypically deterministic
callee acquires, caller releases explicitlyFunctions and types that wrap resource acquisition but still require explicit release, e.g. asprintf/free, setvbuf, open/close, et c.not ergonomicnot ergonomicdeterministic; subject to programmer error
anyone acquires, anyone releases when ref count is 0Reference-counting smart pointers, like shared_ptr, Rc, and Arc.anyacyclic graph; possible references to objects of same or longer lifetimesnon-deterministic; subject to leaks via cyclic references
anyone acquires, global garbage collectionLanguages with a garbage collector in its runtime, such as Lisp, JavaScript, Python, Go, et c.anyarbitrary graph of objects of arbitrary lifetimesnon-deterministic; subject to leaks if program terminates before collection completes
anyone acquires, global static analysis ensures implicit releaseLanguages that statically analyze explicit and/or deduced lifetime annotations that become part of an object’s type (see e.g. linear typing).inversely proportional to the complexity of entangled lifetimesarbitrary graph of objects with references to objects of same or longer lifetimestypically deterministic

† Although one can imagine implicit but non-deterministic release, I don’t know of a language that has it. RAII is the generalized form of these strategies.

‡ Go’s defer is a special case: an explicit expression of non-deterministic release.

* Arena acquisition and release (when resources acquired during a given epoch are released together) can ease the difficulty that arises when objects of different lifetimes become entangled in a graph. The costs are that resource release within an arena might not be deterministic, and that some resources may have longer lifetimes than strictly necessary.