-
Notifications
You must be signed in to change notification settings - Fork 3.3k
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
Rework enums in rust. #6098
Rework enums in rust. #6098
Conversation
They're now a unit struct, rather than an enum. This is a backwards incompatible change but the previous version had UB and was also backwards incompatible so...
@jean-airoldie @vglavnyy @rw @aardappel This is the implementation of #5581 (comment) PTAL |
I have no idea what effect this has on usage in Rust, but them being explicitly sized seems nice. |
Oh, one other backwards-incompatible thing I forgot to add is proper support for bitflags |
What's the motivation for this change? |
Ok, but the idiomatic thing is to use a Result. By adding a magic "unknown value" enum variant, you not only introduce a custom Result implementation (which this is, since everyone would need to account for that), but also the awful C-style magic constants approach. I would vote for placing invalid values as an error variant of a Result. |
I'm not adding a magic "unknown value" variant. I'm using a C like representation. struct MyEnum(u16);
impl MyEnum {
// Variants are expressed as associated constants.
pub const Variant1: MyEnum = MyEnum(1);
pub const Variant2: MyEnum = MyEnum(2);
} The result based representation goes against Flatbuffer's zero-parsing / be-very-fast philosophy because Another minor win, this way, is that we'd write match my_enum {
MyEnum::Variant1 => ...,
MyEnum::Variant2 => ...,
MyEnum(x) => ...,
} which I think looks better than match my_enum {
Ok(MyEnum::Variant1) => ...,
Ok(MyEnum::Variant2) => ...,
Err(x) => ...,
} |
While I agree using a Result will copy some data, I nevertheless think in 2020 we can live with the burden of copying 16bits, even on embedded devices, if we get proper idiomatic API in return. Not to mention your example does the exact same thing by putting values in your custom struct, just like you would do when putting them in a Result. This is a typical case of a fallible conversion which in Rust is handled by
We can see that the proposed implementation simply reinvents the Result type. In fact, if this is merged every enum in FB will become a custom, independent version of a Result, with What should be done to solve the compatibility problem is to use idiomatic fallible conversion mechanism already provided by Rust, rather than reinvent the wheel. Simply implement |
@rw opinion? |
I think you've misunderstood. In my example
I think the
|
I don't think you should model Rust api based on C idioms, since Rust has its own idiomatic ways of doing things, which can be argued, are better in this case. The compatibility case you describe isn't a problem in proper Rust for a few reasons.
As you can see, Result already handles problematic cases and has the added bonus of already being the standard way of doing those things. In short - let's not reinvent the wheel, especially if the wheel is based on C and looks like a square. |
Ok, that's getting a little rude. Regardless, I think our points have been made. If we prioritize idiomatic-ness over performance and also think enums should be exhaustive (thus code breaking is a good thing), we can do things the Care to weigh in? @rw @aardappel |
I've yet to write a single line of Rust, so I'm going to leave that to @rw :) |
@jean-airoldie might also have an opinion |
Previously, the bitflags attribute was just ignored. This is a breaking change as the bitflgs API is not like a normal rust enum (duh).
At this point I'm wondering if we should simply wrap accessors in a Result/Option rather than depending on (non-existent yet) validator. @rw @ImmemorConsultrixContrarie opinions? |
No, we can't. Getting things from table or struct should be infallible. However, reading tables or structs from bytes could be fallible. But that's not a PR about rewriting |
That's true in a general sense, but in FB, a struct and a vector of bytes is the same thing, unless we add an intermediate parsing step. To have a good Rust API which upholds the contract of being a safe abstraction over an unsafe code, we either need a validator or a Result everywhere we can fail. Right now we have a situation where we panic or cause unsoundness. Of course this PR is not about rewriting the whole Rust generator, but it exhibits a part of the problem. |
I think, bitflags part should be deferred for later, because right now Without bitflags part this PR would only close soundness holes, not dig a new ones. And once it is agreed on what Edit: @CasperN |
Ok, I'm not a Rust programmer (yet), but I just had a brainfart for another possible solution: The problem is that Rust enums's semantics rely on them being fixed to an exact number of elements (which is a great feature for the language itself), but which is fundamentally incompatible with extensible nature of serialization. Most Rust enums (and other ADTs like in Haskell etc) that must represent non-presence usually bake a For a schema enum that has Advantages:
Disadvantages:
If I would re-design FlatBuffers from scratch with Rust in mind, I would require at the schema level that every enum has a |
The proposed |
Not solving a problem, because enum can't be stored as enum, unless you close all the gaps, eventually making enum a The problem here is that Rust enums can't be stored in structs or slices as enums due to endianness and inexhaustiveness of flatbuffers' enums, because in Rust having enum with invalid value is UB; making enum from integer is not a problem. |
Anyway, let's try to get back on topic. The new discussion is whether we should go with a combined approach, where the native Rust enum is used in the object API. It seems @aardappel and @ImmemorConsultrixContrarie and I agree that the normal enum accessor should be a closer model to the flatbuffers enum.
@krojew we'll add it to the tutorial if that helps
@rw how does this sound? |
@CasperN SGTM, thanks! |
I think this PR is almost ready to go in. Given its a fairly signifiant change, there's remarkably little code changed in Can I get another round of comments? |
In another business day, I'll assume tacit approval and will merge. |
They're now a unit struct, rather than an enum. This is a backwards incompatible change but the previous version had UB and was also backwards incompatible so while I was at it; I brought some constants and functions into the type's own namespace, which is also backwards incompatible.
ENUM_MIN_FOO => Foo::ENUM_MIN
ENUM_MAX_FOO => Foo::ENUM_MAX
enum_name_foo => Foo::variant_name
I deleted the jump-table-to-name-str thing since Rust seems to compile
match
into jump tables in the common case of sequential enum values (as far as I can tell).