Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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

Tags:

macros

rust

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 */ }
}
like image 279
Arne Avatar asked Dec 11 '15 00:12

Arne


People also ask

How do macros work Rust?

Rust has excellent support for macros. Macros enable you to write code that writes other code, which is known as metaprogramming. Macros provide functionality similar to functions but without the runtime cost. There is some compile-time cost, however, since macros are expanded during compile time.

Can you use macros in Rust?

The most widely used form of macros in Rust is the declarative macro. These are also sometimes referred to as “macros by example,” “ macro_rules! macros,” or just plain “macros.” At their core, declarative macros allow you to write something similar to a Rust match expression.

What is derive macro Rust?

Derive macros These macros can create new items given the token stream of a struct, enum, or union. They can also define derive macro helper attributes. Custom derive macros are defined by a public function with the proc_macro_derive attribute and a signature of (TokenStream) -> TokenStream .


3 Answers

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.

like image 116
Francis Gagné Avatar answered Oct 23 '22 07:10

Francis Gagné


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` :( 
like image 32
Boiethios Avatar answered Oct 23 '22 05:10

Boiethios


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.

like image 21
Misty Avatar answered Oct 23 '22 07:10

Misty