Type of (mythical) discriminant_of?

in std::mem there are generally functions e.g. size_of and size_of_val, where size_of takes a type and size_of_val a &T.

the discriminant function follows similarly to the _of_val variations of other functions, however it lacks the variation taking the type as input. It doesn't really want a type as input, rather a const generic, or a constructor value?

enum Foo {
    Bar(())
}

looking at error messages the &Foo::Bar has either type fn(()) -> Foo::{Foo::Bar}, or &fn(()) -> Foo::{Foo::Bar} not sure.

Anyhow I looked for a bit through const generics, but could not figure out how to declare anything resembling the type discriminant_of should have. e.g. (paraphrasing something to the effect of) discriminant_of<T, E, V>(fn(T) -> E::{E::V}) -> Discriminant<E>...

Anyhow, I'd love to see a function of this form in the std library in the future but it is definitely a weird one.

I believe the type of the discriminant is deliberately not accessible. Per the mem::discriminant RFC https://github.com/rust-lang/rfcs/blob/master/text/1696-discriminant.md

Add a function that extracts the discriminant from an enum variant as a comparable, hashable, printable, but (for now) opaque and unorderable type.

Making Discriminant generic provides several benefits:

  • discriminant(&EnumA::Variant) == discriminant(&EnumB::Variant) is statically prevented.
  • In the future, we can implement different behavior for different kinds of enums. For example, if we add a way to distinguish C-like enums at the type level, then we can add a method like Discriminant::into_inner for only those enums. Or enums with certain kinds of discriminants could become orderable.

Alternatives

...

  1. Directly stabilize the discriminant_value intrinsic, or a wrapper that doesn't use an opaque newtype. This more drastically precludes future enum representation optimizations, and won't be able to take advantage of future type system improvements that would let discriminant return a type dependent on the enum.

So, aside from speculating about the distant future of Rust where more of this type magic is available and locking in discriminant types might be acceptable... what did you want this for? Or were you just wondering why it wasn't there?

EDIT: https://github.com/rust-lang/rfcs/pull/2684 is an interesting RFC on discriminants that ended up closed for now

Essentially i'd like to use a Discriminant, in a function which only takes a single argument of type EnumA, and its a bit awkward to synthesize a value of type EnumA, to take the address of so I can pass it to std::mem::discriminant.

I don't think the des...(&EnumA::V) == desc(&EnumB) is an issue here it's already taken care of by Descriminant not having that form of equality.

Okay, but that's not a use case. At this level of abstraction I can still just ask "why?" again. Could you get a little more concrete about what you're trying to accomplish with this function?

1 Like

Essentially, say I have an proc-macro for enum Foo above,

#[Derive(Something)]
enum Foo {
  #[special]
  Bar(()),
  Baz,
}

And from the derive macros in an assosciated trait, i'd like to match from a reference to an instance of that enum

impl Foo {
  fn is_special(self) -> bool {
   std::mem::discriminant(&self) == std::mem::discriminant_of(&Foo::Bar)
 }
}

Other than that it was mostly curiousity, essentially I'm just trying to derive a bunch more stuff that currently I have to derive manually for each enum I use the derive macro on...

If I'm reading that right, it's equivalent to matches!(self, Foo::Bar(_)). What's the advantage of explicitly extracting discriminant values in this case?

3 Likes

Sorry, It's early and and my example didn't really reflect what I'd hoped for If I limit that to:

impl Foo {
  const Special: Descriminant<Foo> = discriminant_of(&Foo::Bar)
}

the hope was that people could derive stuff which are generic over all the enums which are derived from the macro rather than them having to manually derive it, for their specific Foo, and specific Foo::Bar... The derive macro cannot provide really all the logic necessary, and just write generic functions generalizing over Discriminant<T>...

I guess I just find it weird that there is only a binary mechanism for the type.

Am I getting this right: You want to mark an enum variant for the purpose of doing derives. Isn't that what attributes are good for?

Edit: Reading your description a few more times, perhaps this is not what you meant.

What I want to do is allow users to generalize over Discriminant<T>, but such that it can be used in a unary function accepting a single value of type T, but in order to do so, I need a way for the generic function, to be able to capture a value of type Discriminant<T>, and there is no current way for either the derive macro, or the generic function to synthesize a value of type &T, to get the right kind of a Discriminant<T>

I'm still having a really hard time figuring out what it is that you're trying to achieve here. A concrete example/use caelse would probably help.

Maybe try to assume that the feature you're asking for does exist, then give/outline some code of what to do with it that isn't really possible already in a better way.

Thinking about it, I don't really know the canonical use cases of the current discriminant function either.

1 Like

Alright, i'll work on coming up with a concrete example (though finding concrete examples for generic problems which can't be expressed in the current language :cry:)

I guess, even putting your previous abstract use case into (pseudo-)code should help a lot. It doesn't have to be, like, a real-world practical concrete example.

1 Like

Here is the first thing that came to mind, If it could get rid of the parameter d, and

use std::mem::Discriminant;
use std::mem::discriminant;

enum Foo {
    Foo(())
}

impl Foo {
    // won't work, not const...
    // const FooDiscrim: Discriminant<Foo> = discriminant(&Foo::Foo(()));
}

fn foo<T>(x: T, d: Discriminant<T>) -> Result<T, T> {
    if discriminant(&x) == d {
        Err(x)
    } else {
        Ok(x)
    }
}

This example removing d, I guess brings in a bunch of orthogonal concerns but it would just be nice to be able to get at Discriminant<Foo>, so you can call it when you only have one reference to a Foo.

There is a feature for const_discriminant, related to this (I haven't found an RFC for it though, I'll keep looking), this is as close as I got assuming Default, but that isn't const.

Essentially discriminant is currently only able to be gotten at if you can produce a value of it.

#![feature(never_type)]
#![feature(const_discriminant)]
#![feature(const_fn)]
#![feature(associated_type_bounds)]

use std::mem::Discriminant;
use std::mem::discriminant;

const NONE_DISCRIM: Discriminant<Option<!>> = discriminant(&None);

enum Foo<T> {
    Foo(T),
}

impl<T> Foo <T>
  where T: Default {
  const FOO_DISCRIM: Discriminant<Foo<T>> = discriminant(&Foo::Foo(Default::default()));
}
#[derive(Debug)]
enum Foo {
    Bar(()),
    Baz,
}

fn main ()
{
    assert_eq!(Foo::Bar(()).discriminant(), Foo::BAR_DISCRIMINANT);
    assert_ne!(Foo::Baz.discriminant(), Foo::BAR_DISCRIMINANT);
}

const _: () = {
    #[derive(Debug)]
    pub
    struct Bar;

    #[derive(Debug)]
    pub
    struct FooDiscriminant<'__> /* = */ (
        &'__ Foo,
    );

    impl Foo {
        pub
        const BAR_DISCRIMINANT: Bar = Bar;
        
        #[inline]
        pub
        fn discriminant (self: &'_ Self)
          -> FooDiscriminant<'_>
        {
            FooDiscriminant(self)
        }
    }

    impl<'__> PartialEq<FooDiscriminant<'__>> for Bar {
        fn eq (self: &'_ Bar, other: &'_ FooDiscriminant<'__>)
          -> bool
        {
            matches!(other.0, &Foo::Bar(_))
        }
    }

    impl<'__> PartialEq<Bar> for FooDiscriminant<'__> {
        fn eq (self: &'_ FooDiscriminant<'__>, _: &'_ Bar)
          -> bool
        {
            matches!(self.0, &Foo::Bar(_))
        }
    }
};

cool, just want to note that seems to be generalizing over FooDiscriminant<T>, rather than any Discriminant<T>. I guess capturing it though kind of ruins that.

Yeah, FooDiscriminant<'__> could rather be DiscriminantOf<'__, Enum> and then have the PartialEq impls of Bar "just" target DiscriminantOf<'__, Foo>. This way you can factor that struct out of the macro (leading to a tiny compilation improvement).

Anyways, in both cases there is boilerplate required, but that should not be an issue for a (derive) macro.

1 Like

This topic was automatically closed 90 days after the last reply. New replies are no longer allowed.