Search code examples
pointersrustundefined-behaviorunsafe

Storing Zero-Sized type instance into a null pointer


As per Rustonomicon

Rust largely understands that any operation that produces or stores a ZST can be reduced to a no-op

on the other hand

Note that references to ZSTs (including empty slices), just like all other references, must be non-null and suitably aligned. Dereferencing a null or unaligned pointer to a ZST is undefined behavior, just like for any other type.

This two paragraphs appeared a bit contradictory to me. Consider the following example:

fn main() {
    let null_ptr = ptr::null_mut::<()>();
    unsafe { *null_ptr = () }
}

It compiles and runs fine under rustc 1.75.0, but is the behavior of the code well defined?

From one hand it should be no-op, so there's probably no any problems, from the other there's a dereferencing of null pointer which is UB.

Note: The reason I tried to use null pointers here is that allocators might allocate some non-zero sized memory even for ZSTs which in my specific environment is not accepted.


Solution

  • The first statement doesn't talk about semantics - you will see from the context that it talks about performance. ZSTs are efficient because operating on them is no-op. Still, there are requirements for them, arising from the operational semantics of Rust. Your snippet is UB and Miri flags it as such.

    The solution to your problem is to use a non-null, aligned, but not allocated pointer for this situation. std::ptr::NonNull::dangling().as_ptr() can easily give you one. Such pointer is valid for ZSTs because it is aligned and non-null (provenance isn't required for ZSTs, although note that currently incorrect provenance can cause UB).

    Also note that requesting an allocation of size zero is UB on its own so you must check for it (assuming you use Rust's interface for allocators).