-
Notifications
You must be signed in to change notification settings - Fork 12.8k
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
What are semantics of empty enums that have been artificially fabricated? #4499
Comments
I don't think this actually does work? Since it's impossible to construct a value of type |
Oh, you are right. My example is bogus and doesn't demonstrate the problem. Take this one:
Of course this code can never be run (without But there are a lot of other similar things we could catch, like using |
Huh, now I'm morbidly wondering what happens if you cast something to I'm not really sure what error we should report here, tbh. I'd be fine with disallowing uninhabited types, but they do have their uses. If we make a conscious decision to allow uninhabited types, then it doesn't seem principled to forbid doing perfectly sound things with them. After all, if someone is using uninhabited types, they are probably up to some monkey business that we can't usefully second-guess. |
I think we should have a policy that uninhabited types are fine, but if we encounter a place where we can't sensibly codegen because the type is uninhabited, we generate a This case is borderline to me because being C-like (and hence castable to uint) is not a property that all enums share, so we can choose to say that empty enums do not have this property if we like. However, it also seems reasonable to say "C-like enums are those for which all variants have arguments", in which case empty variants qualify. In that case, I'd say that 'foo as uint' should |
That's what we already do for matching on an empty enum -- fail with "the impossible happened" or something like that. |
right, that's what I thought we did |
This is really a language spec question. Nominating for milestone 1. |
consensus is "this should be an error", accepted for backwards compatibility |
The only thing you can cast to an empty enum is another 0-size struct, so attempting anything else will give a This test case fails with
I also can't seem to cast an empty enum to anything. The compiler says However, maybe we could also make the type-size of empty enums something other than 0, so even the above transmute attempt fails? Would that make sense, or would it mess up e.g. cases where you have a void inside a struct? |
Triage bump. |
(maybe we should hide empty Enums under a feature flag for 1.0?) |
On Thu, Sep 26, 2013 at 10:53:58AM -0700, Felix S Klock II wrote:
No! Let's just decide and settle this. It is... really not important. |
@nikomatsakis its more that there are other things that seem ... questionable with empty enums. Like case of |
visiting for triage @nikomatsakis @pnkfelix @brson what's the final take here? |
@flaper87 I'm inclined at this point to go with @nikomatsakis and say that behavior now, as described by @bblum in the above comment is fine. There may be other oddities with empty enums (like #2634), but as it stands this bug is kind of bogus: According to a survey I just did of the examples provided in the description and comments, only bblum's comment seems to reflect the current state of affairs, and his example does not describe a bug per se, but rather just shows how you can currently use I'm going to update the description to reflect this state of things. And then I'm going to close the bug. |
Okay, I am not going to close the bug quite yet, because I would like us to official decide whether or not our current behavior on these two cases (copied from my updated bug description) are okay: use std::cast;
enum Foo { }
struct Bar;
#[cfg(ex6)]
fn main() {
let b : Bar = Bar;
let i : Foo = unsafe { cast::transmute(b) };
println!("b: {:?}", b);
match i {
}
}
#[cfg(ex7)]
fn main() {
let b : Bar = Bar;
let i : Foo = unsafe { cast::transmute(b) };
println!("b: {:?}", b);
match i {
_ => { println!("The impossible!"); }
}
} The difference in the two behaviors is kind of strange/interesting. I think the results are acceptable, but it might also be reasonable to think that |
Nominating for 1.0, P-backcompat-lang. |
@pnkfelix uh, mmhh o_0 IMHO, |
The question is absurd. Types are propositions, and constructing an inhabitant of an uninhabited type corresponds to proving a contradiction. If you could do it in safe code, it would mean the type system is unsound. Doing it in |
In case it is not clear, my main goal was to report what the implementation does now, since one of the suggested paths forward was "whatever the behavior is now, it's fine." I wanted to see what the current behavior was before I signed off on that path. What I was expecting when I made these experiments, based on this comment thread, was for that @flaper87 I don't have a strong objection to making @glaebhoerl The question may indeed be absurd. I did not intend to imply that I wanted a concrete definition of the behavior one gets; I just whipped up the current bug title because the previous title on the bug was misleading about the issue here. I do not mind saying that using unsafe code to create an instance of an empty enum yields undefined behavior. But I do think that it is worth spelling that undefined-ness here out explicitly in our documentation somewhere; mixing together unsafe transmutation and type-safe algebraic enums is not standard programming-language practice. (I have seen plenty of such transmutation when implementing language runtimes, but my impression is that Rust must define up front the semantics of many such interactions between unsafe constructs and the type system.) In short, I still think it is interesting that the behavior differs so significantly between |
An empty It might be a good idea to forbid any arms at all, including wildcards, inside an empty enum EDIT: If I write this:
the compiler complains:
If I remove the variant from the |
@pcwalton points out that memory layout of non C-like enums are undefined. as long as zero-variant enums are not C-like enums, I am willing to retract my prior belief about this being a 1.0 issue. Unnominating. |
I've opened #12609 about what I see as the actual bug here: you should not be able to even write a wildcard match on an empty enum, so there's no behavioral inconsistency between ex6 and ex7, as only ex6 should be legal. |
Is there a way in which constructing a value of a type which supposedly has zero values can be regarded as anything but a flagrant abuse of the type system and severely undefined behaviour? With or without enums having an undefined representation, it seems that having a value of an uninhabited type is horribly bad practice, and we don't need to specify what happens when one does occur (nasal demons and so on). In particular, assuming that trait Foo<T, E> { fn run(&self) -> Result<T, E>; }
impl Foo<int, Void> for PureIntCalculation {
fn run(&self) -> Result<int, Void> { ... }
}
impl Foo<int, IoError> for ImpureIntCalc { ... } where the first |
I think this is clearly undefined under the current definition of |
Updated bug report follows (see bottom for original bug report)
This bug appears to have been filed in error but then spawned a very interesting conversation.
The question centers around how empty enums (e.g.
enum Foo { }
, which has zero variants) which in principle cannot safely exist, interact with our casting operations (namely safeas
and unsafecast::transmute
).Here is an some code that I wrote that summarizes the various scenarios that were described in the comments. Most of the scenarios do not compile (which is good). The fifth, sixth, and seventh all compile; their runtime behaviors vary, but most are acceptable. The only questionable thing @pnkfelix can see here is the behavior for the seventh example,
cfg ex7
.Code:
Transcript of compile (+ runs when compilable):
Original bug report follows:
Something like this works:
Doesn't make any sense.
The text was updated successfully, but these errors were encountered: