Skip to content

Commit b38ce52

Browse files
committed
Revise text about compound assignment eval order
1 parent c613c1e commit b38ce52

File tree

1 file changed

+32
-31
lines changed

1 file changed

+32
-31
lines changed

src/expressions/operator-expr.md

Lines changed: 32 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -902,9 +902,8 @@ Attempting to use a value expression is a compiler error rather than promoting i
902902
r[expr.compound-assign.operand-order]
903903
Evaluation of compound assignment expressions depends on the types of the operands.
904904

905-
r[expr.compound-assign.primitive-order]
906-
If both types are primitives and the expression is non-generic (i.e., directly uses concrete types), then the modifying operand will be evaluated first followed by the assigned operand.
907-
It will then set the value of the assigned operand's place to the value of performing the operation of the operator with the values of the assigned operand and modifying operand.
905+
r[expr.compound-assign.primitives]
906+
If the types of both operands are known, prior to monomorphization, to be primitive, the right hand side is evaluated first, the left hand side is evaluated next, and the place given by the evaluation of the left hand side is mutated by applying the operator to the values of both sides.
908907

909908
```rust
910909
# use core::{num::Wrapping, ops::AddAssign};
@@ -948,49 +947,51 @@ fn main() {
948947
```
949948

950949
> [!NOTE]
951-
> This is different than other expressions in that the right operand is evaluated before the left one.
952-
953-
> [!NOTE]
954-
> This right-before-left evaluation only occurs in non-generic code involving primitive types.
955-
> In all other cases---including generic contexts or non-primitive types---the expression is desugared into a trait method call, and operands are evaluated left to right.
950+
> This is unusual. Elsewhere left to right evaluation is the norm.
951+
>
952+
> See the [eval order test] for more examples.
956953
957954
r[expr.compound-assign.trait]
958-
Otherwise, this expression is syntactic sugar for calling the function of the overloading compound assignment trait of the operator (see the table earlier in this chapter).
959-
A mutable borrow of the assigned operand is automatically taken.
955+
Otherwise, this expression is syntactic sugar for using the corresponding trait for the operator (see [expr.arith-logic.behavior]) and calling its method with the left hand side as the [receiver] and the right hand side as the next argument.
960956

961-
For example, the following expression statements in `example` are equivalent:
957+
For example, the following two statements are equivalent:
962958

963959
```rust
964-
# struct Addable;
965960
# use std::ops::AddAssign;
966-
967-
impl AddAssign<Addable> for Addable {
968-
/* */
969-
# fn add_assign(&mut self, other: Addable) {}
970-
}
971-
972-
fn example() {
973-
# let (mut a1, a2) = (Addable, Addable);
974-
a1 += a2;
975-
976-
# let (mut a1, a2) = (Addable, Addable);
977-
AddAssign::add_assign(&mut a1, a2);
961+
fn f<T: AddAssign + Copy>(mut x: T, y: T) {
962+
x += y; // Statement 1.
963+
x.add_assign(y); // Statement 2.
978964
}
979965
```
980966

967+
> [!NOTE]
968+
> Surprisingly, desugaring this further to a fully qualified method call is not equivalent, as there is special borrow checker behavior when the sugared form is used.
969+
>
970+
> ```rust
971+
> # use std::ops::AddAssign;
972+
> fn f<T: AddAssign + Copy>(mut x: T) {
973+
> x += x; //~ OK
974+
> }
975+
> ```
976+
>
977+
> ```rust,compile_fail,E0503
978+
> # use std::ops::AddAssign;
979+
> fn f<T: AddAssign + Copy>(mut x: T) {
980+
> <T as AddAssign>::add_assign(&mut x, x);
981+
> //~^ ERROR cannot use `x` because it was mutably borrowed
982+
> }
983+
> ```
984+
981985
r[expr.compound-assign.result]
982-
Like assignment expressions, compound assignment expressions always produce [the unit value][unit].
986+
As with normal assignment expressions, compound assignment expressions always produce [the unit value][unit].
983987
984988
> [!WARNING]
985-
> The evaluation order of operands varies depending on how the expression is desugared.
986-
> Non-generic primitive assignments may evaluate the right-hand side first.
987-
> Trait-based assignments, including all generic cases, evaluate left-hand side first.
988-
> Avoid writing code that depends on operand evaluation order.
989-
> See [this test] for an example of using this dependency.
989+
> Avoid writing code that depends on the evaluation order of operands in compound assignments as it can be unusual and surprising.
990990
991991
[`Try`]: core::ops::Try
992992
[copies or moves]: ../expressions.md#moved-and-copied-types
993993
[dropping]: ../destructors.md
994+
[eval order test]: https://github.com/rust-lang/rust/blob/1.58.0/src/test/ui/expr/compound-assignment/eval-order.rs
994995
[explicit discriminants]: ../items/enumerations.md#explicit-discriminants
995996
[field-less enums]: ../items/enumerations.md#field-less-enum
996997
[grouped expression]: grouped-expr.md
@@ -1007,10 +1008,10 @@ Like assignment expressions, compound assignment expressions always produce [the
10071008
[Unit-only enums]: ../items/enumerations.md#unit-only-enum
10081009
[value expression]: ../expressions.md#place-expressions-and-value-expressions
10091010
[temporary value]: ../expressions.md#temporaries
1010-
[this test]: https://github.com/rust-lang/rust/blob/1.58.0/src/test/ui/expr/compound-assignment/eval-order.rs
10111011
[float-float]: https://github.com/rust-lang/rust/issues/15536
10121012
[Function pointer]: ../types/function-pointer.md
10131013
[Function item]: ../types/function-item.md
1014+
[receiver]: expr.method.intro
10141015
[undefined behavior]: ../behavior-considered-undefined.md
10151016
[Underscore expressions]: ./underscore-expr.md
10161017
[range expressions]: ./range-expr.md

0 commit comments

Comments
 (0)