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.
Related
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 {}
}
}
Is there any way to pattern match on an &Box<T> without using as_ref? I'm using stable Rust, so the answer can't involve box_patterns.
Essentially, I have code like this:
enum Foo {
Thing(Box<ThingKind>),
}
enum ThingKind {
One(Foo),
Two(Foo, Foo),
}
fn my_function(foo: &Foo) {
match foo {
Foo::Thing(ThingKind::One(_inner)) => { /* ... */ }
Foo::Thing(ThingKind::Two(_left, _right)) => { /* ... */ }
}
}
but the compiler doesn't like that:
error[E0308]: mismatched types
--> src/lib.rs:12:20
|
11 | match foo {
| --- this expression has type `&Foo`
12 | Foo::Thing(ThingKind::One(_inner)) => { /* ... */ }
| ^^^^^^^^^^^^^^^^^^^^^^ expected struct `Box`, found enum `ThingKind`
|
= note: expected struct `Box<ThingKind>`
found enum `ThingKind`
error[E0308]: mismatched types
--> src/lib.rs:13:20
|
11 | match foo {
| --- this expression has type `&Foo`
12 | Foo::Thing(ThingKind::One(_inner)) => { /* ... */ }
13 | Foo::Thing(ThingKind::Two(_left, _right)) => { /* ... */ }
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ expected struct `Box`, found enum `ThingKind`
|
= note: expected struct `Box<ThingKind>`
found enum `ThingKind`
error: aborting due to 2 previous errors
Then I tried this instead:
enum Foo {
Thing(Box<ThingKind>),
}
enum ThingKind {
One(Foo),
Two(Foo, Foo),
}
fn my_function(foo: &Foo) {
match foo {
Foo::Thing(kind) => match kind {
ThingKind::One(_inner) => { /* ... */ }
ThingKind::Two(_left, _right) => { /* ... */ }
},
}
}
but the compiler gives a similar error on that one as well.
Then I tried using kind.as_ref() in the inner match, which does work, but it's kind of annoying to have to add that every time. So, is there any way to do it without as_ref?
This looks like a job for std::ops::Deref.
enum Foo {
Thing(Box<ThingKind>),
}
enum ThingKind {
One(Foo),
Two(Foo, Foo),
}
impl std::ops::Deref for Foo {
type Target = ThingKind;
fn deref(&self) -> &Self::Target {
match self {
Foo::Thing(b) => &*b,
}
}
}
fn my_function(foo: &Foo) {
match &**foo {
ThingKind::One(_inner) => { /* ... */ }
ThingKind::Two(_left, _right) => { /* ... */ }
}
}
I have this code, but it gives me a compile error. How to create an instance of the Test struct in order to fix the error?
enum Fruit {
Apple(String),
Banana(String),
}
struct Test {
A: Box<Fruit>,
}
fn main() {
let name = String::from("apple");
let test = Test {
A: Box::<Fruit::Apple("apple".to_string())>,
};
// match test {
// Box
// }
}
error: unmatched angle bracket
--> src/main.rs:13:17
|
13 | A: Box::<Fruit::Apple("apple".to_string())>,
| ^ help: remove extra angle bracket
error: expected type, found `"apple"`
--> src/main.rs:13:31
|
12 | let test = Test {
| ---- while parsing this struct
13 | A: Box::<Fruit::Apple("apple".to_string())>,
| ^^^^^^^ expected type
I think this may help you.
#[derive(Debug)]
enum Fruit {
Apple(String),
Banana(String),
}
#[derive(Debug)]
struct Test {
A: Box<Fruit>,
}
fn main() {
let test = Test {
A: Box::new(Fruit::Apple("apple".to_string())),
};
println!("{:?}", test)
}
Box<T> is the generic representation of a Box containing T. You use this when specifying the type of a value, such as a struct field or return type:
struct Test {
A: Box<Fruit>,
}
fn test() -> Box<Fruit> {}
To actually allocate memory on the heap and create a Box that stores T, you have to use the Box::new() function:
let test = Test {
A: Box::new(Fruit::Apple("apple".to_string())),
};
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
As per the Serde specification, an Object / Map<String, Value> is a Value:
pub enum Value {
Null,
Bool(bool),
Number(Number),
String(String),
Array(Vec<Value>),
Object(Map<String, Value>),
}
Yet when I compile this code:
extern crate serde;
#[macro_use]
extern crate serde_json;
#[derive(Debug)]
struct Wrapper {
ok: bool,
data: Option<serde_json::Value>,
}
impl Wrapper {
fn ok() -> Wrapper {
Wrapper {
ok: true,
data: None,
}
}
pub fn data(&mut self, data: serde_json::Value) -> &mut Wrapper {
self.data = Some(data);
self
}
pub fn finalize(self) -> Wrapper {
self
}
}
trait IsValidWrapper {
fn is_valid_wrapper(&self) -> bool;
}
impl IsValidWrapper for serde_json::Map<std::string::String, serde_json::Value> {
fn is_valid_wrapper(&self) -> bool {
self["ok"].as_bool().unwrap_or(false)
}
}
fn main() {
let json = json!({
"name": "John Doe",
"age": 43,
"phones": [
"+44 1234567",
"+44 2345678"
]
});
let converted_json: Wrapper = json
.as_object()
.map_or_else(
|| Err(json),
|obj| {
if obj.is_valid_wrapper() {
Ok(Wrapper::ok().data(obj["data"].clone()).finalize())
} else {
Err(*obj as serde_json::Value)
}
},
)
.unwrap_or_else(|data| Wrapper::ok().data(data.clone()).finalize());
println!(
"org json = {:?} => converted json = {:?}",
json, converted_json
);
}
I get this error:
error[E0605]: non-primitive cast: `serde_json::Map<std::string::String, serde_json::Value>` as `serde_json::Value`
--> src/main.rs:60:25
|
60 | Err(*obj as serde_json::Value)
| ^^^^^^^^^^^^^^^^^^^^^^^^^
|
= note: an `as` expression can only be used to convert between primitive types. Consider using the `From` trait
Is there a way to downcast a Map into a Value?
an Object / Map<String, Value> is a Value
No, it is not. Value is a type. Map<String, Value> is a type. Value::Object is an enum variant, which is not a separate type. In this case, Value::Object holds another value of type Map<String, Value>. You have to wrap the value in the variant to convert the type:
Err(serde_json::Value::Object(obj))
This will lead you to the problem:
error[E0308]: mismatched types
--> src/main.rs:57:55
|
57 | Err(serde_json::Value::Object(obj))
| ^^^ expected struct `serde_json::Map`, found reference
|
= note: expected type `serde_json::Map<std::string::String, serde_json::Value>`
found type `&serde_json::Map<std::string::String, serde_json::Value>`
as_object returns a reference to the contained object (if it's present), not the value itself. You will need to match on it for now:
let converted_json = match json {
serde_json::Value::Object(obj) => {}
_ => {}
};
Something like this:
let converted_json = match json {
serde_json::Value::Object(obj) => {
if obj.is_valid_wrapper() {
let mut w = Wrapper::ok();
w.data(obj["data"].clone());
Ok(w.finalize())
} else {
Err(serde_json::Value::Object(obj))
}
}
other => Err(other),
};