|
6 | 6 | Types and Traits |
7 | 7 | ================ |
8 | 8 |
|
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 |
12 | 12 | :status: draft |
13 | 13 | :release: 1.85.0;1.85.1 |
14 | 14 | :fls: fls_cokwseo3nnr |
15 | | - :decidability: decidable |
| 15 | + :decidability: undecidable |
16 | 16 | :scope: module |
17 | | - :tags: numerics |
| 17 | + :tags: types, safety, understandability |
18 | 18 |
|
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. |
22 | 20 |
|
| 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 | + |
23 | 29 | .. rationale:: |
24 | | - :id: rat_kYiIiW8R2qD1 |
| 30 | + :id: rat_kYiIiW8R2qD2 |
25 | 31 | :status: draft |
26 | 32 |
|
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: |
36 | 35 |
|
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. |
39 | 51 |
|
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 | + } |
43 | 72 |
|
44 | 73 | .. non_compliant_example:: |
45 | 74 | :id: non_compl_ex_PO5TyFsRTlWv |
46 | 75 | :status: draft |
47 | 76 |
|
| 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 | + |
48 | 86 | .. code-block:: rust |
49 | 87 |
|
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 |
53 | 100 | } |
54 | 101 |
|
55 | 102 | .. compliant_example:: |
56 | | - :id: compl_ex_WTe7GoPu5Ez0 |
| 103 | + :id: compl_ex_WTe7GoPu5Ez1 |
57 | 104 | :status: draft |
58 | 105 |
|
| 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 | + |
59 | 110 | .. code-block:: rust |
60 | 111 |
|
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 | + } |
65 | 130 |
|
| 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