I try to define macro that will be used to define a bunch of constants and one mapping. I suppose that the usage will be like this:
options_filter_consts!(
MEAL_TYPE => "meal_type" // filter_id
options { // filter options (const_name => const_value)
MEAL_NO => "meal_no"
BREAKFAST => "breakfast"
FULLBOARD => "fullboard"
HALFBOARD => "halfboard"
}
);
and then it will produce the following code:
// Filter ID
const MEAL_TYPE:&str = "meal_type";
// Filter Options
const NO_MEAL :&str = "no_meal";
const BREAKFAST:&str = "breakfast";
const FULLBOARD:&str = "fullboard";
const HALFBOARD:&str = "halfboard";
// Option indexes
const NO_MEAL_IDX :usize = 0;
const BREAKFAST_IDX:usize = 1;
const FULLBOARD_IDX:usize = 2;
const HALFBOARD_IDX:usize = 3;
// Mapping indexes -> options
// Naming: <filter_id> + _MAPPING
const MEAL_TYPE_MAPPING:[&str; 4] = [
/* NO_MEAL_IDX: */ NO_MEAL,
/* BREAKFAST_IDX: */ BREAKFAST,
/* FULLBOARD_IDX: */ FULLBOARD,
/* HALFBOARD_IDX: */ HALFBOARD,
];
These constants will be used in main function, e.g.:
fn main() {
println!("filter_id: {}", MEAL_TYPE);
println!("mapping_len: {}", MEAL_TYPE_MAPPING.len());
println!("mapping_item_value: {}", MEAL_TYPE_MAPPING[MEAL_NO_IDX]);
println!("item_value: {}", BREAKFAST);
}
I started writing macro:
macro_rules! options_filter_consts {
($filt_const:ident => $filt_value:expr, options { $($name:ident => $value:expr)+ }) => {
const $filt_const: &str = $filt_value;
$(
const $name : &str = $value;
)+
const IOTA:usize = 0; // how to increment inside marco ?
$(
paste! {
const [<$name _IDX>]:usize = IOTA;
}
)+
};
}
But I have errors such as I cannot increment IOTA constant values and get stuck on it.
Currently, Rust macros do not support any code execution inside the macro. The macro language is based on pattern matching and variable substitution and only evaluates macros.
In such a situation, covering some cases with macros recursion could be possible. For example counting.
In your case, you could also use a similar approach.
/// A macro that generates a sequence of constants with incrementing index values.
macro_rules! increment_consts {
($name:ident, $value:expr) => {
paste! {
const [<$name _IDX>] : usize = $value;
}
};
($name:ident, $value: expr, $($rest:ident, $new_value:expr),*) => {
paste! {
const [<$name _IDX>] : usize = $value;
}
increment_consts!($($rest, $value + 1),*);
};
}
/// A macro that generates a sequence of constants with incrementing index values.
/// The `create_consts!` macro takes a list of identifiers as input and generates
/// a sequence of constants with incrementing index values for each identifier.
macro_rules! create_consts {
($($rest:ident),* $(,)?) => {
increment_consts!($($rest, 0),*);
};
}
Then in main.rs
let's use a new macro.
fn main() {
create_consts!(A, B, C, D);
}
Let's expand the macro with:
cargo +nightly rustc --profile=check -- -Zunpretty=expanded
It’s working; we have exactly what we need.
...
fn main() {
const A_IDX: usize = 0;
const B_IDX: usize = 0 + 1;
const C_IDX: usize = 0 + 1 + 1;
const D_IDX: usize = 0 + 1 + 1 + 1;
}
Let's add it to your solution.
use paste::paste;
/// A macro that generates a sequence of constants with incrementing index values.
macro_rules! increment_consts {
($name:ident, $value:expr) => {
paste! {
const [<$name _IDX>] : usize = $value;
}
// small fine-tune to know the final mapping array size
const _ARR_LEN: usize = $value;
};
($name:ident, $value: expr, $($rest:ident, $new_value:expr),*) => {
paste! {
const [<$name _IDX>] : usize = $value;
}
increment_consts!($($rest, $value + 1),*);
};
}
/// A macro that generates a sequence of constants with incrementing index values.
/// The `create_consts!` macro takes a list of identifiers as input and generates
/// a sequence of constants with incrementing index values for each identifier.
macro_rules! create_consts {
($($rest:ident),* $(,)?) => {
increment_consts!($($rest, 0),*);
};
}
macro_rules! options_filter_consts {
($filt_const:ident => $filt_value:expr, options { $($name:ident => $value:expr)+ }) => {
const $filt_const: &str = $filt_value;
$(
const $name : &str = $value;
)+
create_consts!($($name),+);
paste! {
const [<$filt_const _MAPPING>] : [&str; _ARR_LEN + 1] = [$($name,)+];
}
};
}
fn main() {
options_filter_consts!(
MEAL_TYPE => "meal_type", // filter_id
options { // filter options (const_name => const_value)
MEAL_NO => "meal_no"
BREAKFAST => "breakfast"
FULLBOARD => "fullboard"
HALFBOARD => "halfboard"
}
);
println!("{}", HALFBOARD_IDX);
println!("{}", MEAL_NO_IDX);
println!("{}", FULLBOARD_IDX);
println!("{:?}", MEAL_TYPE_MAPPING);
}
Expand macro one more time to see what we have this time:
...
fn main() {
const MEAL_TYPE: &str = "meal_type";
const MEAL_NO: &str = "meal_no";
const BREAKFAST: &str = "breakfast";
const FULLBOARD: &str = "fullboard";
const HALFBOARD: &str = "halfboard";
const MEAL_NO_IDX: usize = 0;
const BREAKFAST_IDX: usize = 0 + 1;
const FULLBOARD_IDX: usize = 0 + 1 + 1;
const HALFBOARD_IDX: usize = 0 + 1 + 1 + 1;
const _ARR_LEN: usize = 0 + 1 + 1 + 1;
const MEAL_TYPE_MAPPING: [&str; _ARR_LEN + 1] =
[MEAL_NO, BREAKFAST, FULLBOARD, HALFBOARD];
{ ::std::io::_print(format_args!("{0}\n", HALFBOARD_IDX)); };
{ ::std::io::_print(format_args!("{0}\n", MEAL_NO_IDX)); };
{ ::std::io::_print(format_args!("{0}\n", FULLBOARD_IDX)); };
{ ::std::io::_print(format_args!("{0:?}\n", MEAL_TYPE_MAPPING)); };
}
If you want to avoid having _ARR_LEN leftover, consider using the count
macro.
macro_rules! count {
() => { 0 };
($odd:tt $($a:tt $b:tt)*) => { (count!($($a)*) << 1) | 1 };
($($a:tt $even:tt)*) => { count!($($a)*) << 1 };
}
/// A macro that generates a sequence of constants with incrementing index values.
macro_rules! increment_consts {
($name:ident, $value:expr) => {
paste! {
const [<$name _IDX>] : usize = $value;
}
// const _ARR_LEN: usize = $value; -- remove this line
};
($name:ident, $value: expr, $($rest:ident, $new_value:expr),*) => {
paste! {
const [<$name _IDX>] : usize = $value;
}
increment_consts!($($rest, $value + 1),*);
};
}
/// A macro that generates a sequence of constants with incrementing index values.
/// The `create_consts!` macro takes a list of identifiers as input and generates
/// a sequence of constants with incrementing index values for each identifier.
macro_rules! create_consts {
($($rest:ident),* $(,)?) => {
increment_consts!($($rest, 0),*);
};
}
macro_rules! options_filter_consts {
($filt_const:ident => $filt_value:expr, options { $($name:ident => $value:expr)+ }) => {
const $filt_const: &str = $filt_value;
$(
const $name : &str = $value;
)+
create_consts!($($name),+);
paste! {
const [<$filt_const _MAPPING>] : [&str; count!($($name)+)] = [$($name,)+];
}
};
}