Force unwrap inner type of an enum without knowing the variant type

I have an enum type like this:

pub enum Properties {
    PropertiesFoo(Foo),
    PropertiesBar(Bar),
    ...
}

and all types like Foo and Bar are #[repr(C)] types which first field is an int:

#[repr(C)]
Foo {
pub s_type: i32,
...
}
#[repr(C)]
Bar {
pub s_type: i32,
...
}

Now I want to implement PartialEq for my enum type, and by my design, if the s_type of two Foos are the same, then I consider they are equals.

So I write this:

impl PartialEq for Properties {
    fn eq(&self, other: &Self) -> bool {
       // how to unsafely get the first field, aka `s_type`, since I don't know the varint type
    }
}

But I don't know how to get the s_type unsafely. Any way to do this?

P.S. I just want to save Properties in a hash set, so I consider that compare s_type is not an ill design, right?

P.P.S The original problem is that when I write #[derive(PartailEq) for Properties, the compiler said PartailEq is not implement for Foo and Bar, and both Foo and Bar are source codes I can't change.

P.P.P.S I think this question can be split into two questions:

  1. If I just want the enum variant to be the same, no matter what the s_type is in it's tuple, how do I write the PartialEq implementation?
  2. If I consider that if both the enum variant and the s_type to be the same, then it is partial equals, how do I write the code?

P.P.P.P.S I'm sorry I missed key information, and asked a question which is an X/Y problem, the information is that there are tons of variants in the enum type, I don't want to write the enum arms case by case (I don't want to write derive macros either). So the question is probably to be: how to get a numeric representation of an enum variant. And how to unsafely get the s_type (since I didn't use the match pattern so I don't know the type).

For clarification, do you also want to consider two Properties values equal based on having the same s_type value, even if one is a PropertiesFoo and the other is a PropertiesBar?

No, this is not what I want. It's pretty like to save all different type of properties (Foo, Bar, ...) in a has map, which key is the s_type. I use enum is because enum will do the union for me, and enum is little more convenient.

And by the way, when I mean I just want to save Properties in a hash **map**, I mean I just want to save Properties in a hash **set**, I fixed my question.

Okay, then how do you want to handle other variants such as PropertiesBar in the eq implementation? Different variants are unequal, but for the same variant determine equality based on s_type? Why do you think "unsafe" operations would be involved for achieving this?

2 Likes

I guess you could work with something like

impl PartialEq for Properties {
    fn eq(&self, other: &Self) -> bool {
        use Properties::*;
        match (self, other) {
            (PropertiesFoo(x), PropertiesFoo(y)) => x.s_type == y.s_type,
            (PropertiesBar(x), PropertiesBar(y)) => x.s_type == y.s_type,
            _ => false,
        }
    }
}
impl Eq for Properties {}

use std::hash::{Hash, Hasher};
impl Hash for Properties {
    fn hash<H: Hasher>(&self, hasher: &mut H) {
        use Properties::*;
        std::mem::discriminant(self).hash(hasher);
        match self {
            PropertiesFoo(x) => x.s_type.hash(hasher),
            PropertiesBar(x) => x.s_type.hash(hasher),
        }
    }
}

Edit: Fixed the Hash implementation, hashing the discriminant, too, is important. (Or really just any kind of information encoding the variant.)

1 Like

This could probably be achieved by using mem::discriminant for simplicity + efficiency. Of course, it could also be done by match statements, similar to the previous answer.


Regarding the code in my previous response, the structure is somewhat different from what derive(PartialEq) gives you, e.g. expanding macros on

#[derive(PartialEq, Eq, Hash)]
struct Foo;
#[derive(PartialEq, Eq, Hash)]
struct Bar;

#[derive(PartialEq, Eq, Hash)]
enum Properties {
    PropertiesFoo(Foo),
    PropertiesBar(Bar),
}

produces

struct Foo;
impl ::core::marker::StructuralPartialEq for Foo {}
#[automatically_derived]
#[allow(unused_qualifications)]
impl ::core::cmp::PartialEq for Foo {
    #[inline]
    fn eq(&self, other: &Foo) -> bool {
        match *other { Self => match *self { Self => true, }, }
    }
}
impl ::core::marker::StructuralEq for Foo {}
#[automatically_derived]
#[allow(unused_qualifications)]
impl ::core::cmp::Eq for Foo {
    #[inline]
    #[doc(hidden)]
    #[no_coverage]
    fn assert_receiver_is_total_eq(&self) -> () { {} }
}
#[automatically_derived]
#[allow(unused_qualifications)]
impl ::core::hash::Hash for Foo {
    fn hash<__H: ::core::hash::Hasher>(&self, state: &mut __H) -> () {
        match *self { Self => {} }
    }
}
struct Bar;
impl ::core::marker::StructuralPartialEq for Bar {}
#[automatically_derived]
#[allow(unused_qualifications)]
impl ::core::cmp::PartialEq for Bar {
    #[inline]
    fn eq(&self, other: &Bar) -> bool {
        match *other { Self => match *self { Self => true, }, }
    }
}
impl ::core::marker::StructuralEq for Bar {}
#[automatically_derived]
#[allow(unused_qualifications)]
impl ::core::cmp::Eq for Bar {
    #[inline]
    #[doc(hidden)]
    #[no_coverage]
    fn assert_receiver_is_total_eq(&self) -> () { {} }
}
#[automatically_derived]
#[allow(unused_qualifications)]
impl ::core::hash::Hash for Bar {
    fn hash<__H: ::core::hash::Hasher>(&self, state: &mut __H) -> () {
        match *self { Self => {} }
    }
}

enum Properties { PropertiesFoo(Foo), PropertiesBar(Bar), }
impl ::core::marker::StructuralPartialEq for Properties {}
#[automatically_derived]
#[allow(unused_qualifications)]
impl ::core::cmp::PartialEq for Properties {
    #[inline]
    fn eq(&self, other: &Properties) -> bool {
        {
            let __self_vi = ::core::intrinsics::discriminant_value(&*self);
            let __arg_1_vi = ::core::intrinsics::discriminant_value(&*other);
            if true && __self_vi == __arg_1_vi {
                    match (&*self, &*other) {
                        (&Properties::PropertiesFoo(ref __self_0),
                            &Properties::PropertiesFoo(ref __arg_1_0)) =>
                            (*__self_0) == (*__arg_1_0),
                        (&Properties::PropertiesBar(ref __self_0),
                            &Properties::PropertiesBar(ref __arg_1_0)) =>
                            (*__self_0) == (*__arg_1_0),
                        _ => unsafe { ::core::intrinsics::unreachable() }
                    }
                } else { false }
        }
    }
    #[inline]
    fn ne(&self, other: &Properties) -> bool {
        {
            let __self_vi = ::core::intrinsics::discriminant_value(&*self);
            let __arg_1_vi = ::core::intrinsics::discriminant_value(&*other);
            if true && __self_vi == __arg_1_vi {
                    match (&*self, &*other) {
                        (&Properties::PropertiesFoo(ref __self_0),
                            &Properties::PropertiesFoo(ref __arg_1_0)) =>
                            (*__self_0) != (*__arg_1_0),
                        (&Properties::PropertiesBar(ref __self_0),
                            &Properties::PropertiesBar(ref __arg_1_0)) =>
                            (*__self_0) != (*__arg_1_0),
                        _ => unsafe { ::core::intrinsics::unreachable() }
                    }
                } else { true }
        }
    }
}
impl ::core::marker::StructuralEq for Properties {}
#[automatically_derived]
#[allow(unused_qualifications)]
impl ::core::cmp::Eq for Properties {
    #[inline]
    #[doc(hidden)]
    #[no_coverage]
    fn assert_receiver_is_total_eq(&self) -> () {
        {
            let _: ::core::cmp::AssertParamIsEq<Foo>;
            let _: ::core::cmp::AssertParamIsEq<Bar>;
        }
    }
}
#[automatically_derived]
#[allow(unused_qualifications)]
impl ::core::hash::Hash for Properties {
    fn hash<__H: ::core::hash::Hasher>(&self, state: &mut __H) -> () {
        match (&*self,) {
            (&Properties::PropertiesFoo(ref __self_0),) => {
                ::core::hash::Hash::hash(&::core::intrinsics::discriminant_value(self),
                    state);
                ::core::hash::Hash::hash(&(*__self_0), state)
            }
            (&Properties::PropertiesBar(ref __self_0),) => {
                ::core::hash::Hash::hash(&::core::intrinsics::discriminant_value(self),
                    state);
                ::core::hash::Hash::hash(&(*__self_0), state)
            }
        }
    }
}

which notably involves unsafe unreachable, as well as using the discriminant for the PartialEq, too. I don't know how much (if any) performance benefits this has.

1 Like

I'm sorry I missed key information, and asked a question which is an X/Y problem, the information is that there are tons of variants in the enum type, I don't want to write the enum arms case by case (I don't want to write derive macros either). So the question is probably to be: how to get a numeric representation of an enum variant. And how to unsafely get the s_type (since I didn't use the match pattern so I don't know the type).

I had planned to delete this question and create a new one, but it's not very polite to do so, since you are trying to help :rofl:

And It seems that std::mem::discriminant is what I'm looking for. I'll try first.

how about a macro_rules macro though?

#[repr(C)]
pub struct Foo {
    pub s_type: i32,
}
#[repr(C)]
pub struct Bar {
    pub s_type: i32,
}

use paste::paste;
use std::hash::{Hash, Hasher};

macro_rules! define_properties {
    ($($T:ident)*) => {
        paste! {
            pub enum Properties {
                $(
                    [<Properties $T>]($T),
                )*
            }

            impl PartialEq for Properties {
                fn eq(&self, other: &Self) -> bool {
                    use Properties::*;
                    match (self, other) {
                        $(
                            ([<Properties $T>](x), [<Properties $T>](y)) => x.s_type == y.s_type,
                        )*
                        _ => false,
                    }
                }
            }
            impl Eq for Properties {}

            impl Hash for Properties {
                fn hash<H: Hasher>(&self, hasher: &mut H) {
                    use Properties::*;
                    std::mem::discriminant(self).hash(hasher);
                    match self {
                        $(
                            [<Properties $T>](x) => x.s_type.hash(hasher),
                        )*
                    }
                }
            }
        }
    }
}

define_properties!{
    Foo
    Bar
}
1 Like

That is nice, avoiding of creating a crate for derive macros.

By the way, discriminant meet the requirement of

get a numeric representation of an enum variant

Do you happen to know if I can get a pointer to the inner type? In this way, I can use let s_type : *const i32 = transmute(ptr)

A quick look into the assembly

reveals that adding a test for whether the discriminants are equal for the PartialEq implementation helps code optimization a lot. The unsafe version of unreachable can be avoided in this case without negatively affecting the generated assembly, AFAICT.

By the way, if you want to make sure that the s_type fields are all in the same place, you would need to put repr(C) onto the enum, too, otherwise, once Foo and Bar would have different alignments, their s_type field in the enum might be located at different places. E.g. see this example: Compiler Explorer

1 Like

On second thought, it's probably even more straightforward to just add a method for accessing s_type into the macro: Compiler Explorer

As noted in the previous answer, you'd first need to make sure that the inner type is always at the same place, e.g. with repr(C) on the enum. See Type layout - The Rust Reference for the exact effects that this has. You'd still have to figure out the correct offset though somehow (which depends on how the discriminant is actually represented, as well as the maximal alignment of all the types Foo, Bar, etc. Using a macro-generated accessor function (like in my last response) and relying on optimization in case you care about performance in the first place might be more straightforward than using potentially-broken or potentially-platform-dependently-broken unsafe code.

If I have to write repr(C) for an enum and calculate the offset, it's pretty the same as "no, there's no interface in Rust to expose the inner value of an enum variant without using pattern match".

1 Like

If you're guaranteed that the s_type is distinct for each variant and you're ok with unsafe, I think you can instead do something like:

#[repr(C)]
struct Properties {
  s_type: u32,
  s_data: PropertiesData,
}

#[repr(C)]
union PropertiesData {
  Foo(Foo),
  ..
}

moving the type out of the Foo, Bar, etc structs, and passing the whole Properties struct back and forwards from FFI. This is messy, of course, but if you have this type property anyway, it might be less messy that trying to map all the time.

Like with all unsafe code, you're going to want to wrap it up tight in a safe interface, probably conversion to and from the current enum, which is what I'm assuming you're writing!

If that s_type is statically known, you could move the whole problem into the type system and make that an enum itself.

Not if it's part of the FFI contract.