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"
4

2 回答 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());
}

您还可以扩展它以添加您需要的其他方法或特征实现。我经常做类似的事情。

于 2018-12-09T19:16:33.617 回答
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!
}

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

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);
}

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

于 2018-12-09T19:27:18.313 回答