Skip to content

Commit 9d0bb7b

Browse files
committed
Merge branch 'main' into style-guide-changes
2 parents 84a595f + fbe0061 commit 9d0bb7b

File tree

7 files changed

+23022
-22751
lines changed

7 files changed

+23022
-22751
lines changed

README.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@
22

33
Coding Guidelines for Safety Critical Rust developed by the [Safety Critical Rust Consortium][safety-critical-rust-consortium].
44

5+
[View the latest rendered guidelines online](https://rustfoundation.github.io/safety-critical-rust-coding-guidelines/)
6+
57
_Note_: Early, subject to changes.
68

79
## Building the coding guidelines

builder/build_cli.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@
1818
# Automatically watch the following extra directories when --serve is used.
1919
EXTRA_WATCH_DIRS = ["exts", "themes"]
2020

21-
SPEC_CHECKSUM_URL = "https://spec.ferrocene.dev/paragraph-ids.json"
21+
SPEC_CHECKSUM_URL = "https://rust-lang.github.io/fls/paragraph-ids.json"
2222
SPEC_LOCKFILE = "spec.lock"
2323

2424
def build_docs(

exts/coding_guidelines/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@ def setup(app):
4747
types=[str],
4848
)
4949
app.add_config_value(name='fls_paragraph_ids_url',
50-
default='https://spec.ferrocene.dev/paragraph-ids.json',
50+
default='https://rust-lang.github.io/fls/paragraph-ids.json',
5151
rebuild='env')
5252
app.add_config_value(name='enable_spec_lock_consistency',
5353
default=True,

exts/coding_guidelines/fls_checks.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111

1212
# Get the Sphinx logger
1313
logger = logging.getLogger('sphinx')
14-
fls_paragraph_ids_url = "https://spec.ferrocene.dev/paragraph-ids.json"
14+
fls_paragraph_ids_url = "https://rust-lang.github.io/fls/paragraph-ids.json"
1515

1616
class FLSValidationError(SphinxError):
1717
category = "FLS Validation Error"
@@ -206,7 +206,7 @@ def gather_fls_paragraph_ids(app, json_url):
206206
return {}, None
207207

208208
# Base URL for constructing direct links
209-
base_url = "https://spec.ferrocene.dev/"
209+
base_url = "https://rust-lang.github.io/"
210210

211211
# Process each document in the JSON structure
212212
for document in data['documents']:

src/coding-guidelines/expressions.rst

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

66
Expressions
77
===========
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.
64+
65+
.. code-block:: rust
66+
67+
#[repr(C)]
68+
struct Base {
69+
position: (u32, u32)
70+
}
71+
72+
#[repr(C)]
73+
struct Extended {
74+
base: Base,
75+
scale: f32
76+
}
77+
78+
fn non_compliant_example(extended: &Extended) {
79+
let extended = extended as *const Extended;
80+
with_base(unsafe { &*(extended as *const Base) })
81+
}
82+
83+
fn with_base(_: &Base) { ... }

src/coding-guidelines/macros.rst

Lines changed: 193 additions & 0 deletions
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
@@ -301,4 +384,114 @@ Macros
301384
fn example_function() {
302385
// Compliant implementation
303386
}
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.
304405

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
491+
$(
492+
temp_vec.push($x);
493+
)*
494+
temp_vec
495+
}
496+
};
497+
}

0 commit comments

Comments
 (0)