You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Can references to uninhabited types ever be valid? #413
This discussion presupposes that "Does reference validity depend on the contents of memory in any way" (#414) is answered "no". This issue is only about references to uninhabited types; references to any other type are not relevant here.
So, in general we allow references to point to invalid data. Is the following UB?
let x:&! = transmute(&());
Since there is no recursive validity for references, the answer might be "no". However, the reason we could consider making this UB is that all of the arguments in #412 do not apply: to determine whether an &! is valid, we do not have to check memory, do any potentially racy reads, or anything like that. We can just answer the question with "no". Recursive validity is tricky in part because references can "bounce" between being valid and invalid as the contents of memory change; but for references to uninhabited types, this concern does not apply.
Basically, we can think of ! having an impossible-to-satisfy alignment requirement, and therefore &! is invalid since it is never properly aligned. (I don't really want to fold this into the alignment check, but the point is that we already have the validity of &T depend on T, namely via alignment -- I see no fundamental reason why we couldn't do the same with inhabitedness.)
Does this issue also cover &Void and &(!, T)? The latter is problematic since I think there are situations where you can have partially uninit references (and this also partially overlaps with #346). But maybe it suffices to say that an initialized&T (i.e. a value returned from a function, like your transmute example) requires that T be inhabited, even though uninitialized or partially initialized places of type &! or &(!, T) can exist.
One motivation is being able to replace the entire body of an &self function where self is uninhabited with just unreachable_unchecked(). Back at the last Rust All hands (2019 I think?), someone expressed strong interest in that (code size) optimization.
One motivation is being able to replace the entire body of an &self function where self is uninhabited with just unreachable_unchecked(). Back at the last Rust All hands (2019 I think?), someone expressed strong interest in that (code size) optimization.
This has been mentioned elsewhere and I think you agreed to it, but that is not a very compelling motivation since you can just put match *self {} in the body to get the same optimization. (If I had my druthers match self {} would also work; this does not strictly speaking require a "no" answer to the OP question.)
While you can have partially initialized locals of type (!, T), I don't think you can have references to them. Do you have an example for that?
I don't, at least not without writing an unsafe block that would be UB under the OP question (more or less by definition). A variation that may or may not count is &Box<!>; I'm not sure whether this counts as inhabited or not, and it might actually thread the needle between competing notions of inhabitedness since you can empty-match on it and move out a ! (although maybe you want to call this a safety violation), even though by structural analysis of Box<!> you get a Unique which could just be a dangling pointer.
My main motivation for considering that case specially is that the borrow checker does allow for types like &(T, U) to have more complex initialization states, and future lang proposals might reify these as types (e.g. view types), in which case you might have a function that takes an argument of type &{only T is init} (!, T) and you would not be able to assume that this type is uninhabited.
One motivation is being able to replace the entire body of an &self function where self is uninhabited with just unreachable_unchecked(). Back at the last Rust All hands (2019 I think?), someone expressed strong interest in that (code size) optimization.
Wouldn't it be almost always trivial to prove that such a function is unreachable anyway? Assuming reachability checks occur post-monomorphisation.
One motivation is being able to replace the entire body of an &self function where self is uninhabited with just unreachable_unchecked(). Back at the last Rust All hands (2019 I think?), someone expressed strong interest in that (code size) optimization.
I feel like we could do an experiment here to figure out if this is actually worth anything?
If it's unreachable, then you wouldn't even need an unreachable_unchecked, the whole function could be deleted. But it could be a field in a trait object, for example, in which case you do need the function (but it could be a dangling pointer? 🤔 )
This has been mentioned elsewhere and I think you agreed to it, but that is not a very compelling motivation since you can just put match *self {} in the body to get the same optimization. (If I had my druthers match self {} would also work; this does not strictly speaking require a "no" answer to the OP question.)
I'm trying to remember the old discussion. The concern might have been that these are trait impls generated automatically / via proc macros for tons of types. So we can't just add custom code into the body.
I think it was @cramertj who raised this wish back then, not sure if they still remember any details though.
Wouldn't it be almost always trivial to prove that such a function is unreachable anyway? Assuming reachability checks occur post-monomorphisation.
If it's a public function in a lib crate, how would that be trivial?
I guess the benchmarks don't contain auto-derived code for uninhabited types. Also as a MIR opt this is less effective than it could be, if generic types and their impls are monomorphized for an uninhabited type.
My main motivation for considering that case specially is that the borrow checker does allow for types like &(T, U) to have more complex initialization states
I have never seen this happen, so this is news to me. What is an example of that?
I take it back, the borrow checker will reason about partially initialized Box<(T, U)> but not &(T, U). However I can come up with examples that would plausibly benefit from it:
Currently, the returned closure captures &x.a, ..., &x.y as references and hence would be 25 pointers large, but a future optimization could capture &x instead with a borrow state indicating that x.z is uninit.
I think we have to treat this more like a layout question: the unspecified layout algorithm will determine whether a type is inhabited. It guarantees that certain types are inhabited, but for the remainder, either answer is valid. The layout algorithm can change in the future.
We discussed this in the opsem meeting and the general vibe was to continue down this route: "inhabited" is an underspecified function on types similar to "size" and "alignment". We do not guarantee anything about that function except "if the validity invariant of a type is satisfiable, then the type is inhabited".
Activity
digama0 commentedon Jun 6, 2023
Does this issue also cover
&Voidand&(!, T)? The latter is problematic since I think there are situations where you can have partially uninit references (and this also partially overlaps with #346). But maybe it suffices to say that an initialized&T(i.e. a value returned from a function, like yourtransmuteexample) requires thatTbe inhabited, even though uninitialized or partially initialized places of type&!or&(!, T)can exist.RalfJung commentedon Jun 6, 2023
Yes.
While you can have partially initialized locals of type
(!, T), I don't think you can have references to them. Do you have an example for that?chorman0773 commentedon Jun 6, 2023
Is there a reason why they should be invalid unless
&Tis invalid if theTis?RalfJung commentedon Jun 6, 2023
One motivation is being able to replace the entire body of an
&selffunction whereselfis uninhabited with justunreachable_unchecked(). Back at the last Rust All hands (2019 I think?), someone expressed strong interest in that (code size) optimization.digama0 commentedon Jun 6, 2023
This has been mentioned elsewhere and I think you agreed to it, but that is not a very compelling motivation since you can just put
match *self {}in the body to get the same optimization. (If I had my druthersmatch self {}would also work; this does not strictly speaking require a "no" answer to the OP question.)digama0 commentedon Jun 6, 2023
I don't, at least not without writing an unsafe block that would be UB under the OP question (more or less by definition). A variation that may or may not count is
&Box<!>; I'm not sure whether this counts as inhabited or not, and it might actually thread the needle between competing notions of inhabitedness since you can empty-match on it and move out a!(although maybe you want to call this a safety violation), even though by structural analysis ofBox<!>you get aUniquewhich could just be a dangling pointer.My main motivation for considering that case specially is that the borrow checker does allow for types like
&(T, U)to have more complex initialization states, and future lang proposals might reify these as types (e.g. view types), in which case you might have a function that takes an argument of type&{only T is init} (!, T)and you would not be able to assume that this type is uninhabited.Diggsey commentedon Jun 6, 2023
Wouldn't it be almost always trivial to prove that such a function is unreachable anyway? Assuming reachability checks occur post-monomorphisation.
saethlin commentedon Jun 6, 2023
I feel like we could do an experiment here to figure out if this is actually worth anything?
digama0 commentedon Jun 6, 2023
If it's unreachable, then you wouldn't even need an
unreachable_unchecked, the whole function could be deleted. But it could be a field in a trait object, for example, in which case you do need the function (but it could be a dangling pointer? 🤔 )RalfJung commentedon Jun 7, 2023
I'm trying to remember the old discussion. The concern might have been that these are trait impls generated automatically / via proc macros for tons of types. So we can't just add custom code into the body.
I think it was @cramertj who raised this wish back then, not sure if they still remember any details though.
If it's a public function in a lib crate, how would that be trivial?
saethlin commentedon Jun 7, 2023
The perf run for this optimization implemented as a MIR pass indicates no benefit in terms of code size: https://perf.rust-lang.org/compare.html?start=afab3662eb066f05fcdb43c421b72dd19472e752&end=0f4641ea16e650c77a03aa649330702722a5f66f&stat=size%3Alinked_artifact
RalfJung commentedon Jun 8, 2023
I guess the benchmarks don't contain auto-derived code for uninhabited types. Also as a MIR opt this is less effective than it could be, if generic types and their impls are monomorphized for an uninhabited type.
RalfJung commentedon Jun 8, 2023
I have never seen this happen, so this is news to me. What is an example of that?
digama0 commentedon Jun 8, 2023
I take it back, the borrow checker will reason about partially initialized
Box<(T, U)>but not&(T, U). However I can come up with examples that would plausibly benefit from it:Currently, the returned closure captures
&x.a, ..., &x.yas references and hence would be 25 pointers large, but a future optimization could capture&xinstead with a borrow state indicating thatx.zis uninit.109 remaining items
RalfJung commentedon May 28, 2026
We discussed this in the opsem meeting and the general vibe was to continue down this route: "inhabited" is an underspecified function on types similar to "size" and "alignment". We do not guarantee anything about that function except "if the validity invariant of a type is satisfiable, then the type is inhabited".
@rfcbot resolve recursive-types
rust-rfcbot commentedon May 28, 2026
🔔 This is now entering its final comment period, as per the review above. 🔔
rust-rfcbot commentedon Jun 7, 2026
The final comment period, with a disposition to merge, as per the review above, is now complete.
As the automated representative of the governance process, I would like to thank the author for their work and everyone else who contributed.
Rollup merge of rust-lang#156977 - RalfJung:interpret-opsem-inhabited…
Auto merge of #156977 - RalfJung:interpret-opsem-inhabited, r=WaffleL…
Auto merge of #156977 - RalfJung:interpret-opsem-inhabited, r=WaffleL…
Auto merge of #156977 - RalfJung:interpret-opsem-inhabited, r=WaffleL…
Rollup merge of rust-lang#163121 - Amanieu:inline-dest-rawptr, r=saet…
Unrolled build for #163121 in rollup 163189
Rollup merge of #163121 - Amanieu:inline-dest-rawptr, r=saethlin