Carbon's Checked Pointers: Compile-Time Null Safety for Systems Code
Carbon introduces checked pointers as a language-level mechanism to prevent null dereference at compile time, offering a safer alternative to raw C++ pointers while maintaining interoperability.
04 Aug 2025, 13:00 UTC

Problem: Null Pointers in Everyday C++
In C++, a null pointer dereference crashes the program at runtime. Finding the source of such a crash often requires debugging sessions and careful code review. Carbon, Google's experimental successor to C++, introduces checked pointers to make null safety a compile-time concern.
Useful Takeaway
Checked pointers let the compiler reject null dereferences before the code runs, while still allowing explicit conversions to raw C++ pointers when interoping with legacy code.
How Checked Pointers Work
Checked pointers are declared with the checked keyword or via the Checked<T> type. The compiler tracks these values and prevents dereferencing a pointer that might be null. If a value can be null, you must explicitly handle it, otherwise a compile-time diagnostic is emitted.
The feature integrates with Carbon's generics and type system. Functions can accept Checked<T> parameters and return Checked<T> values. When you need to work with raw C++ pointers, Carbon provides an explicit unchecked conversion, making the hand‑off intentional and visible.
Declaration
To mark a pointer as checked, write:
var p: checked int*;
or use the generic form:
var p: Checked;
The compiler then prevents *p if p could be null, unless you've proven it's safe or explicitly allow the dereference.
Worked Example: Safe Increment
Suppose you want a function that increments the integer a pointer points to, without risking a null dereference. With a checked pointer, the compiler enforces safety.
fn increment_checked(ptr: checked int*) -> void {
*ptr += 1;
}
If you call increment_checked with a pointer that might be null, the compiler will emit an error. To pass a potentially null pointer, you must either prove it's non‑null or convert it explicitly:
fn safe_call(raw: int*) -> void {
var checked_ptr: checked int* = unchecked(raw); // explicit conversion
increment_checked(checked_ptr);
}
Trade‑offs and Limitations
- Learning curve: Developers accustomed to raw C++ pointers need to adjust to the checked/unchecked distinction.
- Tooling: Early Carbon releases have sparse IDE support and limited debugger integration. Verifying null‑safety guarantees may require building from source and inspecting compiler diagnostics.
- Explicit conversions: Interoping with existing C++ code requires unchecked conversions, which add verbosity but also clarity.
- Experimental status: The checked pointers design may evolve or be removed before a stable release.
Trying It Yourself
If you want to experiment with checked pointers, the Carbon project provides a concrete verification path:
- Clone the official Carbon repository:
git clone https://github.com/googlecarbonlang/carbon.git - Checkout the latest main branch as of the knowledge cutoff (2024-06).
- Build the compiler with Bazel:
bazel build //toolchains:carbon_cli - Place your example file, e.g.
checked_ptr_example.carbon, in the project. - Compile it:
bazel run //toolchains:carbon_cli -- checked_ptr_example.carbon - Observe the compiler diagnostics: attempting to dereference a null
checked int*should produce a compile-time error, while explicituncheckedconversions should pass. - Run the resulting binary to confirm that safe code executes without runtime null checks.
Closing
Carbon's checked pointers offer a compelling way to eliminate an entire class of null-related bugs at compile time. If you're working on systems code where pointer safety matters, it's worth building the compiler and running the example to see the diagnostics for yourself. Remember to use unchecked only when bridging to legacy C++, and keep an eye on the project's roadmap as the feature matures.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.