rust-lang / rfcs

RFCs for changes to Rust
https://rust-lang.github.io/rfcs/
Apache License 2.0
5.78k stars 1.55k forks source link

Generate an automatic implementation of TryFrom and Into for C-style enums #3600

Open agreyyy opened 3 months ago

agreyyy commented 3 months ago

I would like to propose that the compiler generates an automatic implementation of TryFrom<{Integer}> and Into<{Integer}> for C-style enums. Now I don`t know if they are actually called C-style enums by the community, but I am referring to these types of enums.

enum CStyleEnum {
    Variant1 = 10,
    Variant2 = 20,
    //....
    Variant10 = 100,
}

One cool thing about C-style enums is that you can convert them to any integer type (u8,u16, etc.) using as syntax:

asssert_eq!(CStyleEnum::Variant10 as u8, 100); //this works

However, C-style enums do not auto implement the Into<{Integer}> and TryFrom<{Integer}> traits like other Integers do (i16, u16, etc.), but I think they should. Since C-Style enums are just a subset of all Integers, I think all the common methods of converting them to Integers and converting Integers to C-Style enums except converting Integers to C-Style Enums with the as keyword(example below) should be supported by the compiler.

assert_eq!(100 as CStyleEnum, CStyleEnum::Variant10); //This should NOT work, 
let n1: isize = -100; // Even Though this does
let n2: usize = 100;
assert_eq!(n2 as isize, n1);

Into<{Integer}>

//an automatic implementation of Into<{Integer}> would reuse all the logic defined for the lines below
//impl of Into for C style enums
let into_num = CStyleEnum::Variant2 as u8;
let into_num = CStyleEnum::Variant2 as u16;
let into_num = CStyleEnum::Variant2 as u32;
let into_num = CStyleEnum::Variant2 as u64;
let into_num = CStyleEnum::Variant2 as i8;
let into_num = CStyleEnum::Variant2 as i16;
let into_num = CStyleEnum::Variant2 as i32;
let into_num = CStyleEnum::Variant2 as i64;
let into_num = CStyleEnum::Variant2 as usize;
let into_num = CStyleEnum::Variant2 as isize;

//look at all those automatic conversion implementations, made by the compiler, it would be
//really nice if it went the other way with Into and TryFrom
let variant = CStyleEnum::Variant1;
let into: u8 = variant.into();
assert_eq!(into, 1u8); // I would like for this to work since its logically equal to the above lines using the `as` syntax

I think The auto-impl of Into<{Itegers}> would make sense since the C-style enum explicitly states that you want states to map to some sort of number. Anyways, the most important part of my proposal in the TryFrom<{Integer}> auto-implementation.

TryFrom<{Integer}>

//it would be very, very nice to have an automatic implementation of TryFrom for this type
let try_from = CStyleEnum::try_from(100)
assert_eq!(Ok(CStyleEnum::Variant10), try_from);

//this is much less boilerplate than the following:
impl TryFrom<{u8}> for CStyleEnum {
    type Err = ();
    fn try_from(value: u8) -> Result<Self, Self::Err> {
        match value {
            10 => Ok(CStyleEnum::Variant1),
            20 => Ok(CStyleEnum::Variant2),
            100 => Ok(CStyleEnum::Variant10),
            _ => Err(()) //idk what sort of useful error you can put for this operation since there is only one point of failure
        }
    }
}

Imagine If this CStyleEnum that I`ve been reusing for these code examples has more than 3 variants, or if I had made multiple C-style enums in my codebase, it would be a real eyesore to look at all those manual TryFrom implementations, and a waste of time to write a macro for something so trivial, and probably unnecessary to bring in an external crate with a macro to implement the TryFrom for me like Strum, since the compiler already logically does this for conversions between numbers, e.g. this is What a TryFrom would look like for u8:

//logically, this boilerplate is very similar
//im sure the std does this some different way, but this is probably one of the ways it could
impl TryFrom<i8> for u8 {
    type Error = ();
    fn try_from(value: i8) -> Result<Self, Self::Error> {
        match value {
            value if value > 0 => Ok(transmute(value)) //some sort of bitshifting to turn the i8`s internal
                                                        //bits into the bits of a u8
            _otherwise => Err(())
        }            
    }
}

The drawbacks of this would be if for some reason, the developer would want a different TryFrom implementation for their C-Style enum, but maybe the rust compiler could let the developer implement it themselves, like an ovveride, or instead the developer could opt-in to the compilers implementation via a #[derive(TryFrom)] for their C-Style Enum.

Conclusion

Anyways, this is just a small Quality Of Life change for Rust, which abstracts a bit of boilerplate away from the developers (us), therefore making our experience better, I think the magnitude of this change is similar to adding a (#[derive(Default)] for enums)[https://github.com/rust-lang/rfcs/pull/3107]

agreyyy commented 3 months ago

dk if I should turn this into an RFC or not, I was working with C-Style Enums for a DNS project and found converting from and to bytes with C-Style Enums to be far too much boilerplate for the complexity of the logic defined by them.

programmerjake commented 3 months ago

sounds good as long as you have to opt-in, e.g. via #[derive(Into, TryFrom)]

kennytm commented 3 months ago

for reference OP submitted an actual RFC at #3604.

SOF3 commented 2 months ago

Why do we specifically want Into and TryFrom instead of a dedicated, probably auto trait for these conversions (like the safe-transmute APIs)?

kennytm commented 2 months ago

well, for one, those dedicated traits are already proposed in other RFCs 🙃 (AsRepr, FromRepr @ rust-lang/rust#86772 ← #3040, #3046, not yet implemented)