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 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.
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.
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 .
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With