Skip to content

Commit 4543086

Browse files
rcseacordfelix91grPLeVasseur
authored
revised divide by zero rule (#232)
* revised divide by zero rule * Refine language in division and remainder guidelines * Refine guidelines for integer division and zero checks Clarify guideline on integer division and zero divisors * Update expressions.rst eliminate redundant compliant examples * Update src/coding-guidelines/expressions.rst Co-authored-by: Félix Fischer <[email protected]> * Update src/coding-guidelines/expressions.rst Co-authored-by: Félix Fischer <[email protected]> * Update src/coding-guidelines/expressions.rst Co-authored-by: Félix Fischer <[email protected]> * Update src/coding-guidelines/expressions.rst Co-authored-by: Félix Fischer <[email protected]> * Update src/coding-guidelines/expressions.rst Co-authored-by: Félix Fischer <[email protected]> * Update src/coding-guidelines/expressions.rst Co-authored-by: Félix Fischer <[email protected]> * Update src/coding-guidelines/expressions.rst Co-authored-by: Félix Fischer <[email protected]> * Update src/coding-guidelines/expressions.rst Co-authored-by: Félix Fischer <[email protected]> * Update src/coding-guidelines/expressions.rst Co-authored-by: Félix Fischer <[email protected]> * Update src/coding-guidelines/expressions.rst Co-authored-by: Félix Fischer <[email protected]> * Update src/coding-guidelines/expressions.rst Co-authored-by: Félix Fischer <[email protected]> * Update expressions.rst some minor edits * typo --------- Co-authored-by: Félix Fischer <[email protected]> Co-authored-by: Pete LeVasseur <[email protected]>
1 parent bfad9e1 commit 4543086

File tree

1 file changed

+100
-59
lines changed

1 file changed

+100
-59
lines changed

src/coding-guidelines/expressions.rst

Lines changed: 100 additions & 59 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@
66
Expressions
77
===========
88

9-
109
.. guideline:: Avoid as underscore pointer casts
1110
:id: gui_HDnAZ7EZ4z6G
1211
:category: required
@@ -82,7 +81,7 @@ Expressions
8281
8382
fn with_base(_: &Base) { ... }
8483
85-
.. guideline:: Do not use integer type as divisor
84+
.. guideline:: Do not use an integer type as a divisor during integer division
8685
:id: gui_7y0GAMmtMhch
8786
:category: advisory
8887
:status: draft
@@ -92,32 +91,39 @@ Expressions
9291
:scope: module
9392
:tags: numerics, subset
9493

95-
This guideline applies when a `DivisionExpression
96-
<https://rust-lang.github.io/fls/expressions.html#syntax_divisionexpression>`_ or `RemainderExpression
97-
<https://rust-lang.github.io/fls/expressions.html#syntax_remainderexpression>`_ is used with a RightOperand of
98-
`integer type <https://rust-lang.github.io/fls/types-and-traits.html#integer-types>`_.
94+
Do not provide a right operand of
95+
`integer type <https://rust-lang.github.io/fls/types-and-traits.html#integer-types>`_
96+
during a `division expression
97+
<https://rust-lang.github.io/fls/expressions.html#syntax_divisionexpression>`_ or `remainder expression
98+
<https://rust-lang.github.io/fls/expressions.html#syntax_remainderexpression>`_ when the left operand also has integer type.
99+
100+
This rule applies to the following primitive integer types:
101+
102+
* ``i8``
103+
* ``i16``
104+
* ``i32``
105+
* ``i64``
106+
* ``i128``
107+
* ``u8``
108+
* ``u16``
109+
* ``u32``
110+
* ``u64``
111+
* ``u128``
112+
* ``usize``
113+
* ``isize``
99114

100115
.. rationale::
101116
:id: rat_vLFlPWSCHRje
102117
:status: draft
103118

104-
The built-in semantics for these expressions can result in panics when division by zero occurs. It is
105-
recommended to either:
106-
107-
* Use checked division functions, which ensure the programmer handles the case when the divisor is zero, or
108-
* To create divisors using :std:`std::num::NonZero`, which then allows the programmer to perform those
109-
operations knowing that their divisor is not zero.
110-
111-
**Note:** since the compiler can assume the value of a :std:`std::num::NonZero`
112-
variable to not be zero, checks for zero when dividing by it can be elided in the
113-
final binary, increasing overall performance beyond what normal division can have.
119+
Integer division and integer remainder division both panic when the right operand has a value of zero.
120+
Division by zero is undefined in mathematics because it leads to contradictions and there is no consistent value that can be assigned as its result.
114121

115122
.. non_compliant_example::
116123
:id: non_compl_ex_0XeioBrgfh5z
117124
:status: draft
118125

119-
When either the division or remainder are performed, the right operand is evaluated to zero and the
120-
program panics.
126+
Both the division and remainder operations in this non-compliant example will panic if evaluated because the right operand is zero.
121127

122128
.. code-block:: rust
123129
@@ -129,23 +135,43 @@ Expressions
129135
:id: compl_ex_k1CD6xoZxhXb
130136
:status: draft
131137

132-
There is no compliant way to divide with an integer type. Here, instead, the developer explicitly:
138+
Checked division prevents division by zero from occurring.
139+
The programmer can then handle the returned :std:`std::option::Option`.
140+
Using checked division and remainder is particularly important in the signed integer case,
141+
where arithmetic overflow can also occur when dividing the minimum representable value by -1.
142+
143+
.. code-block:: rust
144+
145+
// Using the checked division API
146+
let y = match 5i32.checked_div(0) {
147+
None => 0
148+
Some(r) => r
149+
};
150+
151+
// Using the checked remainder API
152+
let z = match 5i32.checked_rem(0) {
153+
None => 0
154+
Some(r) => r
155+
};
156+
157+
.. compliant_example::
158+
:id: compl_ex_k1CD6xoZxhXc
159+
:status: draft
133160

134-
* Uses a checked division function, which ensures a zero divisor is handled separately, and
135-
* Creates a divisor using :std:`std::num::NonZero`, which outsources the check for zero to the
136-
construction of that struct. It's worth noting that such a divisor can be used multiple times after it's been created, whilst keeping the guarantee that such divisions will be safe.
161+
This compliant solution creates a divisor using :std:`std::num::NonZero`.
162+
:std:`std::num::NonZero` is a wrapper around primitive integer types that guarantees the contained value is never zero.
163+
:std:`std::num::NonZero::new` creates a new binding that represents a value that is known not to be zero.
164+
This ensures that functions operating on its value can correctly assume that they are not being given zero as their input.
165+
166+
Note that the test for arithmetic overflow that occurs when dividing the minimum representable value by -1 is unnecessary
167+
in this compliant example because the result of the division expression is an unsigned integer type.
137168

138169
.. code-block:: rust
139170
140-
let x = 0;
171+
let x = 0u32;
141172
if let Some(divisor) = match NonZero::<u32>::new(x) {
142-
let result = 5 / divisor;
173+
let result = 5u32 / divisor;
143174
}
144-
let result = match 5u32.checked_rem(x) {
145-
None => 0,
146-
Some(r) => r,
147-
}
148-
149175
150176
.. guideline:: Do not divide by 0
151177
:id: gui_kMbiWbn8Z6g5
@@ -157,64 +183,79 @@ Expressions
157183
:scope: system
158184
:tags: numerics, defect
159185

160-
This guideline applies when unsigned integer or two’s complement division is performed during the
161-
evaluation of an `ArithmeticExpression
162-
<https://rust-lang.github.io/fls/expressions.html#arithmetic-expressions>`_.
186+
Integer division by zero results in a panic.
187+
This includes both `division expressions
188+
<https://rust-lang.github.io/fls/expressions.html#syntax_divisionexpression>`_ and `remainder expressions
189+
<https://rust-lang.github.io/fls/expressions.html#syntax_remainderexpression>`_.
163190

164-
This includes the evaluation of a `RemainderExpression
165-
<https://rust-lang.github.io/fls/expressions.html#syntax_remainderexpression>`_, which uses unsigned integer or two's
166-
complement division.
191+
Division and remainder expressions on signed integers are also susceptible to arithmetic overflow.
192+
Overflow is covered in full by the guideline `Ensure that integer operations do not result in arithmetic overflow`.
167193

168-
This rule does not apply to evaluation of a :std:`core::ops::Div` trait on types other than `integer
194+
This rule applies to the following primitive integer types:
195+
196+
* ``i8``
197+
* ``i16``
198+
* ``i32``
199+
* ``i64``
200+
* ``i128``
201+
* ``u8``
202+
* ``u16``
203+
* ``u32``
204+
* ``u64``
205+
* ``u128``
206+
* ``usize``
207+
* ``isize``
208+
209+
This rule does not apply to evaluation of the :std:`core::ops::Div` trait on types other than `integer
169210
types <https://rust-lang.github.io/fls/types-and-traits.html#integer-types>`_.
170211

212+
This rule is a less strict version of `Do not use an integer type as a divisor during integer division`.
213+
All code that complies with that rule also complies with this rule.
214+
171215
.. rationale::
172216
:id: rat_h84NjY2tLSBW
173217
:status: draft
174218

175-
Integer division by zero results in a panic, which is an abnormal program state and may terminate the
176-
process. The use of :std:`std::num::NonZero` as the divisor is a recommended way to avoid the
177-
undecidability of this guideline.
219+
Integer division by zero results in a panic; an abnormal program state that may terminate the process and must be avoided.
178220

179221
.. non_compliant_example::
180222
:id: non_compl_ex_LLs3vY8aGz0F
181223
:status: draft
182224

183-
When the division is performed, the right operand is evaluated to zero and the program panics.
225+
This non-compliant example panics when the right operand is zero for either the division or remainder operations.
184226

185227
.. code-block:: rust
186228
187229
let x = 0;
188-
let y = 5 / x; // This line will panic.
230+
let y = 5 / x; // Results in a panic.
231+
let z = 5 % x; // Also results in a panic.
189232
190233
.. compliant_example::
191-
:id: compl_ex_Ri9pP5Ch3kbb
234+
:id: compl_ex_Ri9pP5Ch3kcc
192235
:status: draft
193236

194-
There is no compliant way to perform integer division by zero. A checked division will prevent any
195-
division by zero from happening. The programmer can then handle the returned :std:`std::option::Option`.
237+
Compliant examples from `Do not use an integer type as a divisor during integer division` are also valid for this rule.
238+
Additionally, the check for zero can be performed manually, as in this compliant example.
239+
However, as the complexity of the control flow leading to the invariant increases,
240+
it becomes increasingly harder for both programmers and static analysis tools to reason about it.
196241

197-
The check for zero can also be performed manually. However, as the complexity of the control
198-
flow leading to the invariant increases, it becomes increasingly harder to reason about it. For both programmers and static analysis tools.
242+
Note that the test for arithmetic overflow is not necessary for unsigned integers.
199243

200244
.. code-block:: rust
201245
202-
// Example 1: using the checked division API
203-
let result = match 5u8.checked_div(0) {
204-
None => 0
205-
Some(r) => r
206-
};
207-
208-
// Example 2: performing zero-checks by hand
209-
let x = 0;
210-
let y = if x != 0 {
211-
5 / x
246+
// Checking for zero by hand
247+
let x = 0u32;
248+
let y = if x != 0u32 {
249+
5u32 / x
212250
} else {
213-
0
251+
0u32
214252
};
215253
216-
217-
254+
let z = if x != 0u32 {
255+
5u32 % x
256+
} else {
257+
0u32
258+
};
218259
219260
.. guideline:: The 'as' operator should not be used with numeric operands
220261
:id: gui_ADHABsmK9FXz

0 commit comments

Comments
 (0)