1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112
//! This module contains examples of *declarative macros*.
//!
//! Declarative macros present a way how to *substitute* call side expressions with another
//! expression based on matching pattern in a type-safe manner.
/// Macro that defines "approximately equal" for expressions interpreted as [f64].
///
/// Contrary to passing arguments to a function, `$x` is here directly substituted for the calling
/// expression. Moreover, the *types* are not actualt Rust types but rather tokens in Rust syntax -
/// in the case of `$x:expr` it's an arbitrary *expression*.
///
/// Note that the output must be a single expression, so if one wants to use multiple statements,
/// there has to be additional enclosing scope `{}`.
#[macro_export]
macro_rules! approx {
// One can define any pattern that conforms with Rust's grammar (in this case expression)
($x:expr, $y:expr; $eps:expr) => {
($x as f64 - $y as f64).abs() < $eps
};
($x:expr, $y:expr) => {
// It's ok to call other macros (or even the same in this case)
approx!($x, $y; f64::EPSILON)
};
}
/// Type class that defines instances having a known maximum value
pub trait MaxValue {
/// Accessor to the maximum value
fn max_value() -> Self;
}
/// Macro that automates implementation of [MaxValue] for arbitrary types having `MAX` member.
///
/// Automating trait implementation is very useful and common use case. In this example we
/// create a *template* implementation for [MaxValue] for an arbitrary type `$t`.
///
/// Note that macros are compile-time safe because they just substitute actual pieces of code at
/// compile time and because they are hygienic (don't allow to use data from the call side scope).
///
/// In this case, any call of this macro with a type that does not define `::MAX` would be rejected
/// at compile time.
#[macro_export]
macro_rules! impl_max_value {
// One can use regex-like patterns to match variadic arguments. Here we match on one or more
// types separated by comma. The body can then use `$(...)+` to loop over each item.
($($t:ty),+) => {
$(
impl $crate::macros::MaxValue for $t {
fn max_value() -> Self {
<$t>::MAX
}
}
)+
};
}
// This is the where we actually create all the `impl`s
impl_max_value!(u32, i32, u64, i64);
/// Macro that counts any input tokens at compilation time (i.e. resulting in a `const` value) with
/// no residual memory footprint.
/// ```
/// use rust_examples::{count, substitute};
///
/// const COUNT: usize = count!(1, 2, 3);
/// ```
///
/// See the [book about macros](https://danielkeep.github.io/tlborm/book/blk-counting.html) for
/// more and [this video](https://www.youtube.com/watch?v=q6paRBbLgNw&t=4380s) for implementation
/// details.
#[macro_export]
macro_rules! count {
($($item:tt),*) => {
<[()]>::len(&[$(substitute!($item ())),*])
};
}
/// Simple macro for replacing any token `$_t` with expression `$sub`
#[macro_export]
macro_rules! substitute {
($_t:tt $sub:expr) => {
$sub
};
}
#[macro_use]
#[cfg(test)]
mod tests {
use rstest::*;
#[rstest]
#[case(0.0, 0)]
#[case(1.0, 1)]
fn approx_eq(#[case] x: f64, #[case] y: u32) {
assert!(approx!(x, y))
}
#[rstest]
fn max_values() {
assert_eq!(u32::max_value(), u32::MAX);
assert_eq!(i32::max_value(), i32::MAX);
assert_eq!(u64::max_value(), u64::MAX);
assert_eq!(i64::max_value(), i64::MAX);
}
#[rstest]
fn count_items() {
assert_eq!(count!(), 0);
assert_eq!(count!(1), 1);
assert_eq!(count!([1, 2], [], [0, 1, 3]), 3);
}
}
