Idea / Pre-RFC: Null-free pointers
H4n_uL:
The revised proposal lists this under migration cost as unmeasured and needing benchmarks.
As promised in my previous comment, I've measured the codegen impact of null_pointer_is_valid. This addresses a gap that @RalfJung rightly pointed out, and that several others in the thread have implicitly assumed without data.
I am not a compiler engineer, so I kept it to five simple C patterns on aarch64-unknown-none with Homebrew clang 21.1.8 at -O2, comparing baseline against -fno-delete-null-pointer-checks.
// bench.c
// clang -v | Homebrew clang version 21.1.8
// clang --target=aarch64-unknown-none -O2 -S bench.c -o baseline.s
// clang --target=aarch64-unknown-none -O2 -fno-delete-null-pointer-checks -S bench.c -o zeroisvalid.s
// diff baseline.s zeroisvalid.s
#include <stdint.h>
#include <stddef.h>
// C1: null check elimination after dereference
uint32_t check_after_deref(const uint32_t *p) {
uint32_t v = *p; // dereference
if (p) return v + 1; // compiler can assume p != null
return 0; // baseline: this branch eliminated
}
// C2: two paths merging on null knowledge
uint32_t branch_after_store(uint32_t *p, uint32_t val) {
*p = val; // store implies p != null
if (p) return *p + 1; // redundant check?
return 42;
}
// C3: loop with pointer increment
uint32_t sum_until_null(const uint32_t *const *ptrs) {
uint32_t sum = 0;
while (*ptrs) { // null-terminated array of pointers
sum += **(ptrs++);
}
return sum;
}
// C4: devirtualisation / inlining based on nonnull
void copy_if_valid(uint32_t *dst, const uint32_t *src, size_t n) {
if (dst && src) {
for (size_t i = 0; i < n; i++)
dst[i] = src[i];
}
}
// C5: struct access implying nonnull
uint32_t read_two_fields(const struct { uint32_t a; uint32_t b; } *s) {
uint32_t x = s->a; // implies s != null
if (s) return x + s->b; // check eliminable?
return 0;
}
8a9,10
> cbz x0, .LBB0_2
> // %bb.1:
10a13
> .LBB0_2:
21a25,26
> mov w9, #42 // =0x2a
> cmp x0, #0
23c28
< add w0, w1, #1
---
> csinc w0, w9, w1, eq
111a117,118
> cbz x0, .LBB4_2
> // %bb.1:
113a121
> .LBB4_2:
The result: codegen differences appear only where the compiler would otherwise eliminate an implicit null check after a dereference or store: 2–3 additional instructions per site. Explicit null comparisons (C3, C4) produce identical output.
This doesn't prove the cost is zero for Rust, but given that one of the most performance-sensitive C codebases in existence has operated without this optimisation for over fifteen years, I think the default assumption should be that the cost is manageable unless demonstrated otherwise.
Discussion in the ATmosphere