I'm trying to write a Rust macro that allows me to make use of the field names and types of a struct declaration, but I still need to emit the struct.
I've got it working with optional attributes, visibility of the struct (thanks to The Little Book of Rust Macros), but can't figure out how to deal with the optional presence of pub in the individual fields.
So far I've got:
macro_rules! with_generic {
($(#[$struct_meta:meta])*
pub struct $name:ident { $($fname:ident : $ftype:ty), *}
) => {
with_generic![(pub) $(#[$struct_meta])* struct $name {$($fname: $ftype) ,*}];
};
($(#[$struct_meta:meta])*
struct $name:ident { $($fname:ident : $ftype:ty), *}
) => {
with_generic![() $(#[$struct_meta])* struct $name {$($fname: $ftype), *}];
};
(
($($vis:tt)*)
$(#[$struct_meta:meta])*
struct $name:ident { $($fname:ident : $ftype:ty), *}
) => {
// emit the struct here
$(#[$struct_meta])*
$($vis)* struct $name {
$($fname: $ftype,)*
}
// I work with fname and ftypes here
}
}
And it works with something like
with_generic! {
#[derive(PartialEq, Eq, Debug)]
pub struct Person {
first_name: String,
last_name: String
}
}
or
with_generic! {
#[derive(PartialEq, Eq, Debug)]
struct PrivatePerson {
first_name: String,
last_name: String
}
}
but doesn't work with
with_generic! {
#[derive(PartialEq, Eq, Debug)]
struct MixedPerson {
pub first_name: String,
last_name: String
}
}
I'd like to get some help on how to make the macro work with that last case. I feel like I might be missing something basic here, such as the type used for binding visibility. If there's a way to bind the whole struct tree while getting the field names and types, that would also be fine.
I'd also like to learn how to get it to work with structs that have lifetime parameters, but maybe that should be a separate question.
You can't. At least, not with a single, non-recursive rule. This is because Rust doesn't have a macro matcher for visibility.
The parse-macros crate contains a parse_struct! macro that shows the work necessary to completely parse a struct definition. Short version: you need to parse each field individually, with one rule for each of "has pub" and "doesn't have pub".
I'd also just note that there's another case your macro doesn't yet account for: attributes on the fields, which is needed for doc-comments on them to work.
Quite soon, macros 1.1 should be stabilised, which might provide an easier approach (assuming you can express your macro as a derivation, and don't care about older versions of Rust).
Since Rust 1.30, you can match visibility keywords with the vis specifier. A vis metavariable will match nothing if there is no visibility keyword to match, so you don't even need to use it inside $()*. This change makes with_generic vastly simpler:
macro_rules! with_generic {
($(#[$struct_meta:meta])*
$sv:vis struct $name:ident { $($fv:vis $fname:ident : $ftype:ty), *}
) => {
// emit the struct here
$(#[$struct_meta])*
$sv struct $name {
$($fv $fname: $ftype,)*
}
// do whatever else you need here
}
}
Rust 1.15 was officially released shortly after I asked this question and brings procedural macros (custom derive) support like #DK. has said.
Going forward, I think custom derives w/ syn and quote will be the standard way of doing this kind of thing and side-steps this issue completely since you no longer need to manually re-emit the struct.
Related
Given a struct Foo:
struct Foo<'a> {
info: &'a str
}
To create an array of Foos with different strings inside, I would like to have a macro, which could be used like:
assert_eq!(make_foo!("test"; 2), [Foo { info: "test 1" }, Foo { info: "test 2" }]);
What I am confused about specifically is how to iterate over the specific number of times as specified in the second argument.
Currently, to the best of my knowledge, it's impossible to create a macro which works exactly as you wish. It's only kind of possible. I'll explain:
First, there is no way to count with macros, so you can't do the "repeat this n-times" on your own. This is the reason why you can't generate the strings "test 1", "test 2" and so on. It's just not possible. You can, however, create an array of structs with only "test" as string by using the standard array initializer [val; n].
Second, in order to use the array initializer, the type inside the array has to be Copy. But in your case that's not a big problem, since your struct can just derive it.
So let's see, what we can do (playground):
#[derive(Clone, Copy, PartialEq, Debug)]
struct Foo<'a> {
info: &'a str
}
macro_rules! make_foo {
($info:expr; $num:expr) => {
[Foo { info: $info }; $num]
}
}
First, we need to derive a few traits for your struct:
Copy, see above
Clone is required by Copy
PartialEq and Debug are required by assert_eq!()
I think the macro itself is fairly easy to understand: it's just using the array initializer internally.
But how to get exactly the behavior asked in the question?
Don't use a macro and don't use fixed size arrays. A normal function and Vec<T> is probably fine. You could, of course, also write a compiler plugin, but those are unstable right now and it's probably not worth the hassle anyway.
Is it possible to build an enum inside a Rust macro using fields that are defined as macro parameters? I've tried this:
macro_rules! build {
($($case:ty),*) => { enum Test { $($case),* } };
}
fn main() {
build!{ Foo(i32), Bar(i32, i32) };
}
But it fails with error: expected ident, found 'Foo(i32)'
Note that if the fields are defined inside the enum, there is no problem:
macro_rules! build {
($($case:ty),*) => { enum Test { Foo(i32), Bar(i32, i32) } };
}
fn main() {
build!{ Foo(i32), Bar(i32, i32) };
}
It also works if my macro only accepts simple fields:
macro_rules! build {
($($case:ident),*) => { enum Test { $($case),* } };
}
fn main() {
build!{ Foo, Bar };
}
But I've been unable to get it to work in the general case.
It's absolutely possible, but you're conflating totally unrelated concepts.
Something like $case:ty does not mean $case is something which looks like a type, it means $case is literally a type. Enums are not made up of a sequence of types; they're made up of a sequence of variants which are an identifier followed (optionally) by a tuple structure body, a record structure body, or a tag value.
The parser doesn't care if the type you give it happens to coincidentally look like a valid variant, it's simply not expecting a type, and will refuse to parse one in that position.
What you need is to use something like $case:variant. Unfortunately for you, no such matcher exists. The only way to do something like this is to manually parse it using a recursive incremental parser and that is so out of scope of an SO question it's not funny. If you want to learn more, try the chapter on incremental TT munchers in the Little Book of Rust Macros as a starting point.
However, you don't appear to actually do anything with the cases. You're just blindly substituting them. In that case, you can just cheat and not bother with trying to match anything coherent:
macro_rules! build {
($($body:tt)*) => {
as_item! {
enum Test { $($body)* }
}
};
}
macro_rules! as_item {
($i:item) => { $i };
}
fn main() {
build!{ Foo, Bar };
}
(Incidentally, that as_item! thing is explained in the section on AST coercion (a.k.a. "the reparse trick").)
This just grabs everything provided as input to build!, and shoves it into the body of an enum without caring what it looks like.
If you were trying to do something meaningful with the variants, well, you're going to have to be more specific about what you're actually trying to accomplish, as the best advice of how to proceed varies wildly depending on the answer.
I am learning how to use Rust by making a little Pac-Man clone game in SFML (using RSFML) but I have ran into a problem with mapping the Key enum.
I have created this struct which has maps of keys tied to booleans, I used this in a previous C++ project so I am just trying to replicate it.
use sfml::window::keyboard::Key;
use std::collections::HashMap;
pub struct Input {
held_keys: HashMap<Key, bool>,
pressed_keys: HashMap<Key, bool>,
released_keys: HashMap<Key, bool>
}
I then received an error about Key being not hashable. I checked the library and the enum did not derive Hash to make it usable as a key. I looked around for advice about this but did not come with many answers; someone suggested to try and wrap the enum in a new struct type and derive Hash from there.
So I tried adding the following:
#[derive(Hash, Eq, PartialEq)]
struct HKey {
key: Key
}
pub struct Input {
held_keys: HashMap<HKey, bool>,
pressed_keys: HashMap<HKey, bool>,
released_keys: HashMap<HKey, bool>
}
But that still ended up with this error, as I am assuming all it does is mix in the hashable trait for each property in the struct.
the trait `core::hash::Hash` is not implemented for the type `sfml::window::keyboard::Key`
key: Key
^~~~~~~~
in this expansion of #[derive_Hash] (defined in src/input.rs)
help: run `rustc --explain E0277` to see a detailed explanation
note: required by `core::hash::Hash::hash`
I now am guessing I need to try and manually add the Hash trait implementation to the new HKey struct I made but I don't know how to generate a hash from an enum, as it seems it is not easy to turn it into an int. I ideally want do it safely, if Rust allows it. Does anyone have any advice on how to do this?
I am uploading my progress to GitHub, if you need a bigger picture.
The Key enum is a C-like enum (i.e. no variants have additional data), so we can use as to convert an enum of this type to an integer, e.g. u32. Then, we can defer to u32's implementation of Hash.
Here's a minimal example that doesn't use external libraries:
use std::hash::{Hash, Hasher};
#[derive(Copy, Clone)]
enum E {
A, B, C
}
struct NE(E);
impl Hash for NE {
fn hash<H>(&self, state: &mut H) where H: Hasher {
(self.0 as u32).hash(state)
}
}
E doesn't implement Hash. NE wraps an E and implements Hash by converting the enum to u32, then using u32's implementation of Hash.
I have a couple of macros to reduce boilerplate when defining certain tuple-structs of the form:
macro_rules! new_type (($name:ident, $bytes:expr) => (
pub struct $name(pub [u8; $bytes]);
// some common operations on $name
));
However, I would also like to document these new structs. The best thing would be if I could write my documentation right before my macro invocation.
/// A certain type
new_type!(CertainType, 42);
However, Rust won't generate documentation for CertainType when this happens.
Another (not as flexible) alternative would be to do something like:
macro_rules! new_type (($name:ident, $bytes:expr) => (
/// Some more generic documentation for $name
pub struct $name(pub [u8; $bytes]);
// some common operations on $name
));
However, when doing that the Rust macro system doesn't expand the token $name in the documentation comment. The only alternative left is to write very generic documentation in the macro, but that would lead to my library being a lot worse documented than it could be.
What are your recommendations for handling this? The best solution for me would be to be able to write specific documentation for each macro invocation, but if that's not possible I would be grateful for hints on how to expand tokens in documentation comments.
It is possible to capture doc comments in macro invocations. It is not widely-known, but Rust documentation is actually represented as a special kind of attribute on an item. For example:
/// Some documentation comment
pub fn function() {}
// is equivalent to
#[doc="Some documentation comment"]
pub fn function() {}
And it is possible to capture attributes in macros. There are already several macros which use this ability, the most used probably being bitflags!:
macro_rules! bitflags {
(
$(#[$outer:meta])*
pub struct $BitFlags:ident: $T:ty {
$(
$(#[$inner:ident $($args:tt)*])*
const $Flag:ident = $value:expr;
)+
}
) => { /* ... */ };
// ...
}
Note the $(#[$outer:meta])* and $(#[$inner:meta])* parts of the pattern. These capture all attributes placed before the respective item in the pattern. If you write a doc comment there, it will be converted to the doc attribute and will be passed to rustdoc, as usual.
The following is an example from the quick_error crate which also uses this approach:
quick_error! {
#[derive(Debug)]
pub enum SomeError {
/// IO Error
Io(err: io::Error) {}
/// Arbitrary system error
Sys(errno: nix::Errno) {}
}
}
It does work — here is an example of the structure generated by quick_error macro, and here is its definition.
in my verification environment we work with vr_ad UVM package, where there is a general struct for a register vr_ad_reg which has been extended with different type for every register in the environment, etc:
reg_def TIMER_LOAD_0 TIMER 20'h00010 {
reg_fld timer_load : uint : RW : 0xffff;
}:
The vr_ad_reg has predefined function post_access(), which I would like to extend for every register type that starts with the word 'TIMER'. Is there a way to do it? For example:
extend TIMER_* vr_ad_reg { //The intention here to extend the vr_ad_reg for all types that starts with the word TIMER
post_access() is also {
var some_var : uint;
};
}
Thank you for your help
There's no built in construct to extend multiple sub-types. What you can do however is use a macro based solution. Team Specman had a blog post on this topic: http://www.cadence.com/Community/blogs/fv/archive/2009/10/20/extending-multiple-when-subtypes-simultaneously.aspx
They created a define as computed macro that takes multiple sub-types and extends those:
define <multi_when'statement> "extend \[<detr'name>,...\] <base'type> (<MEMBERS {<struct_member>;...})" as computed {
for each in <detr'names> do {
result = appendf("%s extend %s %s %s;",result,it,<base'type>,<MEMBERS>);
};
};
You can then use like so:
extend [ TIMER_LOAD_0, TIMER_LOAD_1, TIMER_LOAD_2 ] vr_ad_reg {
post_access() is also {
// ...
};
};
If you have a lot of registers that match your expression or you don't know the exact name beforehand, you might want to consider using a run-time solution:
extend vr_reg {
post_access() is also {
var some_var: uint;
if str_match(kind.as_a(string), "/^TIMER_*/") {
... // do stuff for the TIMER_* registers
};
};
};