简体   繁体   中英

How do I match the type of an expression in a Rust macro?

This is just pseudocode:

macro_rules! attribute {
    $e: expr<f32> => { /* magical float stuff */ };
    $e: expr<i64> => { /* mystical int stuff */ };
};

I would like to have a differently expanded macro depending on the type that I passed to the macro.

This is how it would work in C++

template <typename T>
struct Attribute{ void operator(T)() {} };

template <>
struct Attribute<float> {
    void operator(float)(float) { /* magical float stuff */ }
};

template <>
struct Attribute<long> {
    void operator()(long) { /* mystical int stuff */ }
}

Rust macros aren't able to do that. Macros operate at the syntactic level, not at the semantic level. That means that although the compiler knows it has an expression (syntax), it doesn't know what the type of the expression's value (semantic) is at the moment the macro is expanded.

A workaround would be to pass the expected type to the macro:

macro_rules! attribute {
    ($e:expr, f32) => { /* magical float stuff */ };
    ($e:expr, i64) => { /* mystical int stuff */ };
}

fn main() {
    attribute!(2 + 2, i64);
}

Or, more simply, define multiple macros.


If you want to do static (compile-time) dispatch based on the type of an expression, you can use traits. Define a trait with the necessary methods, then implement the trait for the types you need. You can implement a trait for any type (including primitives and types from other libraries) if the impl block is in the same crate as the trait definition.

trait Attribute {
    fn process(&self);
}

impl Attribute for f32 {
    fn process(&self) { /* TODO */ }
}

impl Attribute for i64 {
    fn process(&self) { /* TODO */ }
}

macro_rules! attribute {
    ($e:expr) => { Attribute::process(&$e) };
}

fn main() {
    attribute!(2 + 2);
}

Note: You could also write $e.process() in the macro's body, but then the macro might call an unrelated process method.

As already explained, you cannot expand differently depending on the type of an expr . But as a workaround, you can use the any module and try to downcast from the Any trait:

use std::any::Any;

macro_rules! attribute {
    ( $e:expr ) => {
        if let Some(f) = (&$e as &Any).downcast_ref::<f32>() {
            println!("`{}` is f32.", f);
        } else if let Some(f) = (&$e as &Any).downcast_ref::<f64>() {
            println!("`{}` is f64.", f);
        } else {
            println!("I dunno what is `{:?}` :(", $e);
        }
    };
}

fn main() {
    attribute!(0f32);
    attribute!(0f64);
    attribute!(0);
}

Displays:

`0` is f32.
`0` is f64.
I dunno what is `0` :(

While all the answers here are correct, I'd like to provide an answer more akin to your C++ version. Rust provides its own version of templates, generics, and they can be used in the same way you use templates. So, to define a struct and implement functions for certain types:

struct Attribute<T> {
    value: T,
}

impl Attribute<u32> {
    fn call(&self) {
        println!("{} is a u32", self.value);
    }
}

impl Attribute<f64> {
    fn call(&self) {
        println!("{} is a f64", self.value);
    }
}

impl Attribute<String> {
    fn call(&self) {
        println!("{} is a string", self.value);
    }
}

We'd use it like that:

fn main() {
    let a = Attribute{
        value: 5_u32
    };


    a.call();
}

Or simply like this:

Attribute{value: 6.5}.call()

Sadly, Rust doesn't provide () operator overloading in its stable version. You can still define a macro to do the job:

macro_rules! attribute {
    ( $e:expr ) => {
        Attribute{value: $e}.call(); 
    };
}

And use it as so:

attribute!("Hello World!".to_string());

I'd recommend using the trait based approach shown in this answer , as it doesn't use a struct, but a trait, which is considered better practice. This answer may still be helpful in many situations.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM