enum Foo {
OptionA,
OptionB,
OptionC,
}
enum Bar {
OptionA = 1,
OptionB = 2,
OptionC = 4,
}I didn't know this. How is determined? Is there any code inserted by the compiler to do this?
What's the overhead of using Result<T, E>?
> What's the overhead of using Result<T, E>?
Enough bits (+ padding) to discriminate between all the variants, as any tagged union would, modulo some tricks for niche optimization. For example, Result<i64, u64> is 16 bytes in memory because it needs 8 bytes to represent either a i64 or an u64, plus another 8 bytes to determine whether it is an Ok or an Err. You can think of it as a
struct Enum {
variant: usize,
bytes: [u8; N],
}
The niche optimization I mentioned applies for things like Option<NonZeroI64>, where NonZeroI64 fits in 8 bytes but the compiler can ensure that the bitpattern corresponding to the 0 value will never be used, so it can use that to differentiate between Some and None.https://play.rust-lang.org/?version=stable&mode=debug&editio...
Regarding code: because of the "to determine at runtime" of your parent comment, I thought there could be some runtime checks (extra code) for handling enums.
I don't think I have come across any conversation by embedded developers foregoing enums because of that, but anecdotally that space does seem to rely on type state machines a bit more than others, which have no runtime memory cost. A bigger cost consideration is if you have an enum with variants that have wildly different sizes, making the enum big for all cases, whether it is necessary or not. We have a lint to encourage you to Box wildly diverging variants, but that might not be possible if you don't have an allocator.
> Regarding code: because of the "to determine at runtime" of your parent comment, I thought there could be some runtime checks (extra code) for handling enums.
Yeah, I figured that's what happened and I should be more careful with my wording.
Under the hood, of course, they are implemented with C-style unions with fields sharing memory. But to conflate Rust's enums with how they are implemented is to disregard the extra safety that they provide.
enum Result {
Ok,
Err,
}
If this was all you got, you'd need to return something like `(status, value, err)` to model fallible functions. This is not unlike Go's `value, err` convention, except that `status` is additionally inlined into `value` as a sentinel `nil`. This does nothing to prevent you from using the `value` if an error has occurred, or vice versa.Instead, we like to carry data _alongside_ the enum tags:
// using named fields instead of tuple structs, for clarity
enum Result<T, E> {
Ok {value: T},
Err {err: E},
}
Now we can return a single `Result,` and you can only use `value` if the function succeeded, and you can only use `err` if the function failed.