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:
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 examples.cc. These examples use memory as the resource, but keep in mind the same idea applies to other kinds of resources.
Allocation Strategy | Brief Characterization | Ergonomic For Lifetimes | Ergonomic for Object Graph Complexities | Guarantee Of Resource Release |
---|---|---|---|---|
caller acquires, caller releases implicitly | Static or stack storage, released on termination or return
(respectively) | static, stack-local | plain old data (no references); or possible references to objects of same or longer lifetimes | typically deterministic† |
caller acquires, caller releases explicitly | Heap or other external resource released with an explicit call
(free , delete , close , et c.) | not ergonomic* | not ergonomic | deterministic; subject to programmer error |
callee acquires, caller releases implicitly | Types (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 scope | static, stack-local | acyclic graph; possible references to objects of same or longer lifetimes | typically deterministic† |
callee acquires, caller releases explicitly | Functions and types that wrap resource acquisition but still require
explicit release, e.g. asprintf /free , setvbuf ,
open /close , et c. | not ergonomic* | not ergonomic | deterministic‡; subject to programmer error |
anyone acquires, anyone releases when ref count is 0 | Reference-counting smart pointers, like shared_ptr , Rc , and
Arc . | any | acyclic graph; possible references to objects of same or longer lifetimes | non-deterministic; subject to leaks via cyclic references |
anyone acquires, global garbage collection | Languages with a garbage collector in its runtime, such as Lisp, JavaScript, Python, Go, et c. | any | arbitrary graph of objects of arbitrary lifetimes | non-deterministic; subject to leaks if program terminates before collection completes |
anyone acquires, global static analysis ensures implicit release | Languages 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 lifetimes | arbitrary graph of objects with references to objects of same or longer lifetimes | typically 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.