External Publication
Visit Post

Idea / Pre-RFC: Null-free pointers

Rust Internals [Unofficial] February 21, 2026
Source

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

Loading comments...