Carbon Checked Pointers: Architecture Note on Safe Memory Access
An architecture note outlining the requirements, minimal design, trust boundaries, operational checks, failure modes, and evolution triggers for Carbon’s checked pointer feature.
03 May 2026, 10:34 UTC

Requirements
Carbon’s checked pointer feature must satisfy three core requirements:
- Provide compile‑time guarantees that any pointer dereferenced in
safecode cannot refer to deallocated or misaligned memory. - Allow seamless interoperability with existing C++ raw pointers, which remain unchecked and are used for foreign‑function interfaces (FFI).
- Impose minimal runtime overhead so that performance‑critical code can still opt‑out of checks when justified.
Minimal Suitable Design
The design introduces two distinct pointer kinds:
- Checked pointer (
T*in safe code) – carries an extra metadata word that encodes validity (null, allocated, aligned). - Unchecked pointer (
T* raw) – a plain C‑style raw pointer used only across the trust boundary.
Checked pointers are the default in any block marked with the safe keyword. Converting an unchecked pointer to a checked one requires an explicit unchecked cast, making the trust boundary visible in the source.
Example: Safe Function Returning a Checked Pointer
package demo;
safe fn get_answer() -> i32* {
var x: i32 = 42;
return &x; // implicit conversion to checked pointer
}
safe fn main() {
let p: i32* = get_answer();
let v: i32 = *p; // dereference triggers runtime validity check
print(v);
}
The compiler treats &x as a checked pointer because the function is safe. No explicit cast is needed.
Example: Explicit Unchecked Cast Leading to Runtime Abort
unsafe fn bad() -> i32* {
let raw: i32* raw = null; // unchecked null pointer
return unchecked raw; // explicit cast to checked pointer
}
safe fn main() {
let p: i32* = bad();
_ = *p; // runtime check aborts with "invalid checked pointer"
}
The unchecked keyword signals that the programmer takes responsibility for the pointer’s validity; the runtime check will catch the violation.
Trust/Data Boundaries
The boundary between safe and unsafe code is enforced syntactically:
- Only
safefunctions may create, copy, or dereference checked pointers. - Unchecked pointers may cross the boundary only via an explicit
uncheckedcast (or a matchingcheckedcast when moving from unsafe to safe). - Any attempt to use a raw pointer directly in safe code is a compile‑time error.
This clear separation lets tooling reason about where memory safety guarantees apply and where the programmer must manually uphold invariants.
Operational Checks
At runtime each checked pointer stores a single metadata word alongside the address. The word encodes three states:
- Valid (points to live, correctly aligned storage)
- Null
- Invalid (freed, dangling, or misaligned)
Before a dereference, the generated code calls a tiny runtime function that loads the metadata word and aborts if the state is not Valid. The abort is deterministic (e.g., abort()) and does not rely on exceptions, making failure detection predictable in embedded or real‑time contexts.
Because the metadata word is stored adjacent to the pointer, the overhead is one word per pointer and a single branch‑like check per dereference. Inline caching or compile‑time elimination can reduce this further when the compiler can prove validity.
Failure Modes
When a checked pointer fails its validity check, the program aborts with a diagnostic message that includes:
- The source location of the dereference.
- The observed metadata state (null/invalid).
- Optionally, the pointer’s address for debugging.
This deterministic abort prevents silent memory corruption. However, if the metadata word is corrupted (e.g., via an unsafe write), the check may miss the error. Such corruption can only arise from unsafe code, reinforcing the importance of the trust boundary.
Conditions That Would Change the Design
The current design assumes that the cost of one metadata word and a runtime check is acceptable for most safe code. Two plausible shifts could alter the design:
- Performance‑critical scenarios: If profiling shows the metadata word causes unacceptable cache pressure or the check becomes a hotspot, the language could adopt a compile‑time region‑based analysis (similar to Rust’s lifetimes) to eliminate runtime checks entirely for provably safe code paths.
- Reduced FFI need: If the ecosystem evolves to minimize direct C++ interop, the unchecked pointer kind could be removed, making all pointers checked by default and simplifying the trust model.
Either shift would require revising the syntax for casts, updating the compiler’s intermediate representation, and adjusting tooling (debuggers, profilers) to understand the new pointer representation.
Practical Verification Steps
To confirm that the design behaves as described, a developer can:
- Compile a simple Carbon program that declares a
safefunction returning a checked pointer to a stack‑allocated integer and dereferences it. Expect no compile‑time warnings and a successful run. - Introduce an explicit
uncheckedcast of a null raw pointer to a checked pointer, call the function, and verify that the binary aborts with a clear "invalid checked pointer" message. - Inspect the generated LLVM IR (or Carbon’s IR) to ensure each checked pointer is represented as a pair
{ i8*, i1 }(pointer + metadata) and that dereference sites contain a call to the runtime check function.
These steps rely on the current experimental toolchain; results may vary as the language evolves.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.