Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 12 additions & 4 deletions src/behavior-considered-undefined.md
Original file line number Diff line number Diff line change
Expand Up @@ -145,10 +145,15 @@ r[undefined.validity.struct]
* A `struct`, tuple, and array requires all fields/elements to be valid at their respective type.

r[undefined.validity.union]
* For a `union`, the exact validity requirements are not decided yet. Obviously, all values that can be created entirely in safe code are valid. If the union has a [zero-sized] field, then every possible value is valid. Further details are [still being debated](https://github.com/rust-lang/unsafe-code-guidelines/issues/438).
* For a `union`, there are no validity requirements. All byte sequences are valid union values.

r[undefined.validity.reference-box]
* A reference or [`Box<T>`] must be aligned and non-null, it cannot be [dangling], and it must point to a valid value (in case of dynamically sized types, using the actual dynamic type of the pointee as determined by the [metadata]). Note that the last point (about pointing to a valid value) remains a subject of some debate.
* A reference or [`Box<T>`] must be aligned and non-null, it cannot be [dangling], and the pointee type `T` must be *inhabited*.

The exact classification of inhabited types is unspecified, similar to the size and alignment of types.
However, types that can be constructed from safe code are definitely inhabited.
For unsized types, this check considers dynamic information from the metadata:
In particular, if `T` has an unsized tail of slice type `[U]`, and if `U` is uninhabited, and if the length encoded in the metadata is non-zero, then the pointee is uninhabited.
Comment on lines +155 to +156

@WaffleLapkin WaffleLapkin Sep 8, 2026

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I feel like making inhabitedness depend on metadata values is not right / clean.

I would rather make this a separate restriction on the value of the metadata. That is, I would say something along the lines of "A reference or Box<T> must ... have valid metadata" and "slice metadata for a slice type with uninhabited elements, it must be 0".

This allows adding other restrictions in the future, such as the one mentioned by @scottmcm that slice metadata can at most be usize::MAX / size_of::<T>() (for non-ZSTs).

View changes since the review

@RalfJung RalfJung Sep 8, 2026

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We generally pretty consistently treat the metadata as telling us the "actual type" that we use to inspect the pointee. I think it would be odd do do something else here. It would look like an arbitrary set of rules, rather than just something that falls out of a more general principle.

This allows adding other restrictions in the future, such as the one mentioned by @scottmcm that slice metadata can at most be usize::MAX / size_of::() (for non-ZSTs).

As I mentioned in reply to Scott, that restriction is already present. It follows from the fact that the reference has to be dereferenceable for its actual dynamic size.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hm. It still feels weird to me to do it this way for slices, but if we are consistently doing it this way I suppose that is better.

I suppose the reason it feels weird to me is that in the compiler "inhabitedness" is a property of types, and does not concern the value in any way. If we would like exploit this UB in the compiler, we would have to add an arbitrary rule in the form of "if the tail is a slice of an uninhabited type, assume(len == 0)".

In a way, considering the "actual type" feels more arbitrary to me.

That being said, I imagine we have to consider the "actual type" for trait objects, at which point it's better do the same thing for slices too.

(I'd resolve this conversation, but it appears I don't have permissions for that)

@RalfJung RalfJung Sep 8, 2026

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The compiler can only statically approximate all these properties using the types, yes. But that's expected, everything on this page is written assuming full knowledge of the current dynamic state (think: Miri, MiniRust). The compiler might get better at approximating things and that should not change the spec!

IOW, I think you're thinking too much like a compiler writer and not enough like a language specifier. :)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

guilty as charged I'm afraid ^^'

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@RalfJung I agree that this is not stated correctly. It's not about uninhabitedness, this is a validity clause. Inhabitedness is a property of a type, validity is a property of a value of the type.

Suggested change
For unsized types, this check considers dynamic information from the metadata:
In particular, if `T` has an unsized tail of slice type `[U]`, and if `U` is uninhabited, and if the length encoded in the metadata is non-zero, then the pointee is uninhabited.
For unsized types, this check considers dynamic information from the metadata:
In particular, if `T` has an unsized tail of slice type `[U]`, and if `U` is uninhabited, then the only valid value of the length encoded in the metadata is zero.

@RalfJung RalfJung Sep 23, 2026

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it is part of inhabitedness. I view inhabitedness as a property of a potentially unsized type and metadata. We use the metadata to figure out what the actual dynamic type is, and then evaluate inhabitedness of that.

That's what it's going to look like in Miri and MiniRust, and IMO it's the structure we should also follow here. I thought it was okay to leave that implicit, but I see that it is too confusing, so I will try to reword.

@digama0 digama0 Sep 23, 2026

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No, that much I agree with; the part I disagree with is that the consequence is about inhabitedness (of a type). For a type like &dyn NeverImplements, obviously it's going to be inhabited since any types could be implementing NeverImplements. What we want to say is that the value [ptr, never_vtable]: &dyn NeverImplements is invalid, for the reason that the vtable is for a type that is uninhabited. For the unsized type dyn NeverImplements itself, that's a whole separate question, I'm not really sure what validity/inhabitedness means for unsized types since they don't exist on their own but the natural reading is that it is still inhabited, and it has "values" only after fixing the metadata, and there are no valid values which have never_vtable as the metadata.

The reasoning for the present case is similar: if U is uninhabited, then [U; 1] is uninhabited and &[U; 1] is uninhabited and &[U] is inhabited but has only the valid value len = 0; the value [ptr, 1]: &[U] is not valid for the type regardless of ptr. If you would like to say that the type [U] is "uninhabited at len=1" then that's fine but a new concept we haven't discussed before, and also apparently the same thing as just saying [U; 1] is uninhabited.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What is being defined here is whether dyn NeverImplements is inhabited. Which depends on the metadata, just like inhabitedness of [!].

@RalfJung RalfJung Sep 23, 2026

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If you would like to say that the type [U] is "uninhabited at len=1" then that's fine but a new concept we haven't discussed before,

That's exactly what I mean and what I thought we had discussed: for unsized types, inhabitedness depends on the metadata. Slices are inhabited iff the length is 0 or the element type is inhabited. dyn Trait is inhabited iff the actual dynamic type represented in the metadata is inhabited.


r[undefined.validity.wide]
* The [metadata] of a wide reference, [`Box<T>`], or raw pointer must match the type of the [unsized tail]:
Expand Down Expand Up @@ -195,8 +200,11 @@ r[undefined.validity.const-provenance]
> };
> ```

r[undefined.validity.undef]
**Note:** Uninitialized memory is also implicitly invalid for any type that has a restricted set of valid values. In other words, the only cases in which reading uninitialized memory is permitted are inside `union`s and in "padding" (the gaps between the fields of a type).
> [!NOTE]
> The definition above implies that uninitialized memory is invalid everywhere except inside `union`s and in "padding" (the gaps between the fields of a type).

> [!WARNING]
> Just because a value is *valid* does not mean that it is *safe to use*. A value being *valid* merely means that creating the value does not cause immediate undefined behavior; such UB can still be caused later, even by safe operations. For instance, consider that `&str` pointing to initialized non-UTF8 data is *valid* as defined above, but passing such a value to a safe function can cause undefined behavior. As a more extreme example, consider that `&[u8]` pointing to allocated but uninitialized memory is *valid*, but one can trivially cause UB simply by accessing an element of the slice. Generally, only values you can construct in safe code are *safe to use*.

[`bool`]: types/boolean.md
[`const`]: items/constant-items.md
Expand Down
Loading