Skip to content
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

Can't as cast Void to usize #75647

Closed
digama0 opened this issue Aug 17, 2020 · 18 comments · Fixed by #76199
Closed

Can't as cast Void to usize #75647

digama0 opened this issue Aug 17, 2020 · 18 comments · Fixed by #76199
Labels
T-lang Relevant to the language team, which will review and decide on the PR/issue.

Comments

@digama0
Copy link
Contributor

digama0 commented Aug 17, 2020

enum Void { }
fn void_as_usize(x: Void) -> usize { x as usize } // fails, non-primitive cast

As reported on Zulip: https://rust-lang.zulipchat.com/#narrow/stream/122651-general/topic/void.20as.20usize

This works for simple enums with one or more variants, but for zero it fails, possibly due to interaction with #[repr(C)]. This kind of code can come up in macro code.

cc @RalfJung

@RalfJung
Copy link
Member

I think for zero-variant enums it fails mostly as an oversight. This doesn't even have to generate any MIR as it is unreachable code anyway.

Nominating for lang team discussion.

@RalfJung RalfJung added I-nominated T-lang Relevant to the language team, which will review and decide on the PR/issue. labels Aug 18, 2020
@digama0
Copy link
Contributor Author

digama0 commented Aug 18, 2020

From the head comment at

//! * `e` is a C-like enum and `U` is an integer type; *enum-cast*
, I infer that this is indeed only designed to work with C-style enums. My proposal would be to add another cast category saying that empty enums can as cast to anything, desugaring to match x {}.

@RalfJung
Copy link
Member

No, enum casts have nothing to do with C-style enums. This works:

pub enum MirPhase {
    Build = 0,
    Const = 1,
    Validated = 2,
}

fn main() {
    let _val = MirPhase::Build as usize;
}

@RalfJung
Copy link
Member

Oh, the comment says "C-like". That's not the same as repr(C).
"C-like" just means that all enum variants have no data. This is satisfied by Void -- there are no enum variants, and thus all of them have no data.

@digama0
Copy link
Contributor Author

digama0 commented Aug 18, 2020

Here's the offending check:

pub fn is_payloadfree(&self) -> bool {
!self.variants.is_empty() && self.variants.iter().all(|v| v.fields.is_empty())
}

It seems that the "payload-free" check is not just "all variants have no data" but also "it is not empty". I would hesitate to change this definition because it's likely to affect C-like enum handling elsewhere, but maybe CastTy can get another class specifically for uninhabited enums.

@RalfJung
Copy link
Member

I'd argue a no-variant enum is payload-free. But all uses of that function should be carefully checked.

Adding a new case to CastTy seems like a hack to me.

@scottmcm
Copy link
Member

These were historically known as "C-like" enums, and Void is not C-like. From GCC:

<source>:1:12: error: expected unqualified-id at end of input

    1 | enum Void {}

      |            ^

Compiler returned: 1

This kind of code can come up in macro code.

Can you maybe elaborate more on this?

@RalfJung
Copy link
Member

Sure, C might not permit them, but I don't see how that's a reason to disallow as casts in Rust.

@tesuji
Copy link
Contributor

tesuji commented Aug 25, 2020

I mean, what value does Void as usize have that will make sense?

@Aaron1011
Copy link
Member

It's not actually reachable, so it could just typecheck without actually ever needing to produce a value.

@digama0
Copy link
Contributor Author

digama0 commented Aug 25, 2020

@lzutao It can compile the same way as match x {}, which is to say, produce no code and mark the remainder of the function as unreachable. Compiling to 0 would also work for all numeric types that this could target, although then x appears unused which might cause other unintended behaviors in the compiler.

@tesuji
Copy link
Contributor

tesuji commented Aug 25, 2020

Currently, the compiler marks match x {} code as error. What do you mean ?

It can compile the same way as match x {}, which is to say, produce no code and mark the remainder of the function as unreachable

Unreachable is unsafe, does it mean that Void as usize is unsafe ?

@digama0
Copy link
Contributor Author

digama0 commented Aug 25, 2020

@lzutao This uses no unsafe code:

enum Void { }
fn void_as_usize(x: Void) -> usize {
    match x {}
}

Here the code after the match statement (if there was any) would be unreachable, and this requires no unsafe code. Basically x is an impossible value, therefore the function cannot be called in the first place and no code need be generated.

EDIT: Your linked example is pattern matching on an i32 with no branches. Here x is an empty enum, Void, so an empty match of an empty enum is exhaustive without any unsafe.

@tesuji
Copy link
Contributor

tesuji commented Aug 25, 2020

Thanks, I got that part now. But the question raised by Scott still remain: What is the concrete example of macros that have use cases for Void as usize ?

Also, Void is a never type, if Void is usize allowed, would we want to make any operations on Void unreachable?

@digama0
Copy link
Contributor Author

digama0 commented Aug 25, 2020

The actual macro where this came up was a safe wrapper that declares an int-like enum and an array indexed by the enum. Roughly, it creates a type enum Foo { User, Defined } and struct FooVec<T>([T; Foo::LEN]), and then the Index<Foo> implementation for FooVec uses vec.0[i as usize] where i: Foo. When Foo has zero elements, this causes the macro to produce malformed code.

It is possible to work around this issue by explicitly checking for this case, but I don't really see a reason for it not to work in the first place.

would we want to make any operations on Void unreachable?

Any operation on Void is unreachable. That means that the compiler can do whatever it wants to for codegen here because it doesn't matter, the function is not callable. But from a typing point of view, I think that (x: Foo) as usize should have type usize, not !.

@RalfJung
Copy link
Member

It's no surprise that macros run into this -- this happens any time that we have special exceptions. "enums without fields can be cast to integers, except if the enum has 0 variants" is such a special exception, and from a language design perspective it has no reason to exist.

Not sure what the next step here is -- probably an RFC? This is a language extension after all.

@nikomatsakis
Copy link
Contributor

Discussed in the @rust-lang/lang meeting today:

  • We agreed that this seems like a harmless extension (specifically: permitting zero variant enums to be as to a usize).
  • We don't feel an RFC is required and we would recommend somebody creating a PR that fixes this and nominates the PR for lang-team consideration, we can do an FCP directly on the PR.

@Mark-Simulacrum
Copy link
Member

I have opened #76199 to fix this issue.

Dylan-DPC-zz pushed a commit to Dylan-DPC-zz/rust that referenced this issue Oct 17, 2020
…tsakis

Permit uninhabited enums to cast into ints

This essentially reverts part of rust-lang#6204; it is unclear why that [commit](rust-lang@c0f587d) was introduced, and I suspect no one remembers.

The changed code was only called from casting checks and appears to not affect any callers of that code (other than permitting this one case).

Fixes rust-lang#75647.
@bors bors closed this as completed in 496e2fe Oct 17, 2020
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
T-lang Relevant to the language team, which will review and decide on the PR/issue.
Projects
None yet
Development

Successfully merging a pull request may close this issue.

7 participants