Convert not "unit-like" enum to bytes

I am trying convert an enum which has a variant "UNKNOWN(u8)" as default case. I get the following error.

extern crate serde; // 1.0.210

use serde::Serialize;

#[allow(non_camel_case_types)]
#[repr(u8)]
#[derive(Debug, PartialEq, Clone, Copy, Serialize)]
pub enum RelocType {
    ABSOLUTE = 0x00,
    HIGH = 0x01,
    LOW = 0x02,
    HIGHLOW = 0x03,
    UNKNOWN(u8),
}

impl From<u8> for RelocType {
    fn from(value: u8) -> Self {
        //Not matching all values.
        match value {
            0x00 => Self::ABSOLUTE,
            0x01 => Self::HIGH,
            0x02 => Self::LOW,
            0x03 => Self::HIGHLOW,
               _ => Self::UNKNOWN(value),
        }
    }
}

fn main() {
    let rtype = RelocType::from(1);
    
    println!("{}", rtype as u8);
}

(Playground)

Errors:

   Compiling playground v0.0.1 (/playground)
error[E0605]: non-primitive cast: `RelocType` as `u8`
  --> src/main.rs:32:20
   |
32 |     println!("{}", rtype as u8);
   |                    ^^^^^^^^^^^ an `as` expression can be used to convert enum types to numeric types only if the enum type is unit-only or field-less
   |
   = note: see https://doc.rust-lang.org/reference/items/enumerations.html#casting for more information

For more information about this error, try `rustc --explain E0605`.
error: could not compile `playground` (bin "playground") due to 1 previous error

Is there a way to do this?

Thanks.

You have to write a second match.

It's possible to bypass the “non-primitive cast” and fetch the discriminant even from an enum with fields, but that wouldn't get you what you want — all UNKNOWNs have the discriminant 4, not the u8 field value. (Note that UNKNOWN(0), UNKNOWN(1), UNKNOWN(2), and UNKNOWN(3) are all valid values of your enum, so the field can't be stored with the discriminant. You'd need a special U8GreaterThan3 type to achieve that.

1 Like

I would write a matching impl From<RelocType> for u8

I tried writing impl Into<u8> for RelocType (isn't it equivalent in this case) but could not figure out how to get the value associated with UNKNOWN variant.

I do not know what you mean by U8GreaterThan3 type. Can you please provide an example?

Thanks

This doesn't do what you probably think it does. The size of RelocType is 2 bytes, not 1 byte.

1 Like

Something like this (playground):

impl From<RelocType> for u8 {
    fn from(value: RelocType) -> Self {
        match value {
            RelocType::ABSOLUTE => 0,
            RelocType::HIGH => 1,
            RelocType::LOW => 2,
            RelocType::HIGHLOW => 3,
            RelocType::UNKNOWN(inner) => inner,
        }
    }
}

From the docs: "One should avoid implementing Into and implement From instead. Implementing From automatically provides one with an implementation of Into thanks to the blanket implementation in the standard library."

2 Likes

This is doable, but it also means matching all "known" variants same as From<u8>.

Also, I do not really understand what the docs mean in this case. What into implementation do I get?

If you implement From<RelocType> for u8, then the compiler generates an implementation Into<u8> for RelocType for you.

Thanks to that, both of these work:

let a: u8 = rtype.into(); // using Into<u8> for RelocType
let b = u8::from(rtype); // using From<RelocType> for u8
2 Likes

Instead of making Unknown a variant of the enum, I'd implement TryFrom<u8> for RelocType returning any invalid value as type Error = u8;.

And if you don't want to repeat yourself regarding the enum values (which might introduce inconsistencies), you can use num_traits::FromPrimitive:

3 Likes

Sorry, it was a hypothetical thing that doesn't really exist in Rust today. It would be a type that is similar to NonZeroU8 except that instead of excluding 0 from its possible values, it would exclude all of 0, 1, 2, 3.

Note that if you're doing this, it's possible that what you actually want is

#[repr(transparent)]
#[derive(Copy, Clone, Eq, PartialEq)]
struct RelocType(u8);

impl RelocType {
    const ABSOLUTE: Self = Self(0);
    const HIGH : Self = Self(1);
    const LOW : Self = Self(2);
    const HIGHLOW : Self = Self(3);
}

And then you can still use RelocType::HIGH in patterns, but the Unknown works more naturally.

2 Likes

This is a parsing code, I want to know what the value was while serializing/printing even though it did not map to a known variant, so returning Error is not really useful.

My implementation is taken from C api. Your solution is new to me but looks like a good alternative.

Thanks.

Ah, yeah, if you're trying to replicate a C "really just an int" enum, this is often the better pattern.

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.