I'd like to assume a given type implements some trait (e.g. Default
) with a method (e.g. default()
). I want to call that method and store its value into a local variable. Here is a general idea of it:
macro_rules! get_default {
( $x:ty = $alias:ident ) => {
let $alias = $x::default();
};
}
fn main() {
// get_default!(i32 = z);
// println!("get_default! {:?} ", z);
println!("i32 default {:?} ", i32::default());
}
Playground link.
When I try that I get an error:
error: expected expression, found `i32`
--> <anon>:3:22
|>
3 |> let $alias = $x::default();
|> ^^
I understand it's because it expects an expression, but I want to limit input to types only. Is there a way to turn $x
from ty
to expr
, or a way to call a method on a type (even if it's potentially missing).
macro_rules allows users to define syntax extension in a declarative way. We call such extensions "macros by example" or simply "macros". Each macro by example has a name, and one or more rules.
I'm reading a book about Rust, and start playing with Rust macros. All metavariable types are explained there and have examples, except the last one – tt . According to the book, it is a “a single token tree”.
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.
You were almost there. You can hint the expected default type to the compiler and then just use the universal function call syntax:
macro_rules! get_default {
( $x:ty = $alias:ident ) => {
let $alias = <$x as Default>::default();
};
}
fn main() {
get_default!(i32 = z);
println!("get_default! {:?} ", z);
println!("i32 default {:?} ", i32::default());
}
(Playground link)
The key bit is this:
let $alias = <$x as Default>::default();
This casts $x
to the Default
trait and then invokes the default()
method, as you needed.
You can also use a shorthand when you don't need to disambiguate between traits:
let $alias = <$x>::default();
(Playground link)
Using UFCS as shown above, you can disambiguate between traits that implement the same methods. This is the 'angle-bracket form' which is useful if the default()
method is implemented in two traits.
In this specific scenario, you can also use UFCS more specifically, like so:
let $alias: $x = Default::default();
That alone provides enough information for Rust to infer the correct impl
.
(Playground link)
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