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
Copy file name to clipboardExpand all lines: src/coding-guidelines/expressions.rst
+76Lines changed: 76 additions & 0 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -5,3 +5,79 @@
5
5
6
6
Expressions
7
7
===========
8
+
9
+
10
+
.. guideline:: Avoid as underscore pointer casts
11
+
:id: gui_HDnAZ7EZ4z6G
12
+
:category: required
13
+
:status: draft
14
+
:release: <TODO>
15
+
:fls: fls_1qhsun1vyarz
16
+
:decidability: decidable
17
+
:scope: module
18
+
:tags: readability, reduce-human-error
19
+
20
+
Code must not rely on Rust's type inference when doing explicit pointer casts via ``var as Type`` or ``core::mem::transmute``.
21
+
Instead, explicitly specify the complete target type in the ``as`` expression or ``core::mem::transmute`` call expression.
22
+
23
+
.. rationale::
24
+
:id: rat_h8LdJQ1MNKu9
25
+
:status: draft
26
+
27
+
``var as Type`` casts and ``core::mem::transmute``\s between raw pointer types are generally valid and unchecked by the compiler as long the target pointer type is a thin pointer.
28
+
Not specifying the concrete target pointer type allows the compiler to infer it from the surroundings context which may result in the cast accidentally changing due to surrounding type changes resulting in semantically invalid pointer casts.
29
+
30
+
Raw pointers have a variety of invariants to manually keep track of.
31
+
Specifying the concrete types in these scenarios allows the compiler to catch some of these potential issues for the user.
32
+
33
+
.. non_compliant_example::
34
+
:id: non_compl_ex_V37Pl103aUW4
35
+
:status: draft
36
+
37
+
The following code leaves it up to type inference to figure out the concrete types of the raw pointer casts, allowing changes to ``with_base``'s function signature to affect the types the function body of ``non_compliant_example`` without incurring a compiler error.
38
+
39
+
.. code-block:: rust
40
+
41
+
#[repr(C)]
42
+
struct Base {
43
+
position: (u32, u32)
44
+
}
45
+
46
+
#[repr(C)]
47
+
struct Extended {
48
+
base: Base,
49
+
scale: f32
50
+
}
51
+
52
+
fn non_compliant_example(extended: &Extended) {
53
+
let extended = extended as *const _;
54
+
with_base(unsafe { &*(extended as *const _) })
55
+
}
56
+
57
+
fn with_base(_: &Base) { ... }
58
+
59
+
.. compliant_example::
60
+
:id: compl_ex_W08ckDrkOhkt
61
+
:status: draft
62
+
63
+
We specify the concrete target types for our pointer casts resulting in a compilation error if the function signature of ``with_base`` is changed.
Copy file name to clipboardExpand all lines: src/coding-guidelines/macros.rst
+193Lines changed: 193 additions & 0 deletions
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.
173
+
174
+
175
+
93
176
.. guideline:: Shall not use Function-like Macros
94
177
:id: gui_WJlWqgIxmE8P
95
178
:category: mandatory
@@ -301,4 +384,114 @@ Macros
301
384
fn example_function() {
302
385
// Compliant implementation
303
386
}
387
+
388
+
.. guideline:: Do not hide unsafe blocks within macro expansions
389
+
:id: gui_FRLaMIMb4t3S
390
+
:category: required
391
+
:status: draft
392
+
:release: todo
393
+
:fls: fls_4vjbkm4ceymk
394
+
:decidability: todo
395
+
:scope: todo
396
+
:tags: reduce-human-error
397
+
398
+
Description of the guideline goes here.
399
+
400
+
.. rationale::
401
+
:id: rat_WJubG7KuUDLW
402
+
:status: draft
403
+
404
+
Explanation of why this guideline is important.
304
405
406
+
.. non_compliant_example::
407
+
:id: non_compl_ex_AyFnP0lJLHxi
408
+
:status: draft
409
+
410
+
Explanation of code example.
411
+
412
+
.. code-block:: rust
413
+
414
+
fn example_function() {
415
+
// Non-compliant implementation
416
+
}
417
+
418
+
.. compliant_example::
419
+
:id: compl_ex_pO5gP1aj2v4F
420
+
:status: draft
421
+
422
+
Explanation of code example.
423
+
424
+
.. code-block:: rust
425
+
426
+
fn example_function() {
427
+
// Compliant implementation
428
+
}
429
+
430
+
.. guideline:: Names in a macro definition shall use a fully qualified path
431
+
:id: gui_SJMrWDYZ0dN4
432
+
:category: required
433
+
:status: draft
434
+
:release: 1.85.0;1.85.1
435
+
:fls: fls_7kb6ltajgiou
436
+
:decidability: decidable
437
+
:scope: module
438
+
:tags: reduce-human-error
439
+
440
+
Each name inside of the definition of a macro shall either use a global path or path prefixed with $crate.
441
+
442
+
.. rationale::
443
+
:id: rat_VRNXaxmW1l2s
444
+
:status: draft
445
+
446
+
Using a path that refers to an entity relatively inside of a macro subjects it to path resolution
447
+
results which may change depending on where the macro is used. The intended path to refer to an entity
448
+
can be shadowed when using a macro leading to unexpected behaviors. This could lead to developer confusion
449
+
about why a macro behaves differently in diffenent locations, or confusion about where entity in a macro
450
+
will resolve to.
451
+
452
+
.. non_compliant_example::
453
+
:id: non_compl_ex_m2XR1ihTbCQS
454
+
:status: draft
455
+
456
+
The following is a macro which shows referring to a vector entity using a non-global path. Depending on
457
+
where the macro is used a different `Vec` could be used than is intended. If scope where this is used
458
+
defines a struct `Vec` which is not preset at the macro defintion, the macro user might be intending to
459
+
use that in the macro.
460
+
461
+
.. code-block:: rust
462
+
463
+
#[macro_export]
464
+
macro_rules! vec {
465
+
( $( $x:expr ),* ) => {
466
+
{
467
+
let mut temp_vec = Vec::new(); // non-global path
468
+
$(
469
+
temp_vec.push($x);
470
+
)*
471
+
temp_vec
472
+
}
473
+
};
474
+
}
475
+
476
+
.. compliant_example::
477
+
:id: compl_ex_xyaShvxL9JAM
478
+
:status: draft
479
+
480
+
The following is a macro refers to Vec using a global path. Even if there is a different struct called
481
+
`Vec` defined in the scope of the macro usage, this macro will unambigiously use the `Vec` from the
482
+
Standard Library.
483
+
484
+
.. code-block:: rust
485
+
486
+
#[macro_export]
487
+
macro_rules! vec {
488
+
( $( $x:expr ),* ) => {
489
+
{
490
+
let mut temp_vec = ::std::vec::Vec::new(); // global path
0 commit comments