Skip to content

Commit d41cb20

Browse files
x0rwPLeVasseur
andauthored
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]>
1 parent 419dbee commit d41cb20

File tree

1 file changed

+84
-1
lines changed

1 file changed

+84
-1
lines changed

src/coding-guidelines/macros.rst

Lines changed: 84 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,89 @@ Macros
9090
9191
// TODO
9292
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.
173+
174+
175+
93176
.. guideline:: Shall not use Function-like Macros
94177
:id: gui_WJlWqgIxmE8P
95178
:category: mandatory
@@ -342,4 +425,4 @@ Macros
342425
343426
fn example_function() {
344427
// Compliant implementation
345-
}
428+
}

0 commit comments

Comments
 (0)