包装任意类型的宏

时间:2018-12-09 18:28:33

标签: rust rust-macros

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

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 个答案:

答案 0 :(得分: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 :(得分: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);
}

还要注意,您必须传递引用而不是值。