Why does std::slice::from_raw_parts require a non-null pointer for zero-length slices?

The docs say: "p must be non-null, even for zero-length slices."

This is rather annoying. What's the rationale for this requirement?

My guess is that it's because slices are a form of reference, and references can't be null. Other code relies on this assumption. Breaking it means bad things can happen.

What bad things, though? The pointer is never dereferenced for zero-length slices, right?

On surface, it doesn't seem particularly useful to materialize a bogus pointer from 0x1, like the nsstring crate in Gecko does. (Which is how I noticed that my own code violates this requirement.)

The pointer might not be dereferenced, but it might be used in Option<&[T]>, which would mean that None and Some(&[]) would have the same bit pattern and could not be differentiated. Special casing on the slice length would only complicate things, since the space optimization around Option just cares about types implementing NonZero for now. Once this is generalized this could be resolved but is unlikely to be resolved due to everyone expecting &T not to be an all zero bits pattern.

7 Likes

It's not my area of specialty, so maybe someone else can chime in, but I believe we tell LLVM that it's nonnull, and so it optimizes on this basis...

As an example of what @oli_obk said:

let slice: &[u8] = unsafe { std::slice::from_raw_parts(ptr::null(), 0) };
let maybe_slice = Some(slice);
println!("{:?}", maybe_slice); // Prints 'None'
9 Likes

It does make for a surprisingly weird API, and AFAIK we don't expose the 'correct' constant to use for that case (I think it's 1 internally). It could be reasonable for from_raw_parts to handle frobbing 0 to 1 in this case.

1 Like

I see. That makes sense. Thank you.

What's Rust's philosophy regarding explaining why when docs state a non-obvious requirement? That is, if I were to submit a PR to add the why bit to the docs, would that be welcome?

On one hand, from_raw_parts is quite a footgun as-is, but, on the other hand, it is unsafe and it could be argued that it's good for materializing a slice from FFI not to involve a branch. I guess a debug_assert! on null pointer would be a middle ground, but it seems that rustup doesn't provide a debug version of the standard lib for use when debug assertions in user code are enabled. Or does it? (Another option would be to make from_raw_parts check for null and to add another from_truly_raw_parts that'd behave like the current from_raw_parts.)

In any case, it seems that the next step for me is figuring out what to change to make it easy and efficient to decompose mozilla::Span into parts that are appropriate for from_raw_parts for real. mozilla::Span is a port of gsl::span, so it can have a null pointer for a zero-length span. But since we can change mozilla::Span, one option is to make mozilla::Span to enforce the Rust invariant of the pointer never being null. Other options include a Rust-side wrapper for from_raw_parts or a mozilla::Span-side Rust-oriented pointer accessor that'd turn nullptr into 0x1 at access time.

Bug 1359874 in case anyone wants to opine on the solution.

For the const null case I created a clippy lint issue https://github.com/Manishearth/rust-clippy/issues/1703

In general, I am very much in favor of adding why. It can get tricky with things like this, since we don't want to specify too many details in some situations. We can work through it in a PR.

Submitted the PR.