# Why manual memory management still matters

[[dayvster-blog|Dayvi Schuster]]'s case for learning manual memory management even if your day job is JS/Python/Go: it deepens your understanding of how systems actually work, and it opens the door to the modern lower-level languages — Zig, Odin, C3 — where manual memory is a core feature rather than a fallback. The piece is structured as pitfalls, then mental model, then language-by-language treatment.

## Pitfalls

Each illustrated by the same book-lending analogy, which holds up surprisingly well:

- **Dangling pointer** — pointer to memory already freed.
- **Double free** — freeing memory that's already been freed.
- **Use after free** — reading or writing memory after free.
- **Memory leak** — allocating and never freeing.
- **Buffer overflow** — writing past the allocated region.
- **Memory fragmentation** — many small allocations and frees leaving gaps no single allocation can use.
- **Incorrect ownership** — multiple pointers to the same memory, each trying to modify it.

All of these end at "crashes, data corruption, and security vulnerabilities" — Dayvi's point being that the failure modes are uniform and severe.

## Mental model

Two regions:

- **Stack** — LIFO, fixed size, fast. Local variables and function call frames.
- **Heap** — dynamic, flexible, slower. The unstructured pile.

The dichotomy is enough to start reasoning about where allocations happen and what their lifetime is.

## Tools

Standard list: debuggers, sanitizers (AddressSanitizer for OOB and UAF, LeakSanitizer for leaks), logging and profiling, static analysis (Clang's static analyzer), and code review. Dayvi's preference is to lean on these aggressively rather than try to be careful manually.

## Best practices

- Always free what you allocate.
- Use smart pointers in languages that have them (C++).
- Favor scope-based cleanup: C++ RAII, Zig `defer`.
- Test small and often.
- Document the memory-management decisions — your future self will not remember why.

## Language-by-language

**C** — `malloc`/`free`, nothing built-in, third-party tools fill the gap. Valgrind and AddressSanitizer are the canonical pair.

**Fil-C** — modified LLVM/Clang toolchain that adds memory safety to C/C++ at minimal cost without rewriting code. Instruments pointer ops, links against a custom runtime, uses a modified standard library and a memory-safe ABI. `int x = arr[i]` compiles to something like `check_pointer_bounds(arr, i); load_value(arr + i)`. The full model is covered separately in [[simplified-model-of-fil-c]].

**C++** — smart pointers wrap raw pointers and manage lifetime automatically. Modern C++ strongly encourages containers (`std::vector`, `std::string`, `std::map`) over raw allocation. Trouble starts when you write `int* arr = new int[10]` instead of `std::vector<int> arr(10)`.

**Zig** — Dayvi's favorite. Manual memory is core. Allocators are explicit values you pass around; `defer` schedules cleanup at block end. See [[zig-functional-programmers]] for the broader case and [[comptime]] for Zig's main metaprogramming mechanism.

**Odin** — explicit allocator-passing like Zig, plus `context.allocator` as a convenient default. Easy to switch strategies — arena for short-lived data, custom allocator for performance-sensitive subsystems — without rewriting half the program. Predictable code: you know where allocations happen and who owns them.

**C3** — leans on scope-based allocation and deterministic cleanup. Lifetimes tied to scopes; helpers like `mem::new_array` keep you out of raw allocation primitives; temporary/arena allocators encouraged for short-lived data. Targets the biggest source of memory bugs — lifetime confusion. See [[c3-lang]] and [[unsigned-sizes-c3-mistake]] for the design context.

**Rust** — Dayvi's contrarian take: not actually a manual-memory language, just a compile-time compiler-rules system that shifts responsibility off the developer the same way GC does. He cites the "memory leaks are memory-safe in Rust" framing and the Cloudflare Rust outage as evidence that Rust hasn't delivered on what it promised. The note is short and admits it's a topic for another post. (This view is contested elsewhere in the wiki — see [[safe-gc]] and [[rust-async-trait-sync-bound]] for working Rust patterns.)

## When manual is worth reaching for

Three categories:

- **Performance-critical code** — games, trading, real-time audio, high-frequency networking. GC pauses and unpredictable allocation become disqualifying.
- **Embedded systems** — limited memory and CPU, where GC may simply not fit.
- (Cut off in the source — implicitly, anywhere predictability matters more than ergonomics.)

## Where this fits

The piece is a primer aimed at people coming from GC languages, sitting next to [[zig-functional-programmers]] (the Haskeller's case for Zig), [[simplified-model-of-fil-c]] (the safety retrofit for C), and [[stroustrup-memory-leaks]] (the C++ FAQ on hiding allocation in containers). Together they map the current state of the "memory management without GC" conversation across new and old systems languages.
