首页 文章

包含任意数量类型的宏

提问于
浏览
1

是否可以编写一个宏来定义包含任意数量(不同)输入类型的枚举?我想做一种类型级别的匹配 .

type_switch!(i32 => println!("integer"), f32 => println!("float"), Foo => println!("foo"))

这将扩展到:

{
    enum Wrapper {
        Variant1(i32),
        Variant2(f32),
        Variant3(Foo),
    }

    // impl From<i32>, From<f32>, From<Foo> for Wrapper

    |x: Wrapper| match x {
        Wrapper::Variant1(x) => println!("integer"),
        Wrapper::Variant2(x) => println!("float"),
        Wrapper::Variant3(x) => println!("foo"),
    }
}

这样我就可以这样写

let switch = type_switch!(i32 => println!("integer"), f32 => println!("float"), Foo => println!("foo"));
switch(32.into()); // prints "integer"
switch(24.0.into()); // prints "float"

2 回答

  • 1

    如您所建议的那样编写包装器类型是有意义的,但前提是代码的较大部分需要该类型 .

    您的具体示例将在每次使用宏时定义新的枚举,将值移动到新的枚举中,然后立即将其丢弃 .

    这不是惯用的方法,如果这确实是你想象的用途,我建议寻找不同的选择 .

    也就是说,我在很多场合都使用了包装类型 .

    这样的东西将用于声明包装器:

    macro_rules! declare_wrapper {
      (
        $enum_name:ident {
          $( $variant_name:ident( $typ:ty : $description:expr ) ),*
        }
      )=> {
        pub enum $enum_name {
          $(
            $variant_name($typ),
          )*
        }
    
        $(
          impl From<$typ> for $enum_name {
            fn from(value: $typ) -> Self {
              $enum_name::$variant_name(value)
            }
          }
        )*
    
        impl $enum_name {
          fn describe(&self) -> &'static str {
            match self {
              $(
                &$enum_name::$variant_name(_) => $description,
              )*
            }
          }
        }
      };
    }
    
    declare_wrapper!( MyWrapper {
      MyInt(i64 : "int"),
      MyString(String : "string")
    });
    
    fn main() {
      let value = MyWrapper::from(22);
      println!("{}", value.describe());
    }
    

    您还可以对此进行扩展,以添加所需的其他方法或特征 . 我经常做类似的事情 .

  • 1

    在宏中定义特征并为每种类型实现它:

    macro_rules! type_switch {
        ($($ty: ty => $expr: expr),+) => {{
            trait TypeMatch {
                fn type_match(self);
            }
            $(
                impl TypeMatch for $ty {
                    fn type_match(self) {
                        $expr
                    }
                }
            )+
            TypeMatch::type_match
        }}
    }
    

    请注意,第一次调用该函数时,编译器将绑定该类型,以便后续调用必须是相同的类型:

    struct Foo;
    
    fn main() {
        let s = type_switch! {
            i32 => { println!("i32"); },
            f32 => { println!("f32"); },
            Foo => { println!("Foo"); }
        };
    
        s(0);
        s(Foo); // Error!
    }
    

    如果您需要能够使用不同类型调用它,可以通过使用特征对象进行动态分派来修复(以较低的成本):

    macro_rules! type_switch {
        ($($ty: ty => $expr: expr),+) => {{
            trait TypeMatch {
                fn type_match(&self);
            }
            $(
                impl TypeMatch for $ty {
                    fn type_match(&self) {
                        $expr
                    }
                }
            )+
            |value: &dyn TypeMatch| {
                value.type_match()
            }
        }}
    }
    
    struct Foo;
    
    fn main() {
        let s = type_switch! {
            i32 => { println!("i32"); },
            f32 => { println!("f32"); },
            Foo => { println!("Foo"); }
        };
    
        s(&0);
        s(&Foo);
    }
    

    另请注意,您必须传递引用而不是值 .

相关问题