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);
    }
}