My goal is to write a macro expand! such that:
struct A;
struct B;
struct Mut<T>;
expand!() => ()
expand!(A) => (A,)
expand!(mut A) => (Mut<A>,)
expand!(A, mut B) => (A, Mut<B>,)
// etc
[Edit] added trailing comma for consistent tuple syntax.
I wrote this macro so far:
macro_rules! to_type {
( $ty:ty ) => { $ty };
( mut $ty:ty ) => { Mut<$ty> };
}
macro_rules! expand {
( $( $(mut)? $ty:ty ),* ) => {
(
$( to_type!($ty) ),*
,)
};
}
What I'm struggling with, is capturing the mut token. How can I assign it to a variable and reuse it in the macro body? Is it possible to work on more than 1 token at a time?
Something like this?
macro_rules! expand {
(#phase2($($ty_final:ty),*),) => {
($($ty_final,)*)
};
(#phase2($($ty_final:ty),*), mut $ty:ty, $($rest:tt)*) => {
expand!(#phase2($($ty_final,)* Mut::<$ty>), $($rest)*)
};
(#phase2($($ty_final:ty),*), $ty:ty, $($rest:tt)*) => {
expand!(#phase2($($ty_final,)* $ty), $($rest)*)
};
($($t:tt)*) => {
expand!(#phase2(), $($t)*)
};
}
struct A;
struct B;
struct Mut<T>(std::marker::PhantomData<T>);
fn main() {
#[allow(unused_parens)]
let _: expand!() = ();
#[allow(unused_parens)]
let _: expand!(A,) = (A,);
#[allow(unused_parens)]
let _: expand!(mut B,) = (Mut::<B>(Default::default()),);
#[allow(unused_parens)]
let _: expand!(A, mut B,) = (A, Mut::<B>(Default::default()));
}
Related
I'm trying to adapt a macro that takes a variable number of type arguments, to a macro that takes a (possibly aliased) tuple type as its last argument:
macro_rules! serialize_individually2 {
($ecs:expr, $ser:expr, $data:expr, ($( $type:ty),*)) => {
$(
SerializeComponents::<NoError, SimpleMarker<SerializeMe>>::serialize(
&( $ecs.read_storage::<$type>(), ),
&$data.0,
&$data.1,
&mut $ser,
)
.unwrap();
)*
};
}
This works on non-aliased tuple types:
serialize_individually2!(ecs, serializer, data, (AreaOfEffect, BlocksTile));
But not on an aliased type:
type TupleComps = (AreaOfEffect, BlocksTile);
serialize_individually2!(ecs, serializer, data, TupleComps);
Is it possible to pattern match and de-alias TupleComps in a Rust macro?
Original question for reference (more generic, not as well thought out): How to store a list of types using a Rust macro?
This is not possible. However, you can create a trait that does the operation for tuples:
trait TupleReadStorage {
fn read_storage(ecs: &mut Ecs) -> Self;
}
macro_rules! impl_TupleReadStorage {
( $first_item_type:ident $(, $rest_item_types:ident)* ) => {
impl< $first_item_type, $($rest_item_types,)* > TupleReadStorage for ( $first_item_type, $($rest_item_types,)* ) {
fn read_storage(ecs: &mut Ecs) -> Self {
(
ecs.read_storage::<$first_item_type>(),
$( ecs.read_storage::<$rest_item_types>(), )*
)
}
}
impl_TupleReadStorage!( $($rest_item_types),* );
};
() => {
impl TupleReadStorage for () {
fn read_storage(_ecs: &mut Ecs) -> Self { () }
}
};
}
impl_TupleReadStorage!(A, B, C, D, E, F, G, H, I, J);
Then, use it in the macro:
macro_rules! serialize_individually2 {
($ecs:expr, $ser:expr, $data:expr, $type:ty) => {
$(
SerializeComponents::<NoError, SimpleMarker<SerializeMe>>::serialize(
&<$type as TupleReadStorage>::read_storage(&mut $ecs),
&$data.0,
&$data.1,
&mut $ser,
)
.unwrap();
)*
};
}
Let's say I have
macro_rules! tipey {
(Vec<$pt: ty>) => {2};
($pt: ty) => {1};
}
macro_rules! structy {
(struct $i: ident { $($p: ident : $(Vec<)? $pt: ty $(>)?,)+ }) => {
const v: &[usize] = &[ $(tipey!($pt)),+ ];
};
}
structy!(
struct ContentDetails {
pattern: String,
fields: Vec<String>,
}
);
I want to somehow be able to disambiguate the type and know whether it is a Vec<> or a simple type. I'm only dealing with Vecs so no need to expand if not possible.
The issue I have is that if I match Vec<bool> against just $t: ty then I cannot split it up later to see if the $t was Vec<> or not but if I try to collect multiple tts or something else then parsing the list of properties breaks. I really want to avoid having to use proc macros
This is going to be very unreliable for general types, and generic Rust syntax. But if you have a very narrow use-case then you can fix up your code something like this:
macro_rules! tipey {
(Vec<$pt: tt>) => { 2 };
($pt: tt) => { 1 };
}
macro_rules! structy {
(struct $i: ident {
$($p: ident: $pt: tt $(<$gt: tt>)?),+
$(,)?
}) => {
const v: &[usize] = &[ $(tipey!( $pt $(<$gt>)?)),+ ];
};
}
This macro compiles when invoked:
macro_rules! remote_optional {
($remote:ident with=$def:ident $def_str:expr) => {
impl $def {
fn deserialize_option<'de, D>(deserializer: D) -> Result<Option<$remote>, D::Error>
where
D: Deserializer<'de>,
{
#[derive(Deserialize)]
struct Wrapper(#[serde(with = $def_str)] $remote);
let v: Option<Wrapper> = Option::deserialize(deserializer)?;
Ok(v.map(|Wrapper(a)| a))
}
}
}
}
This one doesn't:
macro_rules! remote_optional {
($remote:ident with=$def:ident) => {
impl $def {
fn deserialize_option<'de, D>(deserializer: D) -> Result<Option<$remote>, D::Error>
where
D: Deserializer<'de>,
{
#[derive(Deserialize)]
struct Wrapper(#[serde(with = stringify!($def))] $remote);
let v: Option<Wrapper> = Option::deserialize(deserializer)?;
Ok(v.map(|Wrapper(a)| a))
}
}
}
}
This is because stringify!($def) is passed into the #[serde(...)] attribute unevaluated.
Is there any practical workaround?
Could the macro of two arguments forward to the macro of three arguments, expanding the def identifier?
macro_rules! remote_optional {
// The one that doesn't work (two arguments)
// forwards to the one that *does* work, expanding the
// string.
($remote:ident with=$def:ident) => {
remote_optional!($remote, with=$def, stringify!($def));
};
// The macro that *does* work
($remote:ident with=$def:ident $def_str:expr) => {
impl $def {
fn deserialize_option<'de, D>(deserializer: D) -> Result<Option<$remote>, D::Error>
where
D: Deserializer<'de>,
{
#[derive(Deserialize)]
struct Wrapper(#[serde(with = $def_str)] $remote);
let v: Option<Wrapper> = Option::deserialize(deserializer)?;
Ok(v.map(|Wrapper(a)| a))
}
}
};
}
We could also consider making the macro of three arguments an implementation detail.
Little, isolated proof-of-concept:
macro_rules! my_macro {
($x:expr, $y:expr) => {
my_macro!($x, $y, stringify!($x + $y));
};
($x:expr, $y:expr, $msg:expr) => {
println!("{} + {} = {}", $x, $y, $msg);
};
}
fn main() {
my_macro!(3, 2); // 3 + 2 = 3 + 2
}
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'm not really sure how to phrase this, so the question title is pretty rubbish, but here's what I'm trying to do:
I can write this macro:
macro_rules! op(
( $v1:ident && $v2:ident ) => { Op::And($v1, $v2) };
( $v1:ident || $v2:ident ) => { Op::Or($v1, $v2) };
);
Which I can use like this:
let _ = op!(Expr || Expr);
let _ = op!(Expr && Expr);
What I want to do is to write an arbitrary sequence of tokens like this:
let _ = op!(Expr || Expr || Expr && Expr || Expr);
Which resolves into a Vec of tokens, like:
vec!(T::Expr(e1), T::Or, T::Expr(e2), T::Or, ...)
I can write a vec! like macro:
macro_rules! query(
( $( $x:expr ),* ) => {
{
let mut temp_vec = Vec::new();
$(temp_vec.push($x);)*
temp_vec
}
};
);
...but I can't see how to convert the arbitrary symbols (eg. &&) into tokens as the macro runs.
Is this possible somehow?
playpen link: http://is.gd/I9F5YV
It seems that it's impossible to capture arbitrary symbols matches during macroexpand: as the language reference says, "valid designators are item, block, stmt, pat, expr, ty (type), ident, path, tt". So the best I could suggest is to use "ident"-valid tokens, like "and"/"or" instead of "&&"/"||", for example:
macro_rules! query_op(
( and ) => { "T::And" };
( or ) => { "T::Or" };
( $e:ident ) => { concat!("T::Expr(", stringify!($e), ")") };
);
macro_rules! query(
( $( $x:ident )* ) => {
{
let mut temp_vec = Vec::new();
$(temp_vec.push(query_op!($x));)*
temp_vec
}
};
);
fn main() {
let q = query!(Expr1 or Expr2 and Expr3 or Expr4);
println!("{:?}", q);
}
Outputs:
["T::Expr(Expr1)", "T::Or", "T::Expr(Expr2)", "T::And", "T::Expr(Expr3)", "T::Or", "T::Expr(Expr4)"]