5 ms·
Incompatibility with C enums in repr(C) enums is still not fixed. In C, enums are more like: #[repr(C)] enum Foo { BAR=0, BAZ=1, Unknown(i32),
by v_lisivka 8y ago
Incompatibility with C enums in repr(C) enums is still not fixed.
In C, enums are more like:
#[repr(C)]
enum Foo {
BAR=0,
BAZ=1,
Unknown(i32), // other possible values of int
};
but Rust doesn't allow to write enums like that, so it leads to bugs in FFI.
- kibwen 8y agoIt's true that C allows manually setting an enum to an integral value other than those explicitly specified, but no project that I've ever seen regards such an action as anything other than a bug. Can you give an example of code that's using such a thing, especially for FFI?
- olliej 8y agoThe Mac and iOS design uses explicitly valued enums in all APIs (aside from anything else it makes it much harder to accidentally break ABI). JSC also uses them extensively in the lexer and parser.
- kibwen 8y agoRust can do explicit values for C-style enums; what it currently doesn't support is the ability to set such an enum to an arbitrary (non-declared) value as C does.
- v_lisivka 8y agoProtobuf specification explicitly states that protobuf compatible implementation must support values outside of the enum range, to be forward compatible with future updates to .proto files.
- bonzini 8y agoYou could define flags as an enum with values 1, 2, 4, 8, ... and OR them together.
- auscompgeek 8y agoSuch a scenario is more appropriately represented with the bitflags crate.
- bonzini 8y agoSure, he's talking about C and FFI though. You can use bitflags but you still need to convert at language boundaries.
- gpm 8y ago#[repr(C)] #[derive(PartialEq, Eq, Clone, Copy)] struct Foo(i32); const FOO_BAR: Foo = Foo(0); const FOO_BAZ: Foo = Foo(1); I remember writing a macro to handle this properly, even using modules for namespacing, ages ago...
- huntie 8y agoYou can also use associated consts which feels nicer in my opinion: #[repr(C)] #[derive(PartialEq, Eq, Clone, Copy)] struct Foo(i32); impl Foo { const BAR: Foo = Foo(0); const BAZ: Foo = Foo(1); }
- v_lisivka 8y agoIt doesn't play nicely with tooling like debugging output(#[derive(Debug)]), introspection, serialization, match statements, etc. My solution to this problem, for i8/u8 enum's only, is just to list of unused values as enum Foo { ..., RESERVED_2=2, RESERVED_3=3,.. RESERVED_255=255,}.
- gpm 8y agoThese days it would be perfectly possible to have a macro that looked like cenum! { Foo: i32, Bar = 1, Baz = 2 } That expanded to the above `struct Foo(i32)` code and implemented Debug printing `Foo::Bar`, `Foo::Baz`, and `Foo::Unknown(3)` or something.
- 8y ago
- Twisol 8y agoIs there an issue posted somewhere on this? From a glance, I would imagine you could mediate across the FFI using an `i32`, and provide an `impl From<i32> for Foo` that neatly separates the discriminated and excess cases.
- v_lisivka 8y agoYes, I can use numbers to represent anything, but I will lost compiler support. It's not a solution but workaround. I saw number of tickets about that in Rust ticketing system. This one seems to be most relevant: https://github.com/nikomatsakis/rust-memory-model/issues/41 https://github.com/nikomatsakis/rust-memory-model/issues/41 Then these ones: https://github.com/rust-lang/rust/issues/36927 https://github.com/rust-lang/rust/issues/36927 https://internals.rust-lang.org/t/c-style-enums-in-ffi-and-a-proposal-to-lump-in-with-unions/2744 https://internals.rust-lang.org/t/c-style-enums-in-ffi-and-a... https://internals.rust-lang.org/t/pre-rfc-enum-from-integer/6348 https://internals.rust-lang.org/t/pre-rfc-enum-from-integer/...
- Twisol 8y agoFor the first one, which I see you've made a comment on: > This behaviour affects Prost: i32 type is used instead of enum type, because Protobuf defines that enum variable must be able to hold values outside of enum range to be compatible with future versions. As alternative solution, it's proposed to use _Unknonwn_(i32) variant, but this solution cannot be implemented in current version of Rust, because C-like enums cannot contain tags. In principle, a protobufs library (like Prost) could map a protobuf enum `X` into a Rust enum `ProtobufEnum<X>` with `Known(X)` and `Unknown(i32)` fields. Or autogenerate a Rust enum with the known variants inlined, plus the extra Unknown field. It is incumbent on the library author and/or FFI user to map semantic notions appropriately across the interface. Rust enums do not have C semantics, even if the underlying machine representation is annotated to mimic C. If something across the interface allows its flavor of enum to take on unnamed values, then that semantics needs to be preserved faithfully in Rust -- and that may mean using something slightly different from what Rust itself natively provides.
- 8y ago
- pornel 8y agoIt's not fair to call it "still not fixed". That behavior is entirely intentional and working as designed (even if it's not useful for all C enums), and no changes to it were planned for this edition.
- v_lisivka 8y agoBehavior of #[repr(C)] enum doesn't match behavior of the C enum, so it's bug for #[repr(C)] enum's.
- int_19h 8y agoIn vast majority of cases, C enums are used in the same way as Rust enums - i.e. only the named enum variants are supported by any code that touches enum values (and if you're lucky, it might check for other stuff and reject it explicitly, but more often people forget and things just don't work or silently misbehave). So in practice this behavior is correct. For those very rare cases where someone used open-ended enums in their API, ascribing some meaning to unnamed values that must be exposed, you can always use i32 etc.