Skip to content

Add enum option constified_enum + rustified_enum with conversions #2646

Open

Description

All ways of interop with C enums unfortunately have some downsides. constified-enum, constified-enum-module, newtype-enum, and bitfield-enum can't be matched exhaustively: using the below demo, for example:

match some_foo {
    foo_one => println!("1"),
    foo_too => println!("2"),
    foo_three => println!("3"),
    _ => unimplemented!()
}

If a new variant is added to the enum, it gets swallowed with the _. Or a variant may have accidentally be emitted in the first place.

rustified_enum and rustified-non-exhaustive-enum provide more ergonomic solutions and are easier to match, but they don't have good handling for if C provides value not covered by a variant - which is allowed in C. This leads to bugs that can be impossible to track down.

Proposal: allow creating both a constified enum and a Rust enum, and autogenerate three conversion methods between them:

  • Safe panicking with Into
  • Safe but with an error with TryInto
  • Unsafe, assume you never get an unnamed value

Input C/C++ Header

enum foo {
  one = 1,
  two = 2,
  three = 3
};

Bindgen Invocation

bindgen test.h
bindgen test.h --rustified-enum '.*'

Actual Results

pub const foo_one: foo = 1;
pub const foo_two: foo = 2;
pub const foo_three: foo = 3;
pub type foo = ::std::os::raw::c_uint;
#[repr(u32)]
#[derive(Debug, Copy, Clone, Hash, PartialEq, Eq)]
pub enum foo {
    one = 1,
    two = 2,
    three = 3,
}

Expected Results

Something like this:

pub const foo_one: foo = 1;
pub const foo_two: foo = 2;
pub const foo_three: foo = 3;
pub type foo_ctype = ::std::os::raw::c_uint;

#[repr(u32)]
#[derive(Debug, Copy, Clone, Hash, PartialEq, Eq)]
pub enum foo {
    one = 1,
    two = 2,
    three = 3,
}

impl From<foo_ctype> for foo {
    fn from(value: foo_ctype) -> foo {
        match value{
            1 => foo::one,
            2 => foo::two,
            3 => foo::three,
            _ => panic!("unrecognized option for `foo` {foo_ctype}"),
        }
    }
}

struct FooError(foo_ctype);

impl TryFrom<foo_ctype> for foo {
    type Error = FooError;
    fn try_from(value: foo_ctype) -> Result<foo, FooError> {
        match value{
            1 => Ok(foo::one),
            2 => Ok(foo::two),
            3 => Ok(foo::three),
            _ => Err(FooError(value)),
        }
    }
}

impl foo {
    const unsafe fn from_ctype_unchecked(value: foo_ctype) -> Self {
        std::mem::transmute(value)
    }
}

All bindings would use foo_ctype as the value type, but this would give an easy way to turn it into something exhaustive

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Assignees

No one assigned

    Labels

    rust-for-linuxIssues relevant to the Rust for Linux project

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions