Help with macro_rules

I have the event system, that allows subscribing to events using paths. And I've made the macro to create such paths in a less verbose way. That's fine. But I want to use the same macro for pattern matching.

path!("object", 10)
will expand to
[PathElement::from("object"), PathElement::from(10)]

But for pattern matching I need
path!("object", object_id)
to expand to
[PathElement::String("object"), PathElement::int(object_id)]
so that it can be used as a match pattern.

Full code:
playground

#[derive(Debug, Clone, PartialEq, Eq, Hash)]
enum PathElement {
    Any,  // match any path element
    Rest, // match any remaining path elements
    Int(i32),
    String(&'static str),
}

impl From<&'static str> for PathElement {
    fn from(value: &'static str) -> Self {
        Self::String(value)
    }
}

impl From<i32> for PathElement {
    fn from(value: i32) -> Self {
        Self::Int(value)
    }
}

macro_rules! path_element {
    (*) => {
        crate::PathElement::Any
    };
    ($element:literal) => {
        crate::PathElement::from($element)
    };
}

macro_rules! path {
    ($($element:tt), + ..) => {{
        [
			$(
				path_element!($element),
			)+
			PathElement::Rest,
		]
    }};
    ($($element:tt), +) => (
        [
			$(
				path_element!($element),
			)+
		]
	);
}

fn main() {
    let path = &path!("object", *, "action", * ..)[..];
    assert_eq!(path, [
                PathElement::String("object"),
                PathElement::Any,
                PathElement::String("action"),
                PathElement::Any,
                PathElement::Rest,
            ]);
    
    let path = &path!("object", 10, "action", 15)[..];
    match path {
        // I would like this to be
        // `path!("object", object_id, "action", action_id) => {...}`
        [crate::PathElement::String("object"), crate::PathElement::Int(object_id), crate::PathElement::String("action"), crate::PathElement::Int(action_id)] =>
        {
            assert_eq!(object_id, &10);
            assert_eq!(action_id, &15);
        }
        _ => {
            panic!("not matched");
        }
    }
    
    println!("ok");
}

I don't think you can do this, for a few reasons.

First of all, macros do not have any information about the context in which they are being expanded. As such, there's no way for path to know if it's supposed to expand to the "expression" or "pattern" version.

Secondly, macros do not have any type information. This means that in path!("object", object_id), the macro has no way of knowing that object_id is supposed to be an Int variant.

The first you can solve by having two different macros, or by including some explicit syntax to distinguish the two (e.g. path!(expr: x) vs. path!(pat: x).

The second you can solve either by switching to proc macros and guessing the variant kind from the name of the binding variant (i.e. if the name ends in _id, assume it's an Int, _name implies String), or by expanding the syntax to include some explicit indication of the variant (e.g. path_pat!(String("object"), Int(object_id)) or path_pat!("object": str, object_id: i32)).

I also think it is impossible, but still hope, that I just do not see the solution.

Actually, the first problem is not a problem at all, because the "pattern" version can be the same as the "expression".

[PathElement::String("object"), PathElement::int(10)]

is valid in both contexts.

The second problem can be solved with macro_rules by using prefixes:

path!(str "object", int 10)

But I do not like it.

Hi, welcome to macros. Enjoy your stay. :slight_smile:

But yes, I think that's probably the best you can hope for.

1 Like

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.