Controlling Option layout optimization for PCI

Hi,

I’ve been working on modelling the PCI configuration space and would really like to be able to control the mapping of Option<SomeU8Enum> and Option<0..=254>.

Firstly, the PCI Interrupt Pin has 5 values:-

0x00 - None
0x01 to 0x04 - Something meaningful.

I can’t use an enum to represent this as Option, even if enum SomethingMeaningful is repr(u8), as Rust maps None to a value of 0x05! This means one can’t use an enum here that re-uses Rust’s Option type. Is there anything experimental in the works?

Secondly, I have a number of types where the situation is reversed, and 0xFF is None. I’d really like a NonZeroFF type, but failing that, it would be nice to likewise have an enum. Using new type wrappers id, of course, quite do-able, but it doesn’t exactly model the situation I want.

Anything in the works here? Anything the Rust lang has got experimentally?

Have you seen issue #59788? It’s quite new and there doesn’t seem to be an RFC for this yet.

I guess at the moment the only workaround is to use your own option-like type?

Currently you have no other choice but to implement the OptionEnum yourself, without nesting. With macros the task can be automated:

derive_option! {
OptionMyNonZeroEnum for
    #[non_null]
    #[repr(u8)]
    enum MyNonZeroEnum  {
        A,
        B,
        C,
        D,
    }
}

fn main ()
{
    use self::MyNonZeroEnum::*;
    assert_eq!(1, dbg!(A as u8));
    assert_eq!(2, dbg!(B as u8));
    assert_eq!(3, dbg!(C as u8));
    assert_eq!(4, dbg!(D as u8));

    assert_eq!(0, dbg!(OptionMyNonZeroEnum::None as u8));
    assert_eq!(1, dbg!(OptionMyNonZeroEnum::Some(A) as u8));
    assert_eq!(2, dbg!(OptionMyNonZeroEnum::Some(B) as u8));
    assert_eq!(3, dbg!(OptionMyNonZeroEnum::Some(C) as u8));
    assert_eq!(4, dbg!(OptionMyNonZeroEnum::Some(D) as u8));

    assert_eq!(0, dbg!(OptionMyNonZeroEnum::from(None) as u8));
    assert_eq!(1, dbg!(OptionMyNonZeroEnum::from(Some(A)) as u8));
    assert_eq!(2, dbg!(OptionMyNonZeroEnum::from(Some(B)) as u8));
    assert_eq!(3, dbg!(OptionMyNonZeroEnum::from(Some(C)) as u8));
    assert_eq!(4, dbg!(OptionMyNonZeroEnum::from(Some(D)) as u8));
}

There is another way of doing this:

Just start with the value 1 and end it with the max value of your repr, e.g. 255 for u8.

#[repr(u8)]
enum MyNonZeroEnum {
    A = 1,
    B,
    C,
    D,
    E = 255,
}

fn main() {
    unsafe {
        dbg!(::core::mem::size_of::<Option<MyNonZeroEnum>>());
        dbg!(::core::mem::transmute::<Option<MyNonZeroEnum>, u8>(None));
    }
}

If you messed something up, e.g. forgot to do A = 1, rust will complain:

error[E0512]: cannot transmute between types of different sizes, or dependently-sized types
  --> src/main.rs:13:14
   |
13 |         dbg!(::core::mem::transmute::<Option<MyNonZeroEnum>, u8>(None));
   |              ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
   |
   = note: source type: `std::option::Option<MyNonZeroEnum>` (16 bits)
   = note: target type: `u8` (8 bits)

You could use that as an compile time assert :slight_smile: (note, that this does not guarantee that None is always 0, but you could write a simple assert! for that)

(Playground with static asserts for both cases)

2 Likes

Nice to know! But since the OP’s described enum does not reach 0xff, having an unused discriminant to force None into 0 breaks the point of using an enum to begin with, imho.

You’re right and I think this could be changed IMHO, e.g. that None does not take the hightest available integer, but the lowest. Let’s see if I’ll open an issue :smile:
One could do something like

enum MNZE {
    A = 1,
    B,
    C,
    D,
    #[doc(hidden)]
    _reserved = 255
} 

to force it and not showing up in the documentation.

1 Like

Here is the generalized version of the previous macro to also handle the maximum discriminant being free:

    derive_option! {OptionMyNonMaxEnum  for
        #[non_max]
        #[repr(u8)]
        enum MyNonMaxEnum  {
            A,
            B,
            C,
            D,
        }
    }
    use self::MyNonMaxEnum::*;

    fn main ()
    {
        assert_eq!(0, dbg!(A as u8));
        assert_eq!(1, dbg!(B as u8));
        assert_eq!(2, dbg!(C as u8));
        assert_eq!(3, dbg!(D as u8));
    
        assert_eq!(0xff, dbg!(OptionMyNonMaxEnum::None as u8));
        assert_eq!(0, dbg!(OptionMyNonMaxEnum::Some(A) as u8));
        assert_eq!(1, dbg!(OptionMyNonMaxEnum::Some(B) as u8));
        assert_eq!(2, dbg!(OptionMyNonMaxEnum::Some(C) as u8));
        assert_eq!(3, dbg!(OptionMyNonMaxEnum::Some(D) as u8));
    
        assert_eq!(0xff, dbg!(OptionMyNonMaxEnum::from(None) as u8));
        assert_eq!(0, dbg!(OptionMyNonMaxEnum::from(Some(A)) as u8));
        assert_eq!(1, dbg!(OptionMyNonMaxEnum::from(Some(B)) as u8));
        assert_eq!(2, dbg!(OptionMyNonMaxEnum::from(Some(C)) as u8));
        assert_eq!(3, dbg!(OptionMyNonMaxEnum::from(Some(D)) as u8));
    }
1 Like