defer stays the answer; drop is deferred and the shim can count what leaks
This commit is contained in:
parent
a3fccf440c
commit
a1827adb66
43
NEXT.md
43
NEXT.md
@ -362,6 +362,49 @@ Already banked, and it means classes are less work than plan.org implies: **a ge
|
||||
whose body is a dispatch table, which a reload extends. That is the expensive half of method dispatch, and it is built
|
||||
and tested.
|
||||
|
||||
**Resource cleanup: `defer` stays the answer. `drop` is not built, and `with-cleanup` is not either.** Reached by
|
||||
working the case through rather than by preference, so the reasoning is worth keeping.
|
||||
|
||||
`drop` was specified in `spec-memory.md` this morning. **This amends it: the hook is deferred, not built.** Three things
|
||||
decided against it. It runs code somewhere the reader is not looking, which is the C++ behaviour the author explicitly
|
||||
does not want. It would not even cover the motivating case — `Image` and `Texture2D` are *raylib's* types, and attaching
|
||||
a hook to a foreign type is its own unsolved design question. And its one real advantage, cascading through a container,
|
||||
is the case `Handle` is about to make rare: entities holding handles hold numbers, not resources.
|
||||
|
||||
`with-cleanup` / `unwind-protect` was also put and rejected: awkward with several resources, and it reads worse than
|
||||
what already exists. The raylib begin/end pairs that seemed to motivate it are a macro problem, not a primitive one —
|
||||
`with-drawing` and `with-mode-2d` are three-line macros once the expander lands.
|
||||
|
||||
**What to build instead is small: relax where `defer` may be written.** It is refused today inside a `let`, a loop or a
|
||||
branch. The loop and branch refusals are right — `defer` is a *compile-time* construct, the cleanup copied into every
|
||||
exit path, so "maybe registered" is not expressible and a loop body would fire once at function exit instead of once per
|
||||
iteration. But **a `let` at the top level of a function body has exactly the function's extent** and always registers,
|
||||
so it is as safe as function scope and is refused for a reason that does not apply to it. Relaxing it gives:
|
||||
|
||||
```
|
||||
(defn load-brush []
|
||||
(let [sheet (rl/load-image-from-memory ".png" brush-bytes)]
|
||||
(defer (rl/unload-image sheet))
|
||||
(set brush (rl/load-texture-from-image sheet))
|
||||
(rl/image-flip-horizontal (addr sheet))
|
||||
(set brush-mirrored (rl/load-texture-from-image sheet))))
|
||||
```
|
||||
|
||||
Several resources are several defers, released in reverse, visible in acquisition order. A container of resources is an
|
||||
ordinary loop inside the defer body — the manual cascade, three lines, at one level of nesting.
|
||||
|
||||
**Odin, for the record**, has no destructors, no drop and no finalizers: `delete` frees container memory and nothing
|
||||
else, and resource release is `defer` at the acquisition site. That idiom does not transfer directly only because
|
||||
Odin's `defer` is block-scoped; the relaxation above recovers most of it.
|
||||
|
||||
**The safety net, and the better use of effort: a debug tracking allocator.** ASan's leak detection covers memory
|
||||
*instrumented* code allocated — the Flan allocator, and it is already wired up and clean. It does **not** cover a leaked
|
||||
texture, because that memory belongs to uninstrumented raylib, which is the same reason the sanitizer sweep treats the
|
||||
windowed examples as noise. But every raylib call goes through a *generated* wrapper, so a dev build can count
|
||||
acquisitions against releases at that boundary and report what is still held at exit, by name. No hook, no type
|
||||
annotation, nothing running at a distance — it does not change how code is written, it reports when something was
|
||||
forgotten.
|
||||
|
||||
## Blocked and unfinished
|
||||
|
||||
Everything below was found, decided or half-built and then stopped. Each says what blocks it. Nothing here is a
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user