enumsrustrust-bindgen

How do I convert a C-style enum generated by bindgen to another enum?


I am creating bindings in Rust for a C library and Bindgen generated enums like:

// Rust
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
pub enum rmw_qos_history_policy_t {
    RMW_QOS_POLICY_HISTORY_SYSTEM_DEFAULT = 0,
    RMW_QOS_POLICY_HISTORY_KEEP_LAST = 1,
    RMW_QOS_POLICY_HISTORY_KEEP_ALL = 2,
    RMW_QOS_POLICY_HISTORY_UNKNOWN = 3,
}

I need to convert these to:

// Rust
pub enum QoSHistoryPolicy {
    SystemDefault = 0,
    KeepLast = 1,
    KeepAll = 2,
    Unknown = 3,
}

When importing constant values from this C library:

// C library
const rmw_qos_history_policy_t some_value_from_C = RMW_QOS_POLICY_HISTORY_SYSTEM_DEFAULT;

I would like to do something like:

let some_value: QoSHistoryPolicy = some_value_from_C;

How can I go about it?


Solution

  • The compiler does not inspect enums for ABI compatibility, and as such does not provide a direct way to convert values between these types. A few possible solutions follow.

    1. One-by-one matching

    This is trivial and safe, albeit leading to exhaustive code.

    impl From<rmw_qos_history_policy_t> for QoSHistoryPolicy {
        fn from(x: rmw_qos_history_policy_t) -> Self {
            use rmw_qos_history_policy_t::*;
            match x {
                RMW_QOS_POLICY_HISTORY_SYSTEM_DEFAULT => QoSHistoryPolicy::SystemDefault,
                RMW_QOS_POLICY_HISTORY_KEEP_LAST => QoSHistoryPolicy::KeepLast,
                RMW_QOS_POLICY_HISTORY_KEEP_ALL => QoSHistoryPolicy::KeepAll,
                RMW_QOS_POLICY_HISTORY_UNKNOWN => QoSHistoryPolicy::Unknown,
            }
        }
    }
    

    2. Casting + FromPrimitive

    Rust allows you to convert field-less enums into an integer type using the as operator. The opposite conversion is not always safe however. Derive FromPrimitive using the num crate to obtain the missing piece.

    #[derive(FromPrimitive)]
    pub enum QoSHistoryPolicy { ... }
    
    impl From<rmw_qos_history_policy_t> for QoSHistoryPolicy {
        fn from(x: rmw_qos_history_policy_t) -> Self {
            FromPrimitive::from_u32(x as _).expect("1:1 enum variant matching, all good")
        }
    }
    

    3. Need an enum?

    In the event that you just want an abstraction to low-level bindings, you might go without a new enum type.

    #[repr(transparent)]
    pub struct QoSHistoryPolicy(rmw_qos_history_policy_t);
    

    The type above contains the same information and binary representation, but can expose an encapsulated API. The conversion from the low-level type to the high-level type becomes trivial. The main downside is that you lose pattern matching over its variants.

    4. You're on your own

    When absolutely sure that the two enums are equivalent in their binary representation, you can transmute between them. The compiler won't help you here, this is far from recommended.

    unsafe {
        let policy: QoSHistoryPolicy = std::mem::transmute(val);
    }
    

    See also: