Как преобразовать перечисление в стиле C, созданное bindgen, в другое перечисление?

Я создаю привязки в Rust для библиотеки C и перечислений, сгенерированных Bindgen, например:

// 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,
}

Мне нужно преобразовать их в:

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

При импорте постоянных значений из этой библиотеки C:

// C library
const rmw_qos_history_policy_t some_value_from_C = RMW_QOS_POLICY_HISTORY_SYSTEM_DEFAULT;

Я бы хотел сделать что-то вроде:

let some_value: QoSHistoryPolicy = some_value_from_C;

Как я могу это сделать?

2 ответа

Решение

Компилятор не проверяет перечисления на совместимость с ABI и, как таковой, не предоставляет прямого способа преобразования значений между этими типами. Далее следуют несколько возможных решений.

1. Поочередное соответствие

Это тривиально и безопасно, хотя и приводит к исчерпывающему коду.

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. Кастинг + FromPrimitive

Rust позволяет преобразовывать перечисления без полей в целочисленный тип с помощью asоператор. Однако обратное преобразование не всегда безопасно. ПроизводныйFromPrimitive используя num ящик, чтобы получить недостающий кусок.

#[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. Нужен enum?

В случае, если вам просто нужна абстракция для низкоуровневых привязок, вы можете обойтись без нового типа перечисления.

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

Приведенный выше тип содержит ту же информацию и двоичное представление, но может предоставлять инкапсулированный API. Преобразование из низкоуровневого типа в высокоуровневый становится тривиальным. Основным недостатком является то, что вы теряете сопоставление с образцом по его вариантам.

4. Вы сами по себе

Когда вы абсолютно уверены, что два перечисления эквивалентны в своем двоичном представлении, вы можетеtransmuteмежду ними. Компилятор здесь не поможет, это далеко не рекомендуется.

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

Смотрите также:

Похоже, это хороший кандидат на From черта наQoSHistoryPolicy.

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

так что теперь это должно работать

let some_value: QoSHistoryPolicy = some_value_from_C.into();
Другие вопросы по тегам