|
5 | 5 |
|
6 | 6 | Macros |
7 | 7 | ====== |
| 8 | + |
| 9 | +.. guideline:: Avoid specialized, fixed patterns within declarative macros |
| 10 | + :id: gui_FSpI084vbwmJ |
| 11 | + :status: draft |
| 12 | + :fls: fls_w44hav7mw3ao |
| 13 | + :tags: reduce-human-error |
| 14 | + :category: macros |
| 15 | + :recommendation: encouraged |
| 16 | + |
| 17 | + Description of the guideline goes here. |
| 18 | + |
| 19 | + .. rationale:: |
| 20 | + :id: rat_zqr9uEqP6nzW |
| 21 | + :status: draft |
| 22 | + |
| 23 | + It is common to use declarative macros to implement traits which would |
| 24 | + otherwise involve repetitive code. |
| 25 | + |
| 26 | + In a declarative macro the ordering of the patterns will be the order that |
| 27 | + they are matched against which can lead to unexpected behavior in the case |
| 28 | + where we have unique behavior intended for a particular expression. |
| 29 | + |
| 30 | + If needing to specialize logic within the macro based on a particular |
| 31 | + expression's value, it may be better to not use a declarative macro. |
| 32 | + |
| 33 | + Limitation: Note that following this rule means that we are unable to support |
| 34 | + variadic declarative macros with one or more arguments. |
| 35 | + |
| 36 | + .. bad_example:: |
| 37 | + :id: bad_ex_5vK0CCmePkef |
| 38 | + :status: draft |
| 39 | + |
| 40 | + We have two macro match rules at the same level of nesting. Since the |
| 41 | + matching is done macro rule by macro rule and this process is stopped as soon |
| 42 | + as a macro matcher is matched we will not reach the specialized case for EmergencyValve. |
| 43 | + |
| 44 | + .. code-block:: rust |
| 45 | +
|
| 46 | + #[derive(Debug)] |
| 47 | + enum SafetyLevel { |
| 48 | + Green, |
| 49 | + Yellow, |
| 50 | + Red |
| 51 | + } |
| 52 | +
|
| 53 | + trait SafetyCheck { |
| 54 | + fn verify(&self) -> SafetyLevel; |
| 55 | + } |
| 56 | +
|
| 57 | + // Different device types that need safety checks |
| 58 | + struct PressureSensor {/* ... */} |
| 59 | + struct TemperatureSensor {/* ... */} |
| 60 | + struct EmergencyValve { |
| 61 | + open: bool, |
| 62 | + } |
| 63 | +
|
| 64 | + // This macro has a pattern ordering issue |
| 65 | + macro_rules! impl_safety_trait { |
| 66 | + // Generic pattern matches any type - including EmergencyValve |
| 67 | + ($t:ty) => { |
| 68 | + impl SafetyCheck for $t { |
| 69 | + fn verify(&self) -> SafetyLevel { |
| 70 | + SafetyLevel::Green |
| 71 | + } |
| 72 | + } |
| 73 | + }; |
| 74 | +
|
| 75 | + // Special pattern for EmergencyValve - but never gets matched |
| 76 | + (EmergencyValve) => { |
| 77 | + impl SafetyCheck for EmergencyValve { |
| 78 | + fn verify(&self) -> SafetyLevel { |
| 79 | + // Emergency valve must be open for safety |
| 80 | + if !self.open { |
| 81 | + SafetyLevel::Red |
| 82 | + } else { |
| 83 | + SafetyLevel::Green |
| 84 | + } |
| 85 | + } |
| 86 | + } |
| 87 | + }; |
| 88 | + } |
| 89 | + impl_safety_trait!(EmergencyValve); |
| 90 | + impl_safety_trait!(PressureSensor); |
| 91 | + impl_safety_trait!(TemperatureSensor); |
| 92 | +
|
| 93 | + .. good_example:: |
| 94 | + :id: good_ex_ILBlY8DKB6Vs |
| 95 | + :status: draft |
| 96 | + |
| 97 | + For the specialized implementation we implement the trait directly. |
| 98 | + |
| 99 | + If we wish to use a declarative macro for a certain generic implementation |
| 100 | + we are able to do this. Note there is a single macro rule at the level of |
| 101 | + nesting within the declarative macro. |
| 102 | + |
| 103 | + .. code-block:: rust |
| 104 | +
|
| 105 | + #[derive(Debug)] |
| 106 | + enum SafetyLevel { |
| 107 | + Green, |
| 108 | + Yellow, |
| 109 | + Red |
| 110 | + } |
| 111 | +
|
| 112 | + trait SafetyCheck { |
| 113 | + fn verify(&self) -> SafetyLevel; |
| 114 | + } |
| 115 | +
|
| 116 | + // Different device types that need safety checks |
| 117 | + struct PressureSensor {/* ... */} |
| 118 | + struct TemperatureSensor {/* ... */} |
| 119 | + struct EmergencyValve { |
| 120 | + open: bool, |
| 121 | + } |
| 122 | +
|
| 123 | + // Direct implementation for EmergencyValve |
| 124 | + impl SafetyCheck for EmergencyValve { |
| 125 | + fn verify(&self) -> SafetyLevel { |
| 126 | + // Emergency valve must be open for safety |
| 127 | + if !self.open { |
| 128 | + SafetyLevel::Red |
| 129 | + } else { |
| 130 | + SafetyLevel::Green |
| 131 | + } |
| 132 | + } |
| 133 | + } |
| 134 | +
|
| 135 | + // Use generic implementation for those without |
| 136 | + // special behavior |
| 137 | + macro_rules! impl_safety_traits_generic { |
| 138 | + // Generic pattern for other types |
| 139 | + ($t:ty) => { |
| 140 | + impl SafetyCheck for $t { |
| 141 | + fn verify(&self) -> SafetyLevel { |
| 142 | + SafetyLevel::Green |
| 143 | + } |
| 144 | + } |
| 145 | + }; |
| 146 | + } |
| 147 | + impl_safety_traits_generic!(PressureSensor); |
| 148 | + impl_safety_traits_generic!(TemperatureSensor); |
0 commit comments