Noncombatant 😚 About 🤓 Other Writing 🧐 Bandcamp 🎵 GitHub 💻 Mastodon 🐘 Bluesky 🟦
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
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.
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 ||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
(||not ergonomic＊||not ergonomic||deterministic; subject to programmer error|
|callee acquires, caller releases implicitly||Types (e.g. ||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. ||not ergonomic＊||not ergonomic||deterministic‡; subject to programmer error|
|anyone acquires, anyone releases when ref count is 0||Reference-counting smart pointers, like ||any||acyclic graph; possible references to objects of same or longer lifetimes||non-deterministic; subject to leaks via cyclic references|
|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.
defer is a special case: an explicit expression of
＊ 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.