Skip to content

Commit 4433185

Browse files
committed
Add declarative macro guideline: avoid specializing
1 parent 47dd576 commit 4433185

File tree

1 file changed

+141
-0
lines changed

1 file changed

+141
-0
lines changed

src/coding-guidelines/macros.rst

Lines changed: 141 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,3 +5,144 @@
55

66
Macros
77
======
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

Comments
 (0)