-
Notifications
You must be signed in to change notification settings - Fork 12.9k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Expand is_uninhabited #36449
Expand is_uninhabited #36449
Conversation
r? @arielb1 (rust_highfive has picked a reviewer for you, use r? to override) |
I was worried because this is a feature that would be insta-stable. cc @rust-lang/lang |
I decided to expand this PR to cover (almost) all uninhabited types. There'll be at least one more commit coming. |
Okay, so this PR now does three more things:
|
☔ The latest upstream changes (presumably #36551) made this pull request unmergeable. Please resolve the merge conflicts. |
5a86bab
to
57ceb4b
Compare
One thing I just noticed about this: if a type has a private field which is uninhabited then the entire type will be uninhabited. This means something like this will work when it probably shouldn't:
So, this implementation isn't unsafe but it leaks non-public information about types. A fix would be to change |
Nominating for discussion in this week's lang meeting. |
This at the very least is a breaking change and so it would be nice to have a warning cycle. I'm not sure how that would work though. Although the initial description of this feature seems reasonable ( |
Is it outside of unsafe code? There was some code in libstd that this broke because it was letting a |
@@ -180,8 +180,9 @@ enum Void {} | |||
issue = "0")] | |||
#[doc(hidden)] | |||
pub struct ArgumentV1<'a> { | |||
value: &'a Void, | |||
formatter: fn(&Void, &mut Formatter) -> Result, | |||
_ph: PhantomData<&'a ()>, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I feel like a better fix would be to use &'a ()
. And perhaps Void
ought to be defined not as an empty enum, but rather as a zero-sized struct that exposes no constructor? This is sort of an interesting question. I'm actually not sure what's the best way to model the equivalent of void*
. Seems like something we can discuss when it comes to the unsafe code guidelines. =)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What code broke with &'a Void
? I guess there is a match
somewhere?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't remember what broke exactly but I just tested it without those changes to core::fmt
and this time it worked :/
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't think there was a match anywhere. Just something more to do with having an uninhabited type in live code causing segfaults or something.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It might have had something with old trans' _match
codegen using is_uninhabited
, directly or indirectly.
TBH, any old call to is_uninhabited
is suspicious. You also may have had more local changes at the time.
If we change any behavior here, we should warn about non-generic types that can't have values, IMO.
Otherwise we risk a lot of subtle breakage across the ecosystem (but this is more relevant to the Error
PR).
@canndrew in general, we try not to break unsafe code, though I agree that it has fewer guarantees that safe code (in particular as the unsafe code guidelines work is ongoing). But I feel like |
I feel let res: Result<u32, Void> = ...;
// valid
let Ok(x) = res;
// ERROR!
let Ok(x) = res.as_ref();
enum Three { A, B, C }
enum Two { A, B }
enum One { A }
enum Zero { }
match &some_three {
&A => ...
&B => ...
&C => ...
}
match &some_two {
&A => ...
&B => ...
}
match &some_one {
&A => ...
}
// ERROR!
match &some_zero {
} A lot of people have had trouble adjusting to the concept of uninhabited types. It's a new concept to most programmers. Hopefully though this will change, particularly with the addition of |
Was this discussed in the lang meeting? What was the verdict? |
@canndrew Gah, we missed it due to lack of T-lang label! |
The main concern on the lang team side is that this could break existing matches against types like We'll revisit with crater data. |
Discussed in the @rust-lang/lang meeting briefly. I started a crater run, which seems like some basic data. This can certainly break the static semantics of existing code, right? I think we can legitimately consider this a bug fix, but we'll have to do warnings and so forth. Crater run is comparing: 0a0215d 57ceb4b3c6e6a711124c1a87254ff5cf3de1e1a3 |
@aturon I'm confused by that worry, AFAIK the existing code only uses |
I'd r+ this with the libcore changes removed, TBH. |
@brson hmm it appears that crater came back with null results for me |
I've started a crater run. |
Still working on it. |
No regressions. Some false positives. |
So is this good to go then? |
☔ The latest upstream changes (presumably #37824) made this pull request unmergeable. Please resolve the merge conflicts. |
@canndrew Apologies for the delay. Yes, I think this is ready to land. cc @nikomatsakis @eddyb |
enum Void {} | ||
struct Void { | ||
_private: (), | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'd rather not have any change in libcore
if possible.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What's the problem with this change? I think this is what @nikomatsakis meant by redefining Void
. We need to make some change here or else that module breaks.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Does it break practically, or conceptually?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
First conceptually, then practically when tests fail.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What tests? I don't want to land this if it changes semantics of code that already compiles (as opposed to just letting more code compiles).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
IOW, this is a good/necessary change long-term, but it shouldn't be needed here - if it is, something is wrong with the assumption we're making which is the basis for allowing this PR through.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hmmm... I just removed that change and now all tests pass. I definitely added it out of necessity though, something must have changed in the intervening two months.
I'll do another commit to remove it.
impl<'a, 'gcx, 'tcx> AdtDefData<'tcx, 'static> { | ||
#[inline] | ||
pub fn is_uninhabited_recurse(&'tcx self, | ||
visited: &mut HashMap<(DefId, &'tcx Substs<'tcx>), ()>, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is a set, not a map and you should use FxHashSet
.
890c169
to
d756f61
Compare
substs: &'tcx Substs<'tcx>, | ||
is_union: bool) -> bool { | ||
if is_union { | ||
self.fields.iter().all(|f| f.is_uninhabited_recurse(visited, block, cx, substs)) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This assumes an union
is always one of its members. Did we ever settle this? cc @petrochenkov
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@eddyb
This is certainly a desirable property, but it can be violated right now, see example in #32836 (comment). So it's not settled yet.
However, the logic "all fields are uninhabitable" -> "union is uninhabitable" seems to be correct - a union needs to be initialized before use (move checker ensures this) and it can't be initialized if it only has uninhabited fields.
@bors r+ |
📌 Commit 2121118 has been approved by |
Expand is_uninhabited This allows code such as this to compile: ``` rust let x: ! = ...; match x {}; let y: (u32, !) = ...; match y {}; ``` @eddyb You were worried about making this change. Do you have any idea about what could break? Are there any special tests that need to be written for it?
This allows code such as this to compile:
@eddyb You were worried about making this change. Do you have any idea about what could break? Are there any special tests that need to be written for it?