*The Wrong Memory* has examined memory safety from the perspective of C: how values become sizes, sizes become allocations, pointers identify locations, lengths determine ranges, and objects acquire and lose lifetimes. At every stage, the program depends on facts that must remain true for a memory operation to be valid.
In C, most of those facts are implicit.
The programmer knows that an integer represents a number of elements rather than bytes. The programmer knows that a pointer refers to a particular allocation. The programmer knows how much capacity remains. The programmer knows that an object is still alive. The programmer knows who owns it and who is permitted to modify it.
The compiler often knows none of these things.
This leads to a broader question, one that reaches beyond C and into the design of programming languages themselves:
> **What makes this memory operation safe, and how does the language know?**
That question is a useful lens through which to view the evolution of systems programming languages. The answers differ considerably. Some languages use garbage collection to remove explicit lifetime management. Others use ownership, borrowing and lifetimes. Some make bounds and ranges explicit. Others provide contracts and formal verification. Some express permissions through capabilities. Some combine several of these mechanisms while retaining an explicitly unsafe subset for low-level operations.
What they have in common is a change in where the safety argument lives.
Instead of asking the programmer to remember every fact necessary to justify a memory operation, modern language design increasingly attempts to **represent, check, enforce or automatically maintain those facts**.
That is a profound change in the programming model.
Consider a familiar C operation:
memcpy(destination, source, length);
For this operation to be valid, a substantial collection of conditions must hold.
The source must identify a live object containing at least `length` readable bytes. The destination must identify a live object containing at least `length` writable bytes. `length` must represent the intended quantity. The pointers must identify the intended objects. The operation must satisfy the requirements of the particular memory primitive.
Yet the types involved may communicate very little of this to the compiler.
A pointer is, broadly speaking, an address-bearing value.
A `size_t` is an integer.
The relationship between them is largely a programmer-maintained fact.
The programmer's intended meaning is much richer:
> This reference identifies this particular object, at this particular location, for this particular amount of memory, while that object remains alive and accessible to this operation.
The difference between those two descriptions is one of the recurring sources of C memory errors.
The problem is therefore deeper than the existence of dangerous functions. It is that C allows important safety properties to exist primarily as **assumptions**.
A programmer may know that a pointer is valid without the type system knowing why. A programmer may know that a length corresponds to a particular buffer without the language expressing that relationship. A programmer may know that an object will remain alive until a particular operation without the compiler being able to establish it.
The program consequently contains a safety argument that exists partly outside the formal structure of the program itself.
That argument can be correct.
It can also silently become false.
The analysis in *The Wrong Memory* can be understood in terms of several fundamental properties:
* **Object:** What object is being accessed?
* **Location:** Where within that object is the operation taking place?
* **Amount:** How much memory is being accessed?
* **Lifetime:** Does the object still exist?
* **Ownership:** Does this code have the right to access, modify or destroy it?
These questions are closely related, but they are not identical.
A pointer can identify the wrong object even when its numerical value is perfectly valid. A length can be wrong even when the pointer is correct. An address and length can both be correct while the object has already ceased to exist. An object can be alive and correctly addressed while the current code nevertheless lacks the authority to modify or destroy it.
This multidimensional nature of memory safety is important for programming-language design.
There is no single feature that solves all of these problems.
Instead, languages can choose where and how each safety property should be established.
One of the most successful approaches is also one of the simplest:
**Do not require ordinary application code to determine when an object can be destroyed.**
Languages such as Java, C#, Go and many others use garbage collection to manage object lifetimes.
This eliminates an entire category of errors examined in *The Wrong Memory*.
In C, a typical lifetime failure looks like:
allocate
↓
use
↓
free
↓
dangling pointer
↓
use again
The problem arises because the programmer is responsible for making the `free()` decision correctly and for ensuring that no later operation retains an invalid reference.
In a tracing garbage-collected system, ordinary program code generally does not make that decision explicitly. The runtime determines when an object is no longer reachable and can reclaim it.
This does not make the program universally safe or correct. A garbage-collected program can still calculate the wrong index, select the wrong object, expose sensitive information, retain objects unnecessarily, or implement defective logic.
But it removes a fundamental category of temporal memory errors.
The design lesson is important:
> **If the programmer does not have to establish a property manually, the programmer cannot accidentally establish that property incorrectly.**
Garbage collection therefore answers one of the questions raised by this book by removing it from ordinary application code:
> When is it safe to destroy this object?
The runtime answers that question.
A second approach is to make the relationship between an object and its accessible extent explicit.
An ordinary C pointer does not inherently carry its bounds. This is why interfaces of the form
void process(char *data, size_t length);
are so common—and so dependent on convention.
The programmer must preserve the relationship between `data` and `length`.
A safer abstraction can instead represent a bounded region as a single value: a slice, span, array view or equivalent construct.
The difference is more than syntactic.
Instead of:
address + separately maintained length
the abstraction represents:
a bounded view of an object
The address and the information required to interpret that address safely remain connected.
Rust's slices are a prominent example. Rust's ownership model combines with borrowing and slices to provide compile-time memory-safety guarantees without requiring a garbage collector.
Swift takes a different overall approach but similarly provides bounds checking for arrays and buffers. Its language documentation explicitly identifies bounds safety as one of its memory-safety guarantees.
The broader principle is straightforward:
> **When the safety of one value depends on another, keeping those values semantically connected makes the safety property easier to preserve and verify.**
This directly addresses a recurring failure mode in *The Wrong Memory*: the accidental separation of an address from the information needed to use it safely.
Garbage collection removes much of the programmer's responsibility for lifetime.
Ownership systems take a different approach.
Rather than having a runtime determine when an object can be destroyed, the language tracks who owns an object and constrains how references to it may be used.
Rust is the best-known modern example.
Its ownership model allows the compiler to provide memory-safety guarantees without a garbage collector. Ownership, borrowing and lifetime rules constrain how references are created and used, and violations of those rules result in compilation failure.
This addresses questions that bounds checking alone cannot answer:
> Does the object this reference identifies still exist?
> Who is responsible for it?
> Can another reference modify it?
> Can these references coexist?
These are precisely the questions behind many of the temporal and ownership failures examined in the later chapters of this book.
The important conceptual change is that lifetime and ownership cease to be merely informal promises.
They become properties about which the language can reason.
Rust is particularly significant because it combines several of these ideas without relying on garbage collection.
Ownership, borrowing, lifetimes, slices and the type system allow the compiler to reject many programs in which a C programmer would instead have to maintain an informal memory-safety argument.
That does not mean Rust eliminates the need for low-level reasoning.
Rust has `unsafe` code. It has foreign-function interfaces. It permits operations that cannot be justified entirely by the safe type system. And memory safety does not imply application correctness.
What matters is the boundary.
Rust attempts to establish a protected subset in which the compiler can provide strong memory-safety guarantees, while making the programmer explicitly acknowledge operations that escape those guarantees.
That changes the economics of code review.
In a C program, every pointer operation may require the reviewer to reconstruct an implicit safety argument.
In a Rust program, the safe portion of the program comes with stronger language-level guarantees, allowing disproportionate scrutiny to be concentrated on the comparatively small areas where those guarantees are deliberately bypassed.
This is an important principle:
> **Unsafe operations do not necessarily need to disappear. They need to become identifiable, exceptional and reviewable.**
D is particularly interesting in the context of this book because it occupies an important middle ground.
It retains a systems-programming orientation and low-level capabilities while providing an explicitly defined safe subset. D distinguishes `@safe`, `@trusted` and `@system` code, with `@safe` code subject to restrictions intended to prevent memory corruption.
This is significant because it rejects a false choice.
The alternatives are not necessarily:
> unrestricted C
or
> a language in which low-level operations are impossible.
D instead explores:
> @safe (which sadly, is not the current default), but with explicit escape routes when low-level control is genuinely required.**
That is a compelling model for systems programming.
There are legitimate reasons to manipulate raw memory, perform unusual casts, interact directly with hardware, implement allocators, or cross a foreign-function interface. A systems language that cannot express such operations may simply force programmers to leave the language's safety model entirely.
A language such as D can instead make the boundary explicit.
Conceptually:
text
Program
│
┌────────┴────────┐
│ │
@safe @system
│ │
compiler-enforced low-level
guarantees operations
│
@trusted
│
audited bridge
code
The important idea is not the particular annotations.
It is the architectural principle:
> **Make unsafe reasoning local rather than making it the default condition of the entire program.**
For the argument of *The Wrong Memory*, D is especially instructive because it demonstrates that stronger safety guarantees do not necessarily require abandoning the programming model that attracts developers to C in the first place.
Swift provides another answer to the same problem.
Swift's memory-safety model includes definite initialization, bounds safety, lifetime safety and restrictions on conflicting access to memory. The language and runtime cooperate to enforce these properties, with checks occurring at compile time where possible and at runtime where necessary.
This is an instructive contrast with both C and Rust.
Swift does not simply reproduce C's manual-memory model with additional warnings. Nor does it rely on Rust's ownership system as the primary mechanism.
Instead, it combines automatic memory management with language-level and runtime safety checks.
Swift also demonstrates the importance of making unsafe boundaries explicit. Its tooling can identify constructs and APIs that undermine memory safety, including unsafe pointer operations and other unsafe language features.
This illustrates another important principle:
> **A safe language does not have to deny access to unsafe mechanisms; it needs to make the transition into unsafe territory visible.**
That distinction becomes increasingly important when a language must interoperate with C and other legacy systems.
Ownership and runtime management are not the only ways to move the safety argument into the language ecosystem.
Ada and SPARK represent a different and particularly important tradition: **explicit contracts and formal verification**.
The approach is valuable because not every property that matters can conveniently be encoded in ownership or borrowing rules.
Consider an invariant such as:
length <= capacity
or:
index < number_of_elements
or:
allocated_size >= required_size
These are straightforward examples, but real systems can require much richer relationships involving state, inputs, outputs and sequences of operations.
Contracts allow programmers to state such properties explicitly. Verification tools can then attempt to establish that those properties hold.
This extends the approach taken throughout *The Wrong Memory*.
The book repeatedly asks the reader to identify the facts that must be true for a memory operation to be valid.
Formal verification asks the next question:
> **Can those facts be proved?**
This is an important alternative to the idea that the type system must encode every safety property.
The future of safe systems programming need not consist of one universal mechanism. Types, ownership systems, contracts and formal proofs can address different parts of the problem.
The modern discussion of memory-safe systems programming can sometimes make it appear that the solution began with Rust.
It did not.
Cyclone is an important earlier experiment. Developed as a safe dialect of C, it was designed specifically to prevent buffer overflows, dangling pointers, format-string attacks and memory-management errors while retaining C's low-level control over data representation and memory management.
Cyclone is historically significant because it demonstrates that the underlying problem was recognised long before today's memory-safe systems languages.
Its designers confronted essentially the same question:
> How can we retain C's control over the machine without retaining C's requirement that programmers manually maintain every safety invariant?
Cyclone explored mechanisms including regions and tracked pointers to establish stronger safety properties while retaining a C-like programming model.
The project did not become a mainstream replacement for C. But that is not the important point here.
Its importance is conceptual.
It demonstrates that the tension between low-level control and memory safety has been a programming-language research problem for decades.
Rust, D, Swift and other modern approaches are part of that longer evolution rather than appearing from nowhere.
Pony explores another dimension of the problem: **what a reference permits its holder to do**.
In C, a pointer primarily answers a question about location.
Pony's reference capabilities make access rights part of the type system. Different capabilities express properties such as mutability, sharing and isolation, allowing the compiler to reason about how references may be used and shared.
This is particularly relevant to the aliasing problems that underlie many memory-safety failures.
A reference is not merely:
> "where is the object?"
It can also mean:
> "what am I allowed to do with this object?"
That is a powerful extension of the ideas behind ownership and `const`.
It also connects memory safety with concurrency. If the type system can establish that mutable state cannot be accessed through conflicting capabilities, it can prevent classes of data races as well as memory-safety failures.
Pony therefore illustrates a broader lesson:
> **A safe reference can encode authority, not merely location.**
That is a useful way to think about the evolution from raw pointers to richer reference abstractions.
Experimental languages such as Vale demonstrate that ownership and borrowing are not the only possible answers to temporal memory safety.
Vale explores a different combination of ownership, reference tracking and memory-management techniques, including generational references designed to detect stale references. This illustrates an important alternative to Rust's predominantly compile-time strategy.
The distinction is conceptually useful.
One approach attempts to prevent an invalid relationship from being created:
attempt to create invalid reference
↓
compile error
Another can allow a reference to exist but detect that it has become invalid:
reference exists
↓
object destroyed
↓
reference becomes stale
↓
stale use detected
These approaches have different costs and guarantees.
The broader lesson is that memory safety is a design space, not a single technique.
Languages can choose different points between compile-time prevention, runtime detection, automatic management and explicit programmer responsibility.
The important question remains the same:
> **Where does the evidence for safety come from?**
C++ deserves attention because it demonstrates the limits of strengthening a language without fundamentally changing its safety model.
Modern C++ provides considerably safer abstractions than traditional C:
* RAII;
* smart pointers;
* standard containers;
* iterators and ranges;
* stronger type abstractions;
* increasingly sophisticated analysis tools.
Used consistently, these mechanisms can eliminate many classes of memory-management errors.
But C++ retains extensive mechanisms for bypassing those abstractions.
That creates an important distinction:
> **A language can provide safe abstractions without being a memory-safe language.**
The distinction matters because a safe abstraction depends on programmers remaining within its intended boundaries.
If the programmer can escape the abstraction without entering an explicitly marked unsafe region, the burden of proof remains distributed throughout the program.
This is one reason explicit safe/unsafe boundaries are attractive in newer systems languages.
The objective is not merely to provide safer ways to program.
It is to make the *unsafe alternative visible*.
It would be a mistake to treat these languages as competing answers to a single question.
They occupy different positions in a larger design space.
| Safety problem | Possible language-level response |
| ------------------------------- | ----------------------------------------------------------------------- |
| Object lifetime | Garbage collection, ownership, regions, reference management |
| Dangling references | Borrow checking, ownership, generational references |
| Bounds | Slices, arrays, spans, bounds checking |
| Aliasing | Borrow checking, uniqueness, reference capabilities |
| Mutation | Ownership, capabilities, exclusivity |
| Arithmetic affecting memory | Checked arithmetic, richer numeric types, contracts |
| Resource ownership | RAII, ownership, linear or affine techniques |
| Application-specific invariants | Contracts, refinement and dependent techniques, formal verification |
| Low-level escape hatches | `unsafe`, `@system`, explicit unsafe APIs |
| Foreign-function interfaces | Checked wrappers, annotations, isolation and explicit unsafe boundaries |
| Concurrency | Ownership, capabilities, actor isolation and data-race checking |
No single language needs to employ every mechanism.
What matters is that important safety properties have **somewhere appropriate for their proof to live**.
These approaches can also be understood as occupying different points on a spectrum.
A language can try to **prevent** an invalid operation.
It can allow the operation but **detect** the violation at runtime.
Or it can require enough information to **prove** that the operation is valid.
These are not equivalent.
Consider three possible outcomes:
Implicit C-style contract
incorrect assumption
↓
compiled program
↓
deployment
↓
unexpected input
↓
memory corruption
A runtime-checked model changes this to:
incorrect assumption
↓
compiled program
↓
runtime check
↓
detected violation
A statically checked model attempts to produce:
incorrect assumption
↓
compiler
↓
program rejected
And a stronger type or capability system may go further:
invalid operation
↓
cannot be expressed in safe code
These are different security models.
A vulnerability that becomes a compile-time error is fundamentally different from one that becomes a runtime exception, which is in turn different from one that becomes memory corruption.
This is one reason memory-safe language design is not merely a matter of programmer convenience.
It changes the point at which failures occur and, consequently, the cost and consequences of those failures.
There is an important idea underneath many of these language-design approaches.
A large proportion of C memory errors can be understood as cases where **semantic information is lost**.
A count becomes an ordinary integer.
A bounded region becomes a raw pointer.
An owned object becomes an address.
A lifetime relationship becomes an informal convention.
A range becomes two unrelated function arguments.
An authority to modify an object becomes an unrestricted pointer.
Once that information has disappeared from the program's formal representation, the compiler has far less opportunity to protect it.
Modern language design can therefore be understood partly as an attempt to **preserve semantic information for as long as it remains relevant**.
A slice preserves the relationship between a sequence and its extent.
An ownership type preserves information about responsibility for an object.
A borrow preserves a relationship between an access and the object's lifetime.
A capability preserves information about what operations a reference permits.
A contract preserves an invariant across an interface.
A region preserves information about the lifetime of a collection of objects.
A garbage collector preserves reachability information so that ordinary code need not manage object destruction explicitly.
The common principle is remarkably simple:
> **Do not discard information that the language will later need in order to establish safety.**
This is perhaps the most useful general lesson that memory-safety research has to offer programming-language design.
We can now return to the question at the centre of this appendix:
> **What makes this memory operation safe, and how does the language know?**
For C, the uncomfortable answer is often:
> **The programmer knows.**
That answer can work remarkably well for small programs and experienced teams. It becomes increasingly fragile as software grows in size, complexity and lifespan.
For a garbage-collected language, the runtime establishes important lifetime properties.
For Rust, ownership, borrowing and the type system establish many lifetime, aliasing and ownership properties at compile time.
For Swift, the language and runtime establish initialization, bounds, lifetime and access-safety properties, with the compiler proving what it can and runtime checks handling cases that require them.
For D, a defined safe subset provides compiler-enforced restrictions while explicitly marked code can operate outside those guarantees.
For Ada/SPARK, contracts and formal methods can make important invariants explicit and subject them to verification.
For Pony, reference capabilities make access rights and aliasing properties part of the type system.
For Cyclone, the attempt was to preserve much of C's low-level model while adding mechanisms capable of ruling out important classes of memory errors.
These are very different answers.
But they all move in the same general direction:
**make the safety argument more visible, more explicit and more enforceable.**
There is an important limit to this argument.
A language can prove that an array index is within bounds and the program can still choose the wrong array.
It can prove that an object is alive and the program can still operate on the wrong object.
It can prevent use-after-free and the program can still return the wrong result.
It can prevent memory corruption and the program can still disclose a secret because the programmer implemented the wrong security policy.
Memory safety is therefore not synonymous with correctness or security.
The objective is narrower:
> **Classes of invalid memory operations should be prevented, detected or explicitly isolated without requiring every programmer to reconstruct the same low-level safety argument manually.**
That is already an enormous improvement.
It allows programmers to concentrate more of their attention on the problems that language mechanisms cannot reasonably solve for them.
There is another important lesson.
Systems programming will always have legitimate reasons to perform operations that a language cannot fully prove safe.
Operating-system interfaces may require raw pointers. Device drivers may manipulate hardware registers. Allocators may manage memory directly. High-performance libraries may require carefully controlled representations. Foreign-function interfaces necessarily cross language boundaries.
The existence of unsafe operations is therefore not itself a design failure.
The important question is whether unsafe operations are **ordinary or exceptional**.
C effectively makes the programmer responsible for establishing memory safety throughout the program.
Modern languages can instead attempt to make unsafe operations explicit.
Rust's `unsafe` mechanism, D's `@system` and `@trusted` distinctions, and Swift's explicit unsafe constructs all illustrate versions of this idea.
The principle is straightforward:
> **When the language cannot establish safety, the program should make that fact visible.**
That gives reviewers, auditors and tools somewhere to concentrate their attention.
It also makes the safety boundary architectural rather than merely aspirational.
None of this means that C can simply be discarded.
C remains deeply embedded in operating systems, firmware, embedded devices, libraries, runtimes and infrastructure. Vast amounts of existing software cannot realistically be rewritten.
The more immediate lesson is that the safety boundary around C can be strengthened even when C itself remains.
Safer abstractions can associate pointers with lengths. Static analysis can check contracts. Sanitizers can detect violations during testing. Hardened allocators can make exploitation more difficult. Foreign-function interfaces can carry additional information about bounds and lifetimes. New components can be implemented in memory-safe languages while maintaining carefully controlled interfaces to existing C code.
This suggests a gradual transition rather than an all-or-nothing one.
The question is not necessarily:
> "How do we replace every line of C?"
It may instead be:
> **"Where can we move the safety proof out of C and into stronger abstractions, tools and languages?"**
That is a much more tractable question.
The analysis in *The Wrong Memory* suggests a useful set of questions that a modern systems language should make answerable from the program itself.
### What object is this?
The language should provide some way to distinguish an object from an arbitrary address.
### How much may I access?
The accessible extent should be represented or enforceable.
### Where am I within that object?
Pointer arithmetic and indexing should preserve the relationship between a reference and its object.
### Does the object still exist?
Lifetime should be tracked statically, automatically or through a well-defined runtime mechanism.
### Who owns it?
Ownership should be explicit wherever manual resource management requires it.
### Who else can access it?
Aliasing and mutation should be constrained sufficiently to make their effects tractable.
### What does this number mean?
Where arithmetic controls memory, the language should make dangerous conversions, overflow and unit confusion difficult to overlook.
### What happens when the language cannot prove safety?
The escape into unsafe behaviour should be explicit and reviewable.
These are, in effect, the questions that *The Wrong Memory* asks the C programmer to answer manually.
A modern systems language can attempt to answer them on the programmer's behalf.
The objective is not to make programmers irrelevant.
No compiler can generally determine whether a program selected the correct record, interpreted a network protocol correctly, applied the right security policy or implemented the intended business rule.
But there is an important distinction between:
> **The programmer selected the wrong object.**
and:
> **The programmer accidentally accessed memory outside the object.**
The first is generally an application-level error.
The second is a language-level safety failure.
Programming languages should eliminate as many of the latter as practical.
That is the real promise of memory-safe systems programming.
The goal is not to prove that every program is correct.
It is to ensure that a large and particularly dangerous class of *incorrect programs cannot express their incorrectness as arbitrary memory access*.
The deepest lesson of *The Wrong Memory* is not that C programmers should be more careful.
C programmers have been told to be careful for decades.
The difficulty is that **carefulness is not a scalable memory-safety mechanism**.
Large programs contain too many values, interfaces, ownership relationships, transformations and execution paths for every important invariant to remain indefinitely inside the heads of the programmers maintaining them.
The more durable solution is to move those invariants into the machinery of programming itself.
Some can become types.
Some can become ownership relationships.
Some can become lifetimes.
Some can become bounds.
Some can become capabilities.
Some can become contracts.
Some can be checked statically.
Some can be checked at runtime.
And some operations must remain explicitly unsafe.
The important point is that the safety argument should no longer be invisible.
A programmer should be able to ask:
> **What makes this memory operation safe, and how does the language know?**
And the language should increasingly be able to answer:
> **Because the program's types, ownership, bounds, lifetimes, contracts and other invariants establish it.**
That is the direction in which memory-safe systems programming is moving.
C's great achievement was to give programmers extraordinary control over the machine.
The next generation of systems languages is attempting something more difficult:
**to retain that control while making the programmer prove—or allowing the language to prove—that the control is being exercised over the right memory.**
That is the transition from a world in which programmers are repeatedly told to avoid *the wrong memory* to one in which the language itself increasingly helps ensure that the wrong memory is never an ordinary programming option.