Безопасно ли mem::transmute между перечислением и представлением тегированного объединения?

Официальная документация 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) }
}

Детская площадка

Почему Python в конце концов умрет
Почему Python в конце концов умрет
Последние 20 лет были действительно хорошими для Python. Он прошел путь от "просто языка сценариев" до основного языка, используемого для написания...
1
0
104
2
Перейти к ответу Данный вопрос помечен как решенный

Ответы 2

Согласно 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 и как имитировать его с помощью отдельного тега и объединения.

Поскольку вы трансмутируете ссылки, вам необходимо убедиться, что выравнивание и структура совпадают, но если следовать приведенным выше рекомендациям, все должно быть таким же. Дополнительную информацию см. в Номиконе.

Другие вопросы по теме