Skip to content

Commit fd2c450

Browse files
committed
replaced old rule with new rule
1 parent cc77b6a commit fd2c450

File tree

1 file changed

+87
-29
lines changed

1 file changed

+87
-29
lines changed

src/coding-guidelines/types-and-traits.rst

Lines changed: 87 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -6,60 +6,118 @@
66
Types and Traits
77
================
88

9-
.. guideline:: Avoid Implicit Integer Wrapping
9+
.. guideline:: Use strong types to differentiate between logically distinct values
1010
:id: gui_xztNdXA2oFNB
11-
:category: required
11+
:category: advisory
1212
:status: draft
1313
:release: 1.85.0;1.85.1
1414
:fls: fls_cokwseo3nnr
15-
:decidability: decidable
15+
:decidability: undecidable
1616
:scope: module
17-
:tags: numerics
17+
:tags: types, safety, clarity
1818

19-
Code must not rely on Rust's implicit integer wrapping behavior that may occur in release
20-
builds. Instead, explicitly handle potential overflows using the standard library's checked,
21-
saturating, or wrapping operations.
19+
Parameters and variables with logically distinct types must be statically distinguishable by the type system.
2220

21+
Use a newtype (e.g., ``struct Meters(u32);``) when:
22+
23+
* Two or more quantities share the same underlying primitive representation but are logically distinct
24+
* Confusing them would constitute a semantic error
25+
* You need to improve type safety and encapsulation
26+
* You need to enable trait-based behavior
27+
* You need to establish new invariants
28+
2329
.. rationale::
2430
:id: rat_kYiIiW8R2qD1
2531
:status: draft
2632

27-
In debug builds, Rust performs runtime checks for integer overflow and will panic if detected.
28-
However, in release builds (with optimizations enabled), unless the flag `overflow-checks`_ is
29-
turned on, integer operations silently wrap around on overflow, creating potential for silent
30-
failures and security vulnerabilities. Note that overflow-checks only brings the default panic
31-
behavior from debug into release builds, avoiding potential silent wrap arounds. Nonetheless,
32-
abrupt program termination is usually not suitable and, therefore, turning this flag on must
33-
not be used as a substitute of explicit handling. Furthermore, the behavior in release mode is
34-
under consideration by the The Rust Language Design Team and in the future overflow checking
35-
may be turned on by default in release builds (it is a `frequently requested change`_).
36-
37-
.. _overflow-checks: https://github.com/rust-lang/rust/blob/master/src/doc/rustc/src/codegen-options/index.md#overflow-checks
38-
.. _frequently requested change: https://lang-team.rust-lang.org/frequently-requested-changes.html#numeric-overflow-checking-should-be-on-by-default-even-in-release-mode
39-
40-
Safety-critical software requires consistent and predictable behavior across all build
41-
configurations. Explicit handling of potential overflow conditions improves code clarity,
42-
maintainability, and reduces the risk of numerical errors in production.
33+
This rule ensures that parameters and variables convey intent directly through the type system to avoid accidental misuse of values with identical primitives but different semantics.
34+
In particular:
35+
* Prevents mixing logically distinct values.
36+
Primitive types like ``u32`` or ``u64`` can represent lengths, counters, timestamps, durations, IDs, or other values.
37+
Different semantic domains can be confused, leading to incorrect computations.
38+
The Rust type system prevents such mistakes when semantics are encoded into distinct types.
39+
* Improves static safety.
40+
Statically distinct types allow the compiler to enforce domain distinctions.
41+
Accidental swapping of parameters or returning the wrong quantity becomes a compile-time error.
42+
* Improves readability and discoverability.
43+
Intent-revealing names (``Meters``, ``Seconds``, ``UserId``) make code self-documenting.
44+
Type signatures become easier to read and understand.
45+
* Enables domain-specific trait implementations.
46+
Statically distinct types allow you to implement ``Add``, ``Mul``, or custom traits in ways that match the domain logic.
47+
Aliases cannot do this, because they are not distinct types.
48+
* Supports API evolution.
49+
Statically distinct types act as strong API contracts that can evolve independently from their underlying representations.
4350

4451
.. non_compliant_example::
4552
:id: non_compl_ex_PO5TyFsRTlWv
4653
:status: draft
4754

55+
This noncompliant example uses primitive types directly, leading to potential confusion between ``distance`` and ``time``.
56+
Nothing prevents the caller from passing ``time`` as ``distance`` or vice-versa.
57+
The units of each type are not clear from the function signature alone.
58+
Mistakes compile cleanly and silently produce wrong results.
59+
60+
.. code-block:: rust
61+
62+
fn travel(distance: u32, time: u32) -> u32 {
63+
distance / time
64+
}
65+
66+
fn main() {
67+
let d: Meters = 100;
68+
let t: Seconds = 10;
69+
let _result = travel(t, d); // Compiles, but semantically incorrect
70+
}
71+
72+
.. non_compliant_example::
73+
:id: non_compl_ex_PO5TyFsRTlWu
74+
:status: draft
75+
76+
This noncompliant example uses aliases instead of distinct types.
77+
Aliases do not create new types, so the compiler cannot enforce distinctions between ``Meters`` and ``Seconds``.
78+
79+
Aliases cannot do this, because they are not distinct types.
80+
This noncompliant example uses primitive types directly, leading to potential confusion between ``distance`` and ``time``.
81+
Nothing prevents the caller from passing ``time`` as ``distance`` or vice-versa.
82+
The units of each type are not clear from the function signature alone.
83+
Mistakes compile cleanly and silently produce wrong results.
84+
4885
.. code-block:: rust
4986
50-
fn calculate_next_position(current: u32, velocity: u32) -> u32 {
51-
// Potential for silent overflow in release builds
52-
current + velocity
87+
type Meters = u32;
88+
type Seconds = u32;
89+
type MetersPerSecond = u32;
90+
91+
fn travel(distance: Meters, time: Seconds) -> MetersPerSecond {
92+
distance / time
93+
}
94+
95+
fn main() {
96+
let d: Meters = 100;
97+
let t: Seconds = 10;
98+
let _result = travel(t, d); // Compiles, but semantically incorrect
5399
}
54100
55101
.. compliant_example::
56102
:id: compl_ex_WTe7GoPu5Ez0
57103
:status: draft
58104

105+
This compliant example uses newtypes to create distinct types for ``Meters``, ``Seconds``, and ``MetersPerSecond``.
106+
The compiler enforces correct usage, preventing accidental swapping of parameters.
107+
The function signature clearly conveys the intended semantics of each parameter and return value.
108+
59109
.. code-block:: rust
60110
61-
fn calculate_next_position(current: u32, velocity: u32) -> u32 {
62-
// Explicitly handle potential overflow with checked addition
63-
current.checked_add(velocity).expect("Position calculation overflowed")
111+
struct Meters(u32);
112+
struct Seconds(u32);
113+
struct MetersPerSecond(u32);
114+
115+
fn travel(distance: Meters, time: Seconds) -> MetersPerSecond {
116+
MetersPerSecond(distance.0 / time.0)
64117
}
65118
119+
fn main() {
120+
let d = Meters(100);
121+
let t = Seconds(10);
122+
let _result = travel(d, t); // Correct usage
123+
}

0 commit comments

Comments
 (0)