4 ms·
Sadly, it's not generalized enough to represent C-like enums in Rust, which can be declared as (in _theory_): enum Foo { A = 0, B = 1, C = 2,
by drran 6y ago
Sadly, it's not generalized enough to represent C-like enums in Rust, which can be declared as (in _theory_):
enum Foo {
A = 0,
B = 1,
C = 2,
Unknown(i32),
}
where Unknown field cannot contain values 0, 1, or 2, thus Rust compiler will be able to pack this enum into single i32, like in C.
- steveklabnik 6y agoYes, if you look at the actual PR for this change, you'll see that doing this is using a rustc-specific interface, for similar reasons.
- azornathogron 6y agoFor that use-case can you get away with just setting #[repr(i32)] on the enum and leaving Unknown out of the list? I guess the compiler can't help you keep known and unknown values separate with that structure though, so maybe it's not enough.
- kzrdude 6y agoIf you do that, then 3 etc is not a valid value for the enum. Only valid values for that type are the variants.
- johnsoft 6y agoIt's not really an enum if you can't enumerate all the values at compile time. Rust enums are not C enums. I'd represent it like this: #[repr(transparent)] struct Foo(i32); impl Foo { const A: Foo = Foo(0); const B: Foo = Foo(1); const C: Foo = Foo(2); }
- drran 6y ago1) Enums in C are implemented that way, so C-like enums in Rust must implement it in the same way to be compatible with C. 2) This is required for forward compatibility. For example protobuf explicitly requires it. 3) Your code is not forward compatible. The proper way is to implement it is as i32 and then unwrap it, or: enum Foo { A = 0, B = 1, C = 2, _UNKNOWN_3 = 3, _UNKNOWN_4 = 4, _UNKNOWN_5 = 5, _UNKNOWN_6 = 6, // ... repeat few dozen times, to be forward compatible } Example: https://github.com/apoelstra/rust-bitcoin/blob/c37ab1f9c2392125a492115c1d2c7a8a77cfff36/src/blockdata/opcodes.rs#L415 https://github.com/apoelstra/rust-bitcoin/blob/c37ab1f9c2392... P.S. IMHO, Rust should allow to define enums as: enum Foo { A = 0, B = 1, C = 2, _ , // Unknown values which must be handled by default case } or enum Foo { A = 0, B = 1, C = 2, _OTHER , // Unknown values which must be handled } because current workaround is usable for i8, maybe even for i16, but not for i32.
- pornel 6y agoRust enums are not compatible with C, even with `#[repr(C)]`. Values not explicitly present in the enum are not allowed. Casting them to a Rust enum is UB, and it does actually cause miscompilation, because `match` can be a jump table.
- andrewaylett 6y agoRust has an annotation to mark that an enum is non-exhaustive[1], and a mechanism for declaring an enum as being laid out in a manner compatible with C[2]. I've not tried using them together :). In general, it _is_ possible for any C data layout to be represented in Rust -- but it's not necessarily the case that the representation has the same name. And it's also not the case that we can safely pass memory from C to Rust without validating the content, even if the representation is equivalent for all valid values. [1] https://blog.rust-lang.org/2019/12/19/Rust-1.40.0.html#non_exhaustive-structs-enums-and-variants https://blog.rust-lang.org/2019/12/19/Rust-1.40.0.html#non_e... [2] https://rust-lang.github.io/unsafe-code-guidelines/layout/enums.html#repr-annotations-accepted-on-enums https://rust-lang.github.io/unsafe-code-guidelines/layout/en...
- drran 6y agoTo my surprise, it works partially on latest stable Rust: #[non_exhaustive] #[repr(C)] #[derive(Debug)] #[allow(dead_code)] enum Foo { A = 0, B = 1, C = 2, } pub fn main() { let bar = unsafe { std::mem::transmute::<i32, Foo>(33) }; println!("{:?}", bar); let s = match bar { Foo::A => ("A", bar as i32), Foo::B => ("B", bar as i32), Foo::C => ("C", bar as i32), _ => ("Unknown", bar as i32), }; println!("{:?}", s); } Standard Output C ("Unknown", 33)
- lilyball 6y agoIn what way is johnsoft’s code not forwards-compatible? > Your code is not forward compatible. The proper way is to implement it is as i32 and then unwrap it That’s what they’re doing. It’s a newtype for i32 with a few named values but it can represent any i32, and you can unwrap it with .0 to get the i32 value.