Why Memory-Safety Migration Is a Portfolio
Memory-safety migration is the subject of this series: how organizations move from software that permits dangerous memory mistakes toward software whose languages, libraries, and tooling prevent more of those mistakes by default. The question is not simply whether Rust, Java, Go, or another language is “better.” It is how a real system can reduce memory-related risk without pretending that a large legacy codebase can be replaced in one motion. The useful judgment for a reader is therefore practical: which changes prevent new exposure, which legacy components deserve attention first, and which controls are still needed after a safer language enters the picture?
That framing matters because memory safety is not a niche concern reserved for language designers. The NSA and CISA guidance on memory-safe languages describes a vulnerability class that has repeatedly affected security, reliability, and availability. It cites estimates that memory-safety issues accounted for 66% of CVEs for iOS 12 and 71% for macOS Mojave in one 2019 study. It also reports a Google Project Zero estimate that 75% of CVEs used in observed in-the-wild exploits were memory-safety vulnerabilities, and that 67% of 58 in-the-wild zero-days discovered in 2021 fell into that category. Those figures are attributed estimates, not a new universal measurement of all software. Their significance is the pattern: memory mistakes remain a recurring route to serious failures even after years of secure-coding advice and defensive tooling.
The problem is larger than a bad pointer
Memory is where a program stores values and the relationships needed to use them. A memory-safety failure occurs when the program’s use of that storage no longer matches what was allocated, initialized, owned, or shared. A buffer overflow writes beyond a region’s boundary. A use-after-free keeps using storage after it has been released, perhaps after another part of the program has reused it. A data race lets concurrent operations read and write the same location without adequate coordination. An initialization failure reads a value before the program has established that the value is valid.
These are different mechanisms, but they can converge on the same operational consequences. An out-of-bounds write may corrupt a neighboring object. A later read may disclose data that was placed in the reused region. Corruption can alter control flow, while a malformed access can simply crash a service that must remain available. The point is not that every memory bug is exploitable. The point is that a developer, reviewer, or test suite may have to reason about a dangerous relationship every time code manually controls memory, and an error in that reasoning can escape into production.
This is why memory safety is best understood as a systems problem rather than a matter of individual carelessness. Developer discipline, coding standards, static analysis, fuzzing, and exploit mitigations all have value. But they generally operate around a programming model that still permits the mistake. They find, reduce, or contain failures after the code has expressed a dangerous possibility. A language that rejects an invalid lifetime or checks a boundary by default changes the possibility itself.
Prevention changes the default
Memory-safe languages use different mechanisms, but their shared contribution is to move part of the safety burden out of individual memory-management decisions and into the language and development environment. Bounds checks can keep an access inside an allocated object. Type rules can limit which operations make sense for a value and make object behavior more predictable. Garbage-collected environments such as Java or Go can reclaim storage without requiring every caller to release it at exactly the right time. Rust takes a different route: ownership and borrowing rules constrain who can use or modify data and when storage can be reclaimed. Language and library rules can also prevent or expose classes of unsynchronized shared access.
The distinction is between prevention and remediation. If the default model makes a use-after-free impossible without explicitly stepping outside the safe subset, the development team has removed an entire recurring bug class from ordinary code. If the model detects an invalid access during compilation or at runtime, it can fail near the cause rather than allowing silent corruption to travel through the system. That can improve debugging and reliability as well as security.
“Safe by default,” however, is not shorthand for “secure in every respect.” A memory-safe language does not automatically make authorization correct, input validation complete, cryptography sound, or a dependency trustworthy. It does not eliminate every concurrency design problem, and it cannot decide whether a program’s business logic is the right logic. Memory safety narrows one dangerous part of the failure surface. The remaining surface still requires testing, review, dependency governance, and operational controls.
This boundary also explains why migration should not be sold as a contest between a perfect new language and a worthless old one. The choice is usually between several imperfect paths: introduce stronger guarantees in new code, isolate and replace selected legacy components, harden components that cannot yet move, and verify the seams between them. The language is one control in that portfolio, not the portfolio’s entirety.
Why a rewrite is the wrong first question
A large codebase is not one uniform risk object. Some components face untrusted network input; some parse files or media formats; some handle cryptographic operations; some are operationally critical but isolated behind stable interfaces. Rewriting all of them at once would combine technical risk, schedule risk, compatibility risk, and organizational learning into one bet. It could also postpone the security benefit while the rewrite is still underway.
The more useful first question is where new memory-safety exposure is entering and which existing component combines high attack surface or operational importance with a boundary that can be made explicit. New development is often the lowest-friction starting point. A team can choose a memory-safe language before it adds another generation of technical debt. For legacy systems, modularization and well-defined APIs can make targeted replacement possible. The objective is not to preserve every internal detail; it is to create a boundary across which the old and new components can interact with known data and ownership expectations.
Android provides the guidance’s clearest case for this sequence. In 2019, memory-safety issues accounted for 76% of Android vulnerabilities, according to the cited case study. The Android team then prioritized Rust and Java for new development rather than attempting a massive rewrite of its existing platform. By 2024, the report says, memory-safety vulnerabilities had fallen to 24% of the total. That is a powerful illustration of preventing new vulnerabilities from entering a large system, but it is still a reported association, not proof that language choice alone produced the entire change. Android’s scale, engineering capacity, existing language mix, and other security work matter.
The transferable lesson is narrower and more useful than a slogan: stop adding to the problem before trying to erase all of its history. New-code adoption and targeted legacy work can proceed together. Reliability benefits strengthen the case because fewer memory errors can mean fewer crashes, less silent corruption, faster diagnosis, and less emergency patching. But those benefits should be treated as an engineering rationale, not as a guaranteed return on investment for every organization.
The portfolio has a seam
The transition becomes real at the boundary between components. A memory-safe module may need to call an older library. A new service may have to exchange data with a legacy process. Converting a value between representations can preserve compatibility, but it can also introduce performance cost, ownership ambiguity, or a place where an unsafe assumption crosses languages unnoticed. Dependencies that were not developed under the same safety model remain part of the system’s risk picture.
That is why an adoption roadmap must include more than a preferred language. It should identify which components are exposed, how they communicate, what the interface guarantees, how data is marshaled, and how performance will be tested. It should account for the team’s ability to learn the language and maintain its tools and libraries. It should include dependency review and version discipline. Where a migration is not yet practical, bounds checking, safer library choices, compiler controls, smart pointers, and static or dynamic analysis can reduce risk. Those measures are valuable fallbacks, but the guidance does not claim that they provide the same assurance as a memory-safe language.
Automated translation belongs at this seam as well. A transpiler or an LLM-assisted translator may preserve useful behavior, provide a starting point, and expose defects in old code. It can reduce manual effort while a team incrementally improves a component. But generated code needs review and likely intervention. A program written in the syntax of a memory-safe language does not automatically use that language’s strongest safety types, and code that compiles can still express the wrong behavior. The companion research case in this series examines that distinction in detail.
A disciplined starting point
A credible migration portfolio can begin with five linked decisions. First, prevent new exposure by making memory-safe languages the default for suitable new work. Second, rank legacy components by attack surface and operational consequence, not by how easy they are to rewrite. Third, make interfaces explicit so that mixed-language operation has a testable contract. Fourth, invest in translation tools, training, and review without treating automation as a safety certificate. Fifth, keep broader security and fallback controls active while the portfolio is incomplete.
That sequence does not remove uncertainty. A language may lack a mature library, a component may require low-level performance or unusual concurrency behavior, and an interface may cost more than its security benefit justifies. These are reasons to measure, isolate, defer, or compensate—not reasons to pretend that the rewrite question has only two answers.
The central idea is therefore modest but consequential. Memory-safe languages can prevent important classes of failures at their source, yet the practical value of that prevention depends on where it is introduced, how it crosses system boundaries, and how its output is verified. The next step is to look closely at the mechanisms themselves: what bounds, lifetimes, initialization, and concurrency controls actually change, and where their protection stops.