From 13a9e799bbc6e31e1075f8b9223780e7f79a72ae Mon Sep 17 00:00:00 2001 From: scottmcm Date: Fri, 15 Aug 2025 07:48:37 +0000 Subject: [PATCH 1/5] Add a note about uninhabited-struct layout optimization --- src/frequently-requested-changes.md | 44 +++++++++++++++++++++++++++++ 1 file changed, 44 insertions(+) diff --git a/src/frequently-requested-changes.md b/src/frequently-requested-changes.md index 3e5229b..75e608b 100644 --- a/src/frequently-requested-changes.md +++ b/src/frequently-requested-changes.md @@ -217,3 +217,47 @@ Cross-referencing to other discussions: * https://github.com/rust-lang/rfcs/issues/1397 * https://github.com/rust-lang/rust/issues/17027 * https://github.com/rust-lang/unsafe-code-guidelines/issues/176 + +## Uninhabited `struct`s should all be ZSTs + +It makes conceptual sense that if something is uninhabited, it shouldn't take up any space. +In safe code that works great, but we tried it and ran into problems, so it's not likely to happen. + +The biggest problem is related to field projection during initialization. Take this code: + +```rust +pub fn make_pair(a0: impl Fn() -> T0, a1: impl Fn() -> T1) -> Box<(T0, T1)> { + let mut mu = Box::<(T0, T1)>::new_uninit(); + unsafe { + let p0 = &raw mut (*mu.as_mut_ptr()).0; + p0.write(a0()); + + let p1 = &raw mut (*mu.as_mut_ptr()).1; + p1.write(a1()); + + mu.assume_init() + } +} +``` + +Is that *sound*? It sure looks reasonable -- after all, it initialized both the fields -- but +it depends on exactly what the layout rules are. + +(Aside: Note that a production-ready version of that function should also handle unwinding cleanup +of the first value if constructing the second panicked, but for simplicity of presentation we're +ignoring that part here because leaking is still *sound*.) + +For something simple like `make_pair::`, it's clearly fine. But with `make_pair::` +it's *only* sound if we *don't* let `(u32, !)` become a ZST. We need the allocation for the box +to be large enough to write that `u32` without being an obviously-UB out-of-bounds write. + +Thus if we wanted to always have uninhabited product types be ZSTs, we'd need to give up on certain +other rules, perhaps the one that `T` and `MaybeUninit` always have the same size. So far, the +simpler, less-error-prone experience for writing unsafe code has won out over the minimal space +savings possible from shrinking the types. After all, while it's not necessarily fully unreachable, +as something like `make_pair(|| a, || loop { … })` would still need to allocate the space despite +that never reaching the `assume_init` part, it's still unlikely that this occurs frequently. + +There *is* still interest in doing optimizations like this on *sum* types, however. There's more +to potentially be gained there since one variant of a `union` or `enum` being uninhabited doesn't +keep the whole *value* from being uninhabited the way an uninhabited field does in a `struct`. From c01fff94b29fedce2280d19d5231dab56468e89f Mon Sep 17 00:00:00 2001 From: scottmcm Date: Fri, 15 Aug 2025 18:01:45 +0000 Subject: [PATCH 2/5] Update src/frequently-requested-changes.md Co-authored-by: Jacob Lifshay --- src/frequently-requested-changes.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/frequently-requested-changes.md b/src/frequently-requested-changes.md index 75e608b..58f28ee 100644 --- a/src/frequently-requested-changes.md +++ b/src/frequently-requested-changes.md @@ -258,6 +258,6 @@ savings possible from shrinking the types. After all, while it's not necessaril as something like `make_pair(|| a, || loop { … })` would still need to allocate the space despite that never reaching the `assume_init` part, it's still unlikely that this occurs frequently. -There *is* still interest in doing optimizations like this on *sum* types, however. There's more +There *is* still interest in maybe doing optimizations like this on *sum* types, however. There's more to potentially be gained there since one variant of a `union` or `enum` being uninhabited doesn't keep the whole *value* from being uninhabited the way an uninhabited field does in a `struct`. From 0c9d7a84ea1cf20d778d6c205ec5d97214023e66 Mon Sep 17 00:00:00 2001 From: scottmcm Date: Fri, 15 Aug 2025 18:58:20 +0000 Subject: [PATCH 3/5] Remove the `union` mention --- src/frequently-requested-changes.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/frequently-requested-changes.md b/src/frequently-requested-changes.md index 58f28ee..cb6f388 100644 --- a/src/frequently-requested-changes.md +++ b/src/frequently-requested-changes.md @@ -259,5 +259,5 @@ as something like `make_pair(|| a, || loop { … })` would still need to allocat that never reaching the `assume_init` part, it's still unlikely that this occurs frequently. There *is* still interest in maybe doing optimizations like this on *sum* types, however. There's more -to potentially be gained there since one variant of a `union` or `enum` being uninhabited doesn't +to potentially be gained there since one variant of an `enum` being uninhabited doesn't keep the whole *value* from being uninhabited the way an uninhabited field does in a `struct`. From 2dbc56c4d482caa41910140ebb590ddea4ece439 Mon Sep 17 00:00:00 2001 From: Josh Triplett Date: Thu, 2 Jul 2026 18:05:48 -0700 Subject: [PATCH 4/5] Fix incorrect negation and simplify wording around enum variants --- src/frequently-requested-changes.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/frequently-requested-changes.md b/src/frequently-requested-changes.md index cb6f388..d9c52ee 100644 --- a/src/frequently-requested-changes.md +++ b/src/frequently-requested-changes.md @@ -260,4 +260,4 @@ that never reaching the `assume_init` part, it's still unlikely that this occurs There *is* still interest in maybe doing optimizations like this on *sum* types, however. There's more to potentially be gained there since one variant of an `enum` being uninhabited doesn't -keep the whole *value* from being uninhabited the way an uninhabited field does in a `struct`. +make the whole `enum` uninhabited the way an uninhabited field does in a `struct`. From eaba985e5027c394dcdcba3afa189fec2beb06f8 Mon Sep 17 00:00:00 2001 From: Josh Triplett Date: Thu, 2 Jul 2026 18:09:40 -0700 Subject: [PATCH 5/5] Expand acronym "ZSTs" in heading Suggested-by: Dave Halter --- src/frequently-requested-changes.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/frequently-requested-changes.md b/src/frequently-requested-changes.md index d9c52ee..4c5578b 100644 --- a/src/frequently-requested-changes.md +++ b/src/frequently-requested-changes.md @@ -218,7 +218,7 @@ Cross-referencing to other discussions: * https://github.com/rust-lang/rust/issues/17027 * https://github.com/rust-lang/unsafe-code-guidelines/issues/176 -## Uninhabited `struct`s should all be ZSTs +## Uninhabited `struct`s should all be zero-sized types (ZSTs) It makes conceptual sense that if something is uninhabited, it shouldn't take up any space. In safe code that works great, but we tried it and ran into problems, so it's not likely to happen.