Макрос, который оборачивает произвольное количество типов - PullRequest
0 голосов
/ 09 декабря 2018

Можно ли написать макрос, который определяет перечисление, которое оборачивает произвольное количество (различных) типов ввода?Я хотел бы провести своего рода сопоставление на уровне типа.

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 голосов
/ 09 декабря 2018

Определите черту в вашем макросе и внедрите ее для каждого типа:

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

Также обратите внимание, что вы должны передавать ссылки вместо значений.

0 голосов
/ 09 декабря 2018

Может иметь смысл писать типы-обертки, как вы предлагали, но только если этот тип необходим в больших частях вашего кода.

Ваш конкретный пример будет определять новое перечисление каждый раз, когда вы используете макроспереместите значение в новое перечисление, а затем сразу же выбросите его.

Это не идиоматический подход, и если это действительно ваше воображаемое использование, я бы порекомендовал поискать другие варианты.

Этосказал, что я использовал типы обертки в ряде случаев.

Примерно так будет работать для объявления обертки:

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

Вы также можете расширить это, чтобы добавить дополнительные методы или признаки характерачто тебе нужно.Я делал подобные вещи довольно часто.

...