Rust's Option type guarantees that the size of Option<NonNull<T>>
is the size of a pointer, because 0 can be used to represent the None
variant.
How can I achieve a similar effect for a Rust enum with an integer-repr
which has no zero-valued variant?
My motivation is to write a Rust binding for a C enum defined in a third-party C library which specifies values for all of its enumerators, none of which are 0. And yet, the value of a variable with such an enum type may legitimately be 0 in C code, because C's enum
is pretty much an int
under the hood.
#include<stdio.h>
typedef enum Positive {
One = 1,
Two = 2
} Positive;
int main() {
Positive pos = 0;
printf("A \"Positive\" value: %d\n", pos);
}
I would like to represent this C enum using a Rust enum — an application of the "newtype" design pattern, which adds semantics to an existing type, generally while preserving the underlying representation. In this case, the additional semantic constraint is that the Rust enum type can only take on the values of its variants, rather than any C int
value.
However, we must also accommodate 0 values, as they are used legitimately in C code — for example, if a struct field has this type, its initial value may be set to 0.
(Bogus values which neither correspond to struct variants nor 0 can be handled using careful validation and the num_enum
crate.)
It is important that this type have the same memory representation in C and Rust, so that for example if it is used as struct field it will have the right size.
I would like to solve this problem by representing values of this enum type which might be 0 using Option<T>
. Here's code that I wish worked (unfortunately it is unsound because Option<T>
isn't guaranteed to have an FFI-safe represntation in this scenario):
#[derive(Debug, PartialEq, Eq)]
#[repr(u32)]
enum Positive {
One = 1,
Two = 2,
}
// An attempt to implement the following C function in unsafe Rust:
// `Positive foo(int value) { return (Positive)value; }`
unsafe extern "C" fn foo(value: u32) -> Option<Positive> {
std::mem::transmute(value)
}
fn main() {
let some_one = unsafe { foo(1) };
assert_eq!(some_one, Some(Positive::One));
let some_two = unsafe { foo(2) };
assert_eq!(some_two, Some(Positive::Two));
eprintln!("`Some` variants succeed");
let none = unsafe { foo(0) };
assert_eq!(none, None);
eprintln!("`None` variant succeeds");
}
My current workaround is to add a zero-valued variant to the Rust version of the enum, but I'm unhappy with that because it intrudes on processing logic in Rust code such as exhaustive matching.
I also tried defining my own type Opt<T, ReprT>
which requires a pile of unsafe code I haven't yet figured out how to prove sound at compile time — but even if I solve that problem, the resulting API is going to be verbose and unsatisfactory.
Is there a better way?