Skip to content

Implement Support For repr(int) non-C-like enums #78

Closed
@Gankra

Description

@Gankra

It turns out repr(u32, i8, etc...)on non-C-like enums has had an unofficially specified and C(++)-compatible layout for a good while. I have filed an RFC to make it officially specified, but in the mean time we can set up cbindgen to generate this layout, which will let us use native Rust tagged unions in our bindings, and with today's stable Rust!

You can check out the RFC for details (it's not quite the obvious layout), but here's a test Rust program to prove that it works:

works_for_me.rs
use std::time::Duration;
use std::mem;

#[repr(u8)]
#[derive(Copy, Clone, Eq, PartialEq, Debug)]
enum MyEnum {
    A(u32),
    B { x: u8, y: i16 },
    C,
    D(Option<u32>),
    E(Duration),
}

#[allow(non_snake_case)]
#[repr(C)]
union MyEnumRepr {
    A: MyEnumVariantA,
    B: MyEnumVariantB,
    C: MyEnumVariantC,
    D: MyEnumVariantD,
    E: MyEnumVariantE,
}

#[repr(u8)] #[derive(Copy, Clone)] enum MyEnumTag { A, B, C, D, E }
#[repr(C)] #[derive(Copy, Clone)] struct MyEnumVariantA(MyEnumTag, u32);
#[repr(C)] #[derive(Copy, Clone)] struct MyEnumVariantB { tag: MyEnumTag, x: u8, y: i16 }
#[repr(C)] #[derive(Copy, Clone)] struct MyEnumVariantC(MyEnumTag);
#[repr(C)] #[derive(Copy, Clone)] struct MyEnumVariantD(MyEnumTag, Option<u32>);
#[repr(C)] #[derive(Copy, Clone)] struct MyEnumVariantE(MyEnumTag, Duration);

fn main() {
    let result: Vec<Result<MyEnum, ()>> = vec![
        Ok(MyEnum::A(17)),
        Ok(MyEnum::B { x: 206, y: 1145 }),
        Ok(MyEnum::C),
        Err(()),
        Ok(MyEnum::D(Some(407))),
        Ok(MyEnum::D(None)),
        Ok(MyEnum::E(Duration::from_secs(100))),
        Err(()),
    ];
        
    let input: Vec<u8> = vec![
        0,  17, 0, 0, 0,
        1,  206,  121, 4,
        2,
        8,
        3,  0,  151, 1, 0, 0,
        3,  1,
        4,  100, 0, 0, 0,  0, 0, 0, 0,  0, 0, 0, 0,
        0,
    ];
    
    let mut output = vec![];
    let mut buf = &input[..];
    
    unsafe {
        let mut dest: MyEnum = mem::uninitialized();
        while buf.len() > 0 {
            match parse_my_enum(&mut dest, &mut buf) {
                Ok(()) => output.push(Ok(dest)),
                Err(()) => output.push(Err(())),
            }
        }
    }
    
    assert_eq!(output, result);
}

#[repr(u8)]
#[derive(Copy, Clone, Eq, PartialEq, Debug)]
enum MyEnum {
    A(u32),
    B { x: u8, y: i16 },
    C,
    D(Option<u32>),
    E(Duration),
}

fn parse_my_enum<'a>(dest: &'a mut MyEnum, buf: &mut &[u8]) -> Result<(), ()> {
    unsafe {
        let dest: &'a mut MyEnumRepr = mem::transmute(dest);
        let tag = read_u8(buf)?;
        
        dest.A.0 = match tag {
            0 => MyEnumTag::A,
            1 => MyEnumTag::B,
            2 => MyEnumTag::C,
            3 => MyEnumTag::D,
            4 => MyEnumTag::E,
            _ => return Err(()),
        };
                
        match dest.B.tag {
            MyEnumTag::A => {
                dest.A.1 = read_u32_le(buf)?;
            }
            MyEnumTag::B => {
                dest.B.x = read_u8(buf)?;
                dest.B.y = read_u16_le(buf)? as i16;
            }
            MyEnumTag::C => {
                /* do nothing */
            }
            MyEnumTag::D => {
                let is_some = read_u8(buf)? == 0;
                if is_some {
                    dest.D.1 = Some(read_u32_le(buf)?);
                } else {
                    dest.D.1 = None;
                }
            }
            MyEnumTag::E => {
                let secs = read_u64_le(buf)?;
                let nanos = read_u32_le(buf)?;
                dest.E.1 = Duration::new(secs, nanos);
            }
        }
        Ok(())
    }
}

fn read_u64_le(buf: &mut &[u8]) -> Result<u64, ()> {
    if buf.len() < 8 { return Err(()) }
    let val = (buf[0] as u64) << 0 
            | (buf[1] as u64) << 8
            | (buf[2] as u64) << 16
            | (buf[3] as u64) << 24
            | (buf[4] as u64) << 32
            | (buf[5] as u64) << 40
            | (buf[6] as u64) << 48
            | (buf[7] as u64) << 56;
    *buf = &buf[8..];
    Ok(val)
}

fn read_u32_le(buf: &mut &[u8]) -> Result<u32, ()> {
    if buf.len() < 4 { return Err(()) }
    let val = (buf[0] as u32) << 0 
            | (buf[1] as u32) << 8
            | (buf[2] as u32) << 16
            | (buf[3] as u32) << 24;
    *buf = &buf[4..];
    Ok(val)
}

fn read_u16_le(buf: &mut &[u8]) -> Result<u16, ()> {
    if buf.len() < 2 { return Err(()) }
    let val = (buf[0] as u16) << 0 
            | (buf[1] as u16) << 8;
    *buf = &buf[2..];
    Ok(val)
}

fn read_u8(buf: &mut &[u8]) -> Result<u8, ()> {
    if buf.len() < 1 { return Err(()) }
    let val = buf[0];
    *buf = &buf[1..];
    Ok(val)
}

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions