I'm trying to make a macro that would let me iterate through a list of types to reduce trait impl boilerplate. (I'm currently using a different macro based solution, but this seems more readable, if it's possible without adding a dependency.)
This is the syntax I am aiming for:
trait MyTrait {}
tfor! {
for Ty in [i32, u32] {
impl MyTrait for Ty {}
}
}
My attempt:
macro_rules! tfor {
(for $ty:ident in [$($typ:ident),*] $tt:tt) => {
$(
type $ty = $typ;
tfor! { #extract $tt }
)*
};
(#extract { $($tt:tt)* }) => {
$($tt)*
};
}
This generates an error as both the iterations define a type named Ty in the same scope:
|
4 | type $ty = $typ;
| ^^^^^^^^^^^^^^^^
| |
| `Ty` redefined here
| previous definition of the type `Ty` here
Playground
Is there a way around this? Can I somehow generate a random identifier in place of Ty so that this compiles, without using a proc macro or a dependency?
You can scope the trait implementation inside a const declaration. That way you can re-use the Ty name without causing conflicts.
macro_rules! tfor {
(for $ty:ident in [$($typ:ident),*] $tt:tt) => {
$(
const _: () = {
type $ty = $typ;
tfor! { #extract $tt }
};
)*
};
(#extract { $($tt:tt)* }) => {
$($tt)*
};
}
trait MyTrait {}
tfor! {
for Ty in [i32, u32] {
impl MyTrait for Ty {}
}
}
Related
I want to create a macro which on the received model generates structures with all its fields and implements trait Default, but faced such problem.
My macro struct_field! is recognized as a field, not a macro inside the model!.
Мaybe someone knows how to solve this problem
macro_rules! struct_field {
($fild_name:ident: string = $def_val:expr,) => {
pub $fild_name: Arc<RwLock<String>>
};
($fild_name:ident: string = $def_val:expr, $($fields:tt)+) => {
pub $fild_name: Arc<RwLock<String>>
struct_field!($($fields)+)
};
($fild_name:ident: bool = $def_val:expr,) => {
pub enable_kernel_settings: Arc<AtomicBool>,
};
($fild_name:ident: bool = $def_val:expr, $($fields:tt)+) => {
pub $fild_name: Arc<AtomicBool>,
struct_field!($($fields)+)
};
}
macro_rules! model {
($model:ident { $($inner_model:ident : $inner_model_name:ident { $($fields:tt)+ },)+ }) => {
pub struct $model {$(
$inner_model: $inner_model_name,
)+}
$(pub struct $inner_model_name {
struct_field!($($fields)+) // <-- error
})+
};
}
error:
error: expected `:`, found `!`
--> .../mod.rs:43:29
|
43 | struct_field!($($fields)+)
| ^ expected `:`
...
48 | / model! {
49 | | MainStruct {
50 | | inner_struct1: InnerStruct1 {
51 | | feild1: bool = true,
... |
58 | | }
59 | | }
| |_- in this macro invocation
example:
model! {
MainStruct {
inner_struct1: InnerStruct1 {
feild1: bool = true,
},
inner_struct2: InnerStruct2 {
feild1: bool = false,
feild2: bool = false,
feild3: string = "ignore",
},
}
}
expected result:
pub struct MainStruct {
inner_struct1: InnerStruct1,
inner_struct2: InnerStruct2,
}
pub struct InnerStruct1 {
feild1: Arc<AtomicBool>,
}
pub struct InnerStruct2 {
feild1: Arc<AtomicBool>,
feild2: Arc<AtomicBool>,
feild3: Arc<RwLock<String>>
}
you can test this with "cargo expand"
and you should be enabled rust-nightly
Contrary to C-like macros, Rust macros can only operate on valid AST nodes, that is, they can only be passed something that can be tokenized and, if needed, parsed to some extent, and can only "return" a valid AST node. This, in particular, rules out the invocation of a macro that would be extended as a field definition, because something like a: bool is not a valid AST node.
It is still possible to make a macro to do what you want to do, using function-style procedural macros, but it may become somehow more convoluted.
The following code works:
pub struct Bar {
pub name: String
}
macro_rules! printme {
($myclass: ident) => {
let t = $myclass { name: "abc".to_owned() };
println!("{}", t.name);
}
}
fn main() {
printme!(Bar);
}
However, if Bar is within a module, it won't work, error is no rules expected the token :::
mod foo {
pub struct Bar {
pub name: String
}
}
macro_rules! printme {
($myclass: ident) => {
let t = $myclass { name: "abc".to_owned() };
println!("{}", t.name);
}
}
fn main() {
printme!(foo::Bar); // not allowed
}
It only works if I use an alias:
fn main() {
use foo::Bar as no_colon;
printme!(no_colon);
}
Is there a way to make it work with the colon, without the use alias?
When you write ($myclass: ident) you are saying that the user must write an identifier in that place of the macro invocation. And as you noted, Bar is an identifier, but foo::Bar is not: syntactically, this kind of list-of-identifiers-separated-by-double-colon is called a path.
You can write ($myclass: path), or if you want to limit that to existing types then you can write ($myclass: ty), as #phimuemue's answer suggested. But if you do this, it will fail when trying to use that type to build the object. That is because of how the parser work: it must parse the path and the { in the same token tree, but having the path or ty has broken the link with the {. Since this is just a parser limitation, not a semantic one, you can use a local alias as a workaround, as the other answer suggests.
However, I would suggest to use a trait based solution if possible. I consider that to me much more idiomatic:
trait Nameable {
fn new(name: &str) -> Self;
}
mod foo {
pub struct Bar {
pub name: String
}
impl super::Nameable for Bar {
fn new(name: &str) -> Bar {
Bar {
name: name.to_string()
}
}
}
}
macro_rules! printme {
($myclass: ty) => {
let t = <$myclass as Nameable>::new("abc");
println!("{}", t.name);
}
}
fn main() {
printme!( foo::Bar );
}
Or you can take out the ultimate tool of Rust macros: the list-of-token-trees, that can parse almost anything:
macro_rules! printme {
($($myclass: tt)*) => {
let t = $($myclass)* { name: "abc".to_string() };
println!("{}", t.name);
}
}
When you invoke this macro with printme!(foo::Bar) it will actually parse as a list of three token-trees: foo, :: and Bar, and then your building of the object will just work.
The drawback (or advantage) of this method is that it will eat up all your tokens, no matter what you write into the macro, and if it fails it will emit a weird error message from inside your macro, instead of saying that your token is not valid in this macro invocation.
For example, writing printme!( foo::Bar {} ) with my trait-based macro gives the most helpful error:
error: no rules expected the token `{`
--> src/main.rs:27:24
|
19 | macro_rules! printme {
| -------------------- when calling this macro
...
27 | printme!( foo::Bar {} );
| ^ no rules expected this token in macro call
While writing the same code with the token-tree-list macro produces a few not so helpful messages:
warning: expected `;`, found `{`
--> src/main.rs:21:30
|
21 | let t = $($myclass)* { name: "abc".to_string() };
| ^
...
27 | printme!( foo::Bar {} );
| ------------------------ in this macro invocation
|
= note: this was erroneously allowed and will become a hard error in a future release
error: expected type, found `"abc"`
--> src/main.rs:21:38
|
21 | let t = $($myclass)* { name: "abc".to_string() };
| - ^^^^^ expected type
| |
| tried to parse a type due to this
...
27 | printme!( foo::Bar {} );
| ------------------------ in this macro invocation
error[E0063]: missing field `name` in initializer of `foo::Bar`
--> src/main.rs:27:15
|
27 | printme!( foo::Bar {} );
| ^^^^^^^^ missing `name`
With a little trickery you can make it work:
mod foo {
pub struct Bar {
pub name: String
}
}
macro_rules! printme {
($myclass: ty) => {
type LocalT = $myclass;
let t = LocalT { name: "abc".to_owned() };
println!("{}", t.name);
}
}
fn main() {
printme!(foo::Bar);
}
accept ty (type) instead of ident (identifier)
I do not know why, but I couldn't get it working without LocalT
Is it possible to write a macro that defines an enum which wraps an arbitrary number of (distinct) input types?
I'd like to do a kind of type-level match.
type_switch!(i32 => println!("integer"), f32 => println!("float"), Foo => println!("foo"))
This would expand to:
{
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"),
}
}
so that I can write like
let switch = type_switch!(i32 => println!("integer"), f32 => println!("float"), Foo => println!("foo"));
switch(32.into()); // prints "integer"
switch(24.0.into()); // prints "float"
Define a trait within your macro and implement it for each type:
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
}}
}
Notice that the first time you call the function the compiler will bind the type so that subsequent calls must be the same type:
struct Foo;
fn main() {
let s = type_switch! {
i32 => { println!("i32"); },
f32 => { println!("f32"); },
Foo => { println!("Foo"); }
};
s(0);
s(Foo); // Error!
}
If you need to be able to call it with different types, this can be fixed (at a small cost) by using a trait object for dynamic dispatch:
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);
}
Also notice that you have to pass references instead of values.
It can make sense to write wrapper types as you have proposed, but only if the type is needed in larger parts of your code.
Your specific example would define a new enum every time you use the macro, move the value into the new enum and then immediately throw it away.
That's not a idiomatic approach and if that is indeed your imagined use I'd recommend looking for different options.
That said, I have used wrapper types on a number of occasions.
Something like this will work for declaring a wrapper:
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());
}
You can also extend this to add additional methods or trait impls that you need.
I've done similar things quite often.
I am trying to create a macro which initializes a struct T using P::Child from struct P where P: Parent<Child = T>.
macro_rules! init {
($t:tt, { $( $k:ident => $v:expr ),* }) => {
<$t as Parent>::Child {
$( $k: $v ),*
}
};
}
This macro passes the props as a map which is passed onto the struct's given constructor. Expanded, it would look like this:
#[derive(Debug)]
struct Apple {
a: i32
}
trait Parent {
type Child;
}
struct Mango;
impl Parent for Mango {
type Child = Apple;
}
fn main() {
let a = <Mango as Parent>::Child {
a: 4
};
println!("{:?}", a);
}
Compiling this has the error:
error: expected one of `.`, `::`, `;`, `?`, or an operator, found `{`
--> src/main.rs:25:38
|
25 | let a = <Mango as Parent>::Child {
| ^ expected one of `.`, `::`, `;`, `?`, or an operator here
I have created a macro to initialize a struct in a similar way but I am not able to do it with associated types. I think the compiler does not support it for some reason. Even so, I want to create a macro with such an API.
How could I approach this problem?
Link to Playground
Like so:
macro_rules! init {
($t:ty, { $( $k:ident => $v:expr ),* }) => {
{
type T = <$t as Parent>::Child;
T {
$( $k: $v ),*
}
}
};
}
My assumption would be that this is a limitation of the parser, not of the compiler's type analysis.
I don't think it is possible to initialize an associated type.
What you could do is perhaps have the Child: K with K being a trait having some known constructor (::new etc ...), Then call that constructor instead.
Alternatively you can probably initialize a T directly instead of Child.
Clippy warns about code like this:
fn func<T>(data: &Option<T>) {
if let &Some(ref value) = data {}
}
warning: you don't need to add `&` to all patterns
--> src/main.rs:2:5
|
2 | if let &Some(ref value) = data {}
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
= note: #[warn(match_ref_pats)] on by default
= help: for further information visit https://rust-lang-nursery.github.io/rust-clippy/v0.0.210/index.html#match_ref_pats
help: instead of prefixing all patterns with `&`, you can dereference the expression
|
2 | if let Some(ref value) = *data {}
| ^^^^^^^^^^^^^^^ ^^^^^
Are these constructions the same from compiler point of view:
if let &Some(ref value) = data {
if let Some(ref value) = *data {
If so, what's the point in the Clippy message, just to use uniform style?
Yes, these are the same to the compiler. In this case, there's not much benefit. The real benefit comes from the match equivalent:
fn func(data: &Foo) {
match data {
&Foo::One => {}
&Foo::Two => {}
&Foo::Three => {}
}
}
Here, you only have to place a single dereference, not 3 references in the patterns:
fn func(data: &Foo) {
match *data {
Foo::One => {}
Foo::Two => {}
Foo::Three => {}
}
}
And since Rust 1.26, you don't even have to dereference the expression being matched on:
fn func(data: &Foo) {
match data {
Foo::One => {}
Foo::Two => {}
Foo::Three => {}
}
}
That's why it's the idiomatic choice.
The if let concept is just an extension from this.
You can't always dereference the value. If you tried to do the same thing for a pair of items:
fn func(data: &Foo, data2: &Foo) {
match (*data, *data2) {
(Foo::One, _) => {}
(Foo::Two, _) => {}
(Foo::Three, _) => {}
}
}
You get the error
error[E0507]: cannot move out of borrowed content
--> src/main.rs:8:12
|
8 | match (*data, *data2) {
| ^^^^^ cannot move out of borrowed content
In this case, you can use the reference form:
fn func(data: &Foo, data2: &Foo) {
match (data, data2) {
(&Foo::One, _) => {}
(&Foo::Two, _) => {}
(&Foo::Three, _) => {}
}
}
Or, since Rust 1.26, perform some implicit references:
fn func(data: &Foo, data2: &Foo) {
match (data, data2) {
(Foo::One, x) => {}
(Foo::Two, _) => {}
(Foo::Three, _) => {}
}
}