Skip to content

Commit 587f8bc

Browse files
rcseacordmanhatsu
andauthored
replaced integer overflow rule with strong typing rule (#234)
* replaced old rule with new rule * Apply suggestion from @rcseacord update tags * changed IDs * fix: add indentation to fit rst format * Update types-and-traits.rst * Update types-and-traits.rst --------- Co-authored-by: manhatsu <[email protected]>
1 parent 8819769 commit 587f8bc

File tree

1 file changed

+102
-31
lines changed

1 file changed

+102
-31
lines changed

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

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

9-
.. guideline:: Avoid Implicit Integer Wrapping
10-
:id: gui_xztNdXA2oFNB
11-
:category: required
9+
.. guideline:: Use strong types to differentiate between logically distinct values
10+
:id: gui_xztNdXA2oFNC
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, understandability
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::
24-
:id: rat_kYiIiW8R2qD1
30+
:id: rat_kYiIiW8R2qD2
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`_).
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:
3635

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
36+
* Prevents mixing logically distinct values.
37+
Primitive types like ``u32`` or ``u64`` can represent lengths, counters, timestamps, durations, IDs, or other values.
38+
Different semantic domains can be confused, leading to incorrect computations.
39+
The Rust type system prevents such mistakes when semantics are encoded into distinct types.
40+
* Improves static safety.
41+
Statically distinct types allow the compiler to enforce domain distinctions.
42+
Accidental swapping of parameters or returning the wrong quantity becomes a compile-time error.
43+
* Improves readability and discoverability.
44+
Intent-revealing names (``Meters``, ``Seconds``, ``UserId``) make code self-documenting.
45+
Type signatures become easier to read and understand.
46+
* Enables domain-specific trait implementations.
47+
Statically distinct types allow you to implement ``Add``, ``Mul``, or custom traits in ways that match the domain logic.
48+
Aliases cannot do this, because they are not distinct types.
49+
* Supports API evolution.
50+
Statically distinct types act as strong API contracts that can evolve independently from their underlying representations.
3951

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.
52+
.. non_compliant_example::
53+
:id: non_compl_ex_PO5TyFsRTlWw
54+
:status: draft
55+
56+
This noncompliant example uses primitive types directly, leading to potential confusion between ``distance`` and ``time``.
57+
Nothing prevents the caller from passing ``time`` as ``distance`` or vice-versa.
58+
The units of each type are not clear from the function signature alone.
59+
Mistakes compile cleanly and silently produce wrong results.
60+
61+
.. code-block:: rust
62+
63+
fn travel(distance: u32, time: u32) -> u32 {
64+
distance / time
65+
}
66+
67+
fn main() {
68+
let d = 100;
69+
let t = = 10;
70+
let _result = travel(t, d); // Compiles, but semantically incorrect
71+
}
4372
4473
.. non_compliant_example::
4574
:id: non_compl_ex_PO5TyFsRTlWv
4675
:status: draft
4776

77+
This noncompliant example uses aliases instead of distinct types.
78+
Aliases do not create new types, so the compiler cannot enforce distinctions between ``Meters`` and ``Seconds``.
79+
80+
Aliases cannot do this, because they are not distinct types.
81+
This noncompliant example uses primitive types directly, leading to potential confusion between ``distance`` and ``time``.
82+
Nothing prevents the caller from passing ``time`` as ``distance`` or vice-versa.
83+
The units of each type are not clear from the function signature alone.
84+
Mistakes compile cleanly and silently produce wrong results.
85+
4886
.. code-block:: rust
4987
50-
fn calculate_next_position(current: u32, velocity: u32) -> u32 {
51-
// Potential for silent overflow in release builds
52-
current + velocity
88+
type Meters = u32;
89+
type Seconds = u32;
90+
type MetersPerSecond = u32;
91+
92+
fn travel(distance: Meters, time: Seconds) -> MetersPerSecond {
93+
distance / time
94+
}
95+
96+
fn main() {
97+
let d: Meters = 100;
98+
let t: Seconds = 10;
99+
let _result = travel(t, d); // Compiles, but semantically incorrect
53100
}
54101
55102
.. compliant_example::
56-
:id: compl_ex_WTe7GoPu5Ez0
103+
:id: compl_ex_WTe7GoPu5Ez1
57104
:status: draft
58105

106+
This compliant example uses newtypes to create distinct types for ``Meters``, ``Seconds``, and ``MetersPerSecond``.
107+
The compiler enforces correct usage, preventing accidental swapping of parameters.
108+
The function signature clearly conveys the intended semantics of each parameter and return value.
109+
59110
.. code-block:: rust
60111
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")
64-
}
112+
use std::ops::Div;
113+
114+
#[derive(Debug, Clone, Copy)]
115+
struct Meters(u32);
116+
117+
#[derive(Debug, Clone, Copy)]
118+
struct Seconds(u32);
119+
120+
#[derive(Debug, Clone, Copy)]
121+
struct MetersPerSecond(u32);
122+
123+
impl Div<Seconds> for Meters {
124+
type Output = MetersPerSecond;
125+
126+
fn div(self, rhs: Seconds) -> Self::Output {
127+
MetersPerSecond(self.0 / rhs.0)
128+
}
129+
}
65130
131+
fn main() {
132+
let d = Meters(100);
133+
let t = Seconds(10);
134+
let result = d / t; // Clean and type-safe!
135+
println!("{:?}", result); // MetersPerSecond(10)
136+
}

0 commit comments

Comments
 (0)