You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
guideline: a macro should not be used in place of a function (#74)
* guideline: a macro should not be used in place of a function
* Apply suggestions from code review
Co-authored-by: Pete LeVasseur <[email protected]>
* Update src/coding-guidelines/macros.rst
Co-authored-by: Pete LeVasseur <[email protected]>
* Change compliant/non-compliant example
---------
Co-authored-by: Pete LeVasseur <[email protected]>
Copy file name to clipboardExpand all lines: src/coding-guidelines/macros.rst
+84-1Lines changed: 84 additions & 1 deletion
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -90,6 +90,89 @@ Macros
90
90
91
91
// TODO
92
92
93
+
.. guideline:: A macro should not be used in place of a function
94
+
:id: gui_2jjWUoF1teOY
95
+
:category: mandatory
96
+
:status: draft
97
+
:release: todo
98
+
:fls: fls_xa7lp0zg1ol2
99
+
:decidability: decidable
100
+
:scope: system
101
+
:tags: reduce-human-error
102
+
103
+
Functions should always be preferred over macros, except when macros provide essential functionality that functions cannot, such as variadic interfaces, compile-time code generation, or syntax extensions via custom derive and attribute macros.
104
+
105
+
|
106
+
107
+
.. rationale::
108
+
:id: rat_M9bp23ctkzQ7
109
+
:status: draft
110
+
111
+
Macros are powerful but they come at the cost of readability, complexity, and maintainability. They obfuscate control flow and type signatures.
112
+
113
+
**Debugging Complexity**
114
+
115
+
- Errors point to expanded code rather than source locations, making it difficult to trace compile-time errors back to the original macro invocation.
116
+
117
+
**Optimization**
118
+
119
+
- Macros may inhibit compiler optimizations that work better with functions.
120
+
- Macros act like ``#[inline(always)]`` functions, which can lead to code bloat.
121
+
- They don't benefit from the compiler's inlining heuristics, missing out on selective inlining where the compiler decides when inlining is beneficial.
122
+
123
+
**Functions provide**
124
+
125
+
- Clear type signatures.
126
+
- Predictable behavior.
127
+
- Proper stack traces.
128
+
- Consistent optimization opportunities.
129
+
130
+
131
+
.. non_compliant_example::
132
+
:id: non_compl_ex_TZgk2vG42t2r
133
+
:status: draft
134
+
135
+
Using a macro where a simple function would suffice, leads to hidden mutation:
136
+
137
+
.. code-block:: rust
138
+
139
+
macro_rules! increment_and_double {
140
+
($x:expr) => {
141
+
{
142
+
$x += 1; // mutation is implicit
143
+
$x * 2
144
+
}
145
+
};
146
+
}
147
+
let mut num = 5;
148
+
let result = increment_and_double!(num);
149
+
println!("Result: {}, Num: {}", result, num);
150
+
// Result: 12, Num: 6
151
+
152
+
In this example, calling the macro both increments and returns the value in one go—without any clear indication in its “signature” that it mutates its argument. As a result, num is changed behind the scenes, which can surprise readers and make debugging more difficult.
153
+
154
+
155
+
.. compliant_example::
156
+
:id: compl_ex_iPTgzrvO7qr3
157
+
:status: draft
158
+
159
+
The same functionality, implemented as a function with explicit borrowing:
160
+
161
+
.. code-block:: rust
162
+
163
+
fn increment_and_double(x: &mut i32) -> i32 {
164
+
*x += 1; // mutation is explicit
165
+
*x * 2
166
+
}
167
+
let mut num = 5;
168
+
let result = increment_and_double(&mut num);
169
+
println!("Result: {}, Num: {}", result, num);
170
+
// Result: 12, Num: 6
171
+
172
+
The function version makes the mutation and borrowing explicit in its signature, improving readability, safety, and debuggability.
0 commit comments