Skip to content

consider changing size of empty enum to usize::MAX #1076

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

Open
pnkfelix opened this issue Apr 20, 2015 · 5 comments
Open

consider changing size of empty enum to usize::MAX #1076

pnkfelix opened this issue Apr 20, 2015 · 5 comments
Labels
T-compiler Relevant to the compiler team, which will review and decide on the RFC. T-lang Relevant to the language team, which will review and decide on the RFC.

Comments

@pnkfelix
Copy link
Member

spawned off of rust-lang/rust#24590

Empty enums such as:

enum Empty { }

are a hazard in a number of ways; in particular, odd people like myself keep wanting to investigate what happens if you unsafely inject a value into Empty, e.g. via mem::transmute (or by derefing an unsafe pointer *const Empty, etc).

The resulting code you get when doing a match on such a value is pretty much guaranteed to be undefined behavior. The project developers have stated that the memory layout of an empty enum (in fact, of all non C-like enums) is undefined, and thus undefined behavior is justifiable here.

Still, it seems to easy to expose this case, and it seems like it would be really easy to prevent it, e.g. by changing the representation size of an empty enum to something semi-absurd. Like usize::MAX.

@pnkfelix
Copy link
Member Author

(another option that may be more palatable for various reasons would be to give empty enums zero size rather than usize::MAX size...)

@glaebhoerl
Copy link
Contributor

As argued (not by me) in this thread the logically sensible size for uninhabited types would actually be negative infinity, because this makes the sizes of product and sum types with uninhabited types as members work out properly. sizeof Pair<A, B> = sizeof A + sizeof B1; then sizeof Pair<Foo, Uninhabited> is appropriately negative infinity as well, reflecting the fact a struct with an uninhabited type as a member is also uninhabited. And sizeof Either<A, B> = max(sizeof A, sizeof B)1; then sizeof Either<Foo, Uninhabited> = sizeof Foo as we would expect, reflecting the fact that an enum with an uninhabited type as one of the variants is equivalent to an enum without that variant.

Of course, this doesn't help much in practice because the type of sizes of types is usize which is not inhabited by negative infinity. (We'd probably want Option<usize> or something isomorphic if we wanted to be able to represent things that way.) So I dunno.

Might this also be related to DST, e.g. the "truly unsized types" stuff?

1 To a first approximation, of course there's also padding, the tag field, etc.

(Edit: I see you've also considered Option<usize> in the other thread...)

@Diggsey
Copy link
Contributor

Diggsey commented Apr 20, 2015

It's not enough for it to have a huge/zero/negative infinity size, because even a reference to an uninhabited type should itself be an uninhabited type (since references are guaranteed to be valid). It could be implemented using a more general version of DSTs/truly unsized types, where types get to freely choose the representation of references to themselves (there could be a built-in uninhabited type which all uninhabited types use as the representation for their references).

More realistically, shouldn't the compiler detect that code which actually uses uninhabited types, either directly or through a reference, is invalid? (similar, but not quite the same as how you can't use incomplete types in C++)

@lilyball
Copy link
Contributor

odd people like myself keep wanting to investigate what happens if you unsafely inject a value into Empty, e.g. via mem::transmute (or by derefing an unsafe pointer *const Empty, etc)

Given that uninhabited types currently have a size of 0, how can you inject a value into it? The only legal mem::transmutes should be transmutations of zero-sized values (such as ()), which doesn't seem like a problem. Coercing a pointer to some other value to a pointer to an uninhabited type should be a safe operation. The only thing that I think should change is that it should be illegal to dereference a pointer to an uninhabited type. That would rule out your match *x {} (from rust-lang/rust#24590) and thus avoid the undefined behavior.

@sfackler
Copy link
Member

I have been looking into using an uninhabited type in some changes to rust-postgres to avoid hardcoding support for specific SSL backends: sfackler/rust-postgres@49c09c3#diff-9a0fb54f94cd0bef81a2ada2e9fb0c57R46. It involves having a type that stores the uninhabited type by value, so setting its size to usize::MAX would probably be bad news.

The idea is that the default for the N type parameter on SslMode means you can pass &mut SslMode::None directly to the functions that expect one without needing a "real" SSL implementation around. This doesn't actually work right now since default parameters don't seem to be sufficiently respected unfortunately. Note the use of match *x {} to create a dummy implementation of a trait here: sfackler/rust-postgres@49c09c3#diff-9a0fb54f94cd0bef81a2ada2e9fb0c57R60

@petrochenkov petrochenkov added T-lang Relevant to the language team, which will review and decide on the RFC. T-compiler Relevant to the compiler team, which will review and decide on the RFC. labels Jan 29, 2018
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
T-compiler Relevant to the compiler team, which will review and decide on the RFC. T-lang Relevant to the language team, which will review and decide on the RFC.
Projects
None yet
Development

No branches or pull requests

7 participants