Официальная документация Rust гарантирует размещение перечисления repr(C, u8)
(с полями) с точки зрения эквивалентного набора типов, реализующих объединение, помеченное вручную. Если я определяю обе реализации, имею ли я право выполнять взаимное преобразование между ними с помощью mem::transmute
?
Мотивация заключается в том, что в большей части моего кода я хочу использовать красивое эргономичное перечисление ржавчины, но в одном или двух местах мне нужно выполнить небезопасные операции над представлением тегированного объединения.
/// BEGIN copy-paste from
/// https://doc.rust-lang.org/reference/type-layout.html#combining-primitive-representations-of-enums-with-fields-and-reprc
// This Enum has the same representation as ...
#[repr(C, u8)]
enum MyEnum {
A(u32),
B(f32, u64),
C { x: u32, y: u8 },
D,
}
// ... this struct.
#[repr(C)]
struct MyEnumRepr {
tag: MyEnumDiscriminant,
payload: MyEnumFields,
}
// This is the discriminant enum.
#[repr(u8)]
enum MyEnumDiscriminant { A, B, C, D }
// This is the variant union.
#[repr(C)]
union MyEnumFields {
A: MyAFields,
B: MyBFields,
C: MyCFields,
D: MyDFields,
}
#[repr(C)]
#[derive(Copy, Clone)]
struct MyAFields(u32);
#[repr(C)]
#[derive(Copy, Clone)]
struct MyBFields(f32, u64);
#[repr(C)]
#[derive(Copy, Clone)]
struct MyCFields { x: u32, y: u8 }
// This struct could be omitted (it is a zero-sized type), and it must be in
// C/C++ headers.
#[repr(C)]
#[derive(Copy, Clone)]
struct MyDFields;
/// END copy-paste
fn as_tagged_union(x: &mut MyEnum) -> &mut MyEnumRepr {
unsafe { core::mem::transmute(x) }
}
fn as_enum(x: &mut MyEnumRepr) -> &mut MyEnum {
unsafe { core::mem::transmute(x) }
}
Согласно RFC, который это реализует, да, это определено:
Для обоих макетов программам Rust определено приведение/переинтерпретация/преобразование такого перечисления в эквивалентное определение Repr. Также определено отдельное манипулирование тегом и полезной нагрузкой. Тег и полезные данные должны находиться в согласованном/инициализированном состоянии только тогда, когда значение соответствует (что включает его удаление).
Например, этот код действителен (используя те же определения, что и выше):/// Tries to parse a `#[repr(C, u8)] MyEnum` from a custom binary format, overwriting `dest`. /// On Err, `dest` may be partially overwritten (but will be in a memory-safe state) fn parse_my_enum_from<'a>(dest: &'a mut MyEnum, input: &mut &[u8]) -> Result<(), &'static str> { unsafe { // Convert to raw repr let dest: &'a mut MyEnumRepr = mem::transmute(dest); // If MyEnum was non-trivial, we might match on the tag and // drop_in_place the payload here to start. // Read the tag let tag = input.get(0).ok_or("Couldn't Read Tag")?; dest.tag = match tag { 0 => MyEnumTag::A, 1 => MyEnumTag::B, 2 => MyEnumTag::C, 3 => MyEnumTag::D, _ => { return Err("Invalid Tag Value"); } }; *input = &input[1..]; // Note: it would be very bad if we panicked past this point, or if // the following methods didn't initialize the payload on Err! // Read the payload match dest.tag { MyEnumTag::A => parse_my_enum_a_from(&mut dest.payload.A, input), MyEnumTag::B => parse_my_enum_b_from(&mut dest.payload.B, input), MyEnumTag::C => parse_my_enum_c_from(&mut dest.payload.C, input), MyEnumTag::D => { Ok(()) /* do nothing */ } } } }
Да, вы можете std::mem::transmute
между типами, имеющими одинаковую компоновку и представление. Пример из Rust Reference показывает, как структурирован #[repr(C)]
enum
и как имитировать его с помощью отдельного тега и объединения.
Поскольку вы трансмутируете ссылки, вам необходимо убедиться, что выравнивание и структура совпадают, но если следовать приведенным выше рекомендациям, все должно быть таким же. Дополнительную информацию см. в Номиконе.