Crate to automatically derive as_variant() getters for enums?

I remember seeing a custom derive that would automatically generate as_some_variant() and is_some_variant() methods for a particular enum, but now I can't remember its name.

Given something like this:

#[derive(AsVariant, IsVariant)]
enum Value {
  String(String),
  Null,
  ...
}

I'm hoping to generate something like the following:

impl Value {
  fn is_string(&self) -> bool { matches!(self, Value::String(_)) }

  fn as_string(&self) -> Option<&String> {
    match self {
      Value::String(s) => Some(s),
      _ => None,
    }
  }

  fn is_null(&self) -> bool { matches!(self, Value::Null) }
}

Writing these methods by hand isn't feasible because in practice the Value type is generated by a macro_rules! macro and there are many different enums that may be introduced on an ad-hoc basis[1].


  1. I'm doing AST manipulation and need an easy way to represent things like type DefinitionSite = FunctionDefinition | LetStatement | StructDefinition | Whatever. ↩︎

rust-analyzer has an assist for that IIRC.

Yeah I know about that assist and use it elsewhere, but that won't work in this situation because rust-analyzer doesn't show assists for items inside a macro invocation and it won't automatically keep them in sync when I add/remove fields.

For reference, I have several of these macro invocations and manually keeping 20+ sets of is_xxx() and as_xxx() methods in sync with the definition isn't practical.

ast_node_union! {
    /// The place a name can first be introduced.
    #[derive(Debug, Clone, PartialEq, Eq, Hash)]
    pub enum NameDefinitionSite {
        Function(ast::NamedFunctionDefinition),
        Module(ast::NamedModuleDefinition),
        Constant(ast::AssignmentStatement),
        Parameter(ast::Parameter),
    }
}

If you’re generating the enum in a macro, you can generate the methods in the same macro as well.

You can't create new identifiers like is_function() or as_module() from a macro_rules! macro, though. That's why I was looking for a custom derive that can do it for me.

For reference, here is the full macro definition.

macro_rules! ast_node_union {
    (
        $(#[$meta:meta])*
        $vis:vis enum $name:ident {
            $(
                $variant:ident($ty:ty)
            ),*
            $(,)?
        }
    ) => {
        $(#[$meta])*
        $vis enum $name {
            $($variant($ty)),*
        }

        impl rowan::ast::AstNode for $name {
            type Language = scad_syntax::OpenSCAD;

            fn can_cast(kind: scad_syntax::SyntaxKind) -> bool
            where
                Self: Sized,
            {
                $( <$ty>::can_cast(kind) )||*
            }

            fn cast(node: rowan::SyntaxNode<Self::Language>) -> Option<Self>
            where
                Self: Sized,
            {
                $(
                    if let Some(node) = <$ty>::cast(node.clone()) {
                        return Some($name::$variant(node));
                    }
                )*
                None
            }

            fn syntax(&self) -> &rowan::SyntaxNode<Self::Language> {
                match self {
                    $(
                        $name::$variant(n) => n.syntax(),
                    )*
                }
            }
        }

        $(
            impl From<$ty> for $name {
                fn from(node: $ty) -> Self {
                    $name::$variant(node)
                }
            }

            impl TryFrom<$name> for $ty {
                type Error = $crate::macros::ConversionFailed;

                fn try_from(value: $name) -> Result<$ty, Self::Error> {
                    match value {
                        $name::$variant(v) => Ok(v),
                        _ => Err($crate::macros::ConversionFailed),
                    }
                }
            }
        )*
    };
}

(playground)

You can, with the paste crate. It can also make the idents lowercase.

Yeah, I might do that in the meantime. I was hoping to find the crate I had used in the past though, because it also had some other things I'd like to use.

Searching as_variant in Lib.rs, I wonder whether it is this:

Some crates that I don't think are quite what you seek are

2 Likes