Movatterモバイル変換


[0]ホーム

URL:


Keyboard shortcuts

Press or to navigate between chapters

PressS or/ to search in the book

Press? to show this help

PressEsc to hide this help

The Rust Reference

    Operator expressions

    Operators are defined for built in types by the Rust language.

    Many of the following operators can also be overloaded using traits instd::ops orstd::cmp.

    Overflow

    Integer operators will panic when they overflow when compiled in debug mode.The-C debug-assertions and-C overflow-checks compiler flags can be used to control this more directly.The following things are considered to be overflow:

    • When+,* or binary- create a value greater than the maximum value, or less than the minimum value that can be stored.
    • Applying unary- to the most negative value of any signed integer type, unless the operand is aliteral expression (or a literal expression standing alone inside one or moregrouped expressions).
    • Using/ or%, where the left-hand argument is the smallest integer of a signed integer type and the right-hand argument is-1.These checks occur even when-C overflow-checks is disabled, for legacy reasons.
    • Using<< or>> where the right-hand argument is greater than or equal to the number of bits in the type of the left-hand argument, or is negative.

    Note

    The exception for literal expressions behind unary- means that forms such as-128_i8 orlet j: i8 = -(128) never cause a panic and have the expected value of -128.

    In these cases, the literal expression already has the most negative value for its type (for example,128_i8 has the value -128) because integer literals are truncated to their type per the description inInteger literal expressions.

    Negation of these most negative values leaves the value unchanged due to two’s complement overflow conventions.

    Inrustc, these most negative expressions are also ignored by theoverflowing_literals lint check.

    Borrow operators

    Syntax
    BorrowExpression
          (& |&& )Expression
        | (& |&& )mutExpression
        | (& |&& )rawconstExpression
        | (& |&& )rawmutExpression

    The& (shared borrow) and&mut (mutable borrow) operators are unary prefix operators.

    When applied to aplace expression, this expressions produces a reference (pointer) to the location that the value refers to.

    The memory location is also placed into a borrowed state for the duration of the reference.For a shared borrow (&), this implies that the place may not be mutated, but it may be read or shared again.For a mutable borrow (&mut), the place may not be accessed in any way until the borrow expires.

    &mut evaluates its operand in a mutable place expression context.

    If the& or&mut operators are applied to avalue expression, then atemporary value is created.

    These operators cannot be overloaded.

    #![allow(unused)]fn main() {{    // a temporary with value 7 is created that lasts for this scope.    let shared_reference = &7;}let mut array = [-2, 3, 9];{    // Mutably borrows `array` for this scope.    // `array` may only be used through `mutable_reference`.    let mutable_reference = &mut array;}}

    Even though&& is a single token (the lazy ‘and’ operator), when used in the context of borrow expressions it works as two borrows:

    #![allow(unused)]fn main() {// same meanings:let a = &&  10;let a = & & 10;// same meanings:let a = &&&&  mut 10;let a = && && mut 10;let a = & & & & mut 10;}

    Raw borrow operators

    &raw const and&raw mut are theraw borrow operators.

    The operand expression of these operators is evaluated in place expression context.

    &raw const expr then creates a const raw pointer of type*const T to the given place, and&raw mut expr creates a mutable raw pointer of type*mut T.

    The raw borrow operators must be used instead of a borrow operator whenever the place expression could evaluate to a place that is not properly aligned or does not store a valid value as determined by its type, or whenever creating a reference would introduce incorrect aliasing assumptions.In those situations, using a borrow operator would causeundefined behavior by creating an invalid reference, but a raw pointer may still be constructed.

    The following is an example of creating a raw pointer to an unaligned place through apacked struct:

    #![allow(unused)]fn main() {#[repr(packed)]struct Packed {    f1: u8,    f2: u16,}let packed = Packed { f1: 1, f2: 2 };// `&packed.f2` would create an unaligned reference, and thus be undefined behavior!let raw_f2 = &raw const packed.f2;assert_eq!(unsafe { raw_f2.read_unaligned() }, 2);}

    The following is an example of creating a raw pointer to a place that does not contain a valid value:

    #![allow(unused)]fn main() {use std::mem::MaybeUninit;struct Demo {    field: bool,}let mut uninit = MaybeUninit::<Demo>::uninit();// `&uninit.as_mut().field` would create a reference to an uninitialized `bool`,// and thus be undefined behavior!let f1_ptr = unsafe { &raw mut (*uninit.as_mut_ptr()).field };unsafe { f1_ptr.write(true); }let init = unsafe { uninit.assume_init() };}

    The dereference operator

    Syntax
    DereferenceExpression*Expression

    The* (dereference) operator is also a unary prefix operator.

    When applied to apointer it denotes the pointed-to location.

    If the expression is of type&mut T or*mut T, and is either a local variable, a (nested) field of a local variable or is a mutableplace expression, then the resulting memory location can be assigned to.

    Dereferencing a raw pointer requiresunsafe.

    On non-pointer types*x is equivalent to*std::ops::Deref::deref(&x) in animmutable place expression context and*std::ops::DerefMut::deref_mut(&mut x) in a mutable place expression context.

    #![allow(unused)]fn main() {let x = &7;assert_eq!(*x, 7);let y = &mut 9;*y = 11;assert_eq!(*y, 11);}

    The try propagation expression

    Syntax
    TryPropagationExpressionExpression?

    The try propagation expression uses the value of the inner expression and theTry trait to decide whether to produce a value, and if so, what value to produce, or whether to return a value to the caller, and if so, what value to return.

    Example

    #![allow(unused)]fn main() {use std::num::ParseIntError;fn try_to_parse() -> Result<i32, ParseIntError> {    let x: i32 = "123".parse()?; // `x` is `123`.    let y: i32 = "24a".parse()?; // Returns an `Err()` immediately.    Ok(x + y)                    // Doesn't run.}let res = try_to_parse();println!("{res:?}");assert!(res.is_err())}
    #![allow(unused)]fn main() {fn try_option_some() -> Option<u8> {    let val = Some(1)?;    Some(val)}assert_eq!(try_option_some(), Some(1));fn try_option_none() -> Option<u8> {    let val = None?;    Some(val)}assert_eq!(try_option_none(), None);}
    use std::ops::ControlFlow;pub struct TreeNode<T> {    value: T,    left: Option<Box<TreeNode<T>>>,    right: Option<Box<TreeNode<T>>>,}impl<T> TreeNode<T> {    pub fn traverse_inorder<B>(&self, f: &mut impl FnMut(&T) -> ControlFlow<B>) -> ControlFlow<B> {        if let Some(left) = &self.left {            left.traverse_inorder(f)?;        }        f(&self.value)?;        if let Some(right) = &self.right {            right.traverse_inorder(f)?;        }        ControlFlow::Continue(())    }}fn main() {    let n = TreeNode {        value: 1,        left: Some(Box::new(TreeNode{value: 2, left: None, right: None})),        right: None,    };    let v = n.traverse_inorder(&mut |t| {        if *t == 2 {            ControlFlow::Break("found")        } else {            ControlFlow::Continue(())        }    });    assert_eq!(v, ControlFlow::Break("found"));}

    Note

    TheTry trait is currently unstable, and thus cannot be implemented for user types.

    The try propagation expression is currently roughly equivalent to:

    #![allow(unused)]fn main() {#![ feature(try_trait_v2) ]fn example() -> Result<(), ()> {let expr = Ok(());match core::ops::Try::branch(expr) {    core::ops::ControlFlow::Continue(val) => val,    core::ops::ControlFlow::Break(residual) =>        return core::ops::FromResidual::from_residual(residual),}Ok(())}}

    Note

    The try propagation operator is sometimes calledthe question mark operator,the? operator, orthe try operator.

    The try propagation operator can be applied to expressions with the type of:

    • Result<T, E>
      • Result::Ok(val) evaluates toval.
      • Result::Err(e) returnsResult::Err(From::from(e)).
    • Option<T>
      • Option::Some(val) evaluates toval.
      • Option::None returnsOption::None.
    • ControlFlow<B, C>
      • ControlFlow::Continue(c) evaluates toc.
      • ControlFlow::Break(b) returnsControlFlow::Break(b).
    • Poll<Result<T, E>>
      • Poll::Ready(Ok(val)) evaluates toPoll::Ready(val).
      • Poll::Ready(Err(e)) returnsPoll::Ready(Err(From::from(e))).
      • Poll::Pending evaluates toPoll::Pending.
    • Poll<Option<Result<T, E>>>
      • Poll::Ready(Some(Ok(val))) evaluates toPoll::Ready(Some(val)).
      • Poll::Ready(Some(Err(e))) returnsPoll::Ready(Some(Err(From::from(e)))).
      • Poll::Ready(None) evaluates toPoll::Ready(None).
      • Poll::Pending evaluates toPoll::Pending.

    Negation operators

    Syntax
    NegationExpression
          -Expression
        |!Expression

    These are the last two unary operators.

    This table summarizes the behavior of them on primitive types and which traits are used to overload these operators for other types.Remember that signed integers are always represented using two’s complement.The operands of all of these operators are evaluated invalue expression context so are moved or copied.

    SymbolIntegerboolFloating PointOverloading Trait
    -Negation*Negationstd::ops::Neg
    !Bitwise NOTLogical NOTstd::ops::Not

    * Only for signed integer types.

    Here are some example of these operators

    #![allow(unused)]fn main() {let x = 6;assert_eq!(-x, -6);assert_eq!(!x, -7);assert_eq!(true, !false);}

    Arithmetic and Logical Binary Operators

    Binary operators expressions are all written with infix notation.

    This table summarizes the behavior of arithmetic and logical binary operators on primitive types and which traits are used to overload these operators for other types.Remember that signed integers are always represented using two’s complement.The operands of all of these operators are evaluated invalue expression context so are moved or copied.

    SymbolIntegerboolFloating PointOverloading TraitOverloading Compound Assignment Trait
    +AdditionAdditionstd::ops::Addstd::ops::AddAssign
    -SubtractionSubtractionstd::ops::Substd::ops::SubAssign
    *MultiplicationMultiplicationstd::ops::Mulstd::ops::MulAssign
    /Division*†Divisionstd::ops::Divstd::ops::DivAssign
    %Remainder**†Remainderstd::ops::Remstd::ops::RemAssign
    &Bitwise ANDLogical ANDstd::ops::BitAndstd::ops::BitAndAssign
    |Bitwise ORLogical ORstd::ops::BitOrstd::ops::BitOrAssign
    ^Bitwise XORLogical XORstd::ops::BitXorstd::ops::BitXorAssign
    <<Left Shiftstd::ops::Shlstd::ops::ShlAssign
    >>Right Shift***std::ops::Shrstd::ops::ShrAssign

    * Integer division rounds towards zero.

    ** Rust uses a remainder defined withtruncating division. Givenremainder = dividend % divisor, the remainder will have the same sign as the dividend.

    *** Arithmetic right shift on signed integer types, logical right shift onunsigned integer types.

    † For integer types, division by zero panics.

    Here are examples of these operators being used.

    #![allow(unused)]fn main() {assert_eq!(3 + 6, 9);assert_eq!(5.5 - 1.25, 4.25);assert_eq!(-5 * 14, -70);assert_eq!(14 / 3, 4);assert_eq!(100 % 7, 2);assert_eq!(0b1010 & 0b1100, 0b1000);assert_eq!(0b1010 | 0b1100, 0b1110);assert_eq!(0b1010 ^ 0b1100, 0b110);assert_eq!(13 << 3, 104);assert_eq!(-10 >> 2, -3);}

    Comparison Operators

    Comparison operators are also defined both for primitive types and many types in the standard library.

    Parentheses are required when chaining comparison operators. For example, the expressiona == b == c is invalid and may be written as(a == b) == c.

    Unlike arithmetic and logical operators, the traits for overloading these operators are used more generally to show how a type may be compared and will likely be assumed to define actual comparisons by functions that use these traits as bounds.Many functions and macros in the standard library can then use that assumption (although not to ensure safety).

    Unlike the arithmetic and logical operators above, these operators implicitly take shared borrows of their operands, evaluating them inplace expression context:

    #![allow(unused)]fn main() {let a = 1;let b = 1;a == b;// is equivalent to::std::cmp::PartialEq::eq(&a, &b);}

    This means that the operands don’t have to be moved out of.

    SymbolMeaningOverloading method
    ==Equalstd::cmp::PartialEq::eq
    !=Not equalstd::cmp::PartialEq::ne
    >Greater thanstd::cmp::PartialOrd::gt
    <Less thanstd::cmp::PartialOrd::lt
    >=Greater than or equal tostd::cmp::PartialOrd::ge
    <=Less than or equal tostd::cmp::PartialOrd::le

    Here are examples of the comparison operators being used.

    #![allow(unused)]fn main() {assert!(123 == 123);assert!(23 != -12);assert!(12.5 > 12.2);assert!([1, 2, 3] < [1, 3, 4]);assert!('A' <= 'B');assert!("World" >= "Hello");}

    Lazy boolean operators

    Syntax
    LazyBooleanExpression
          Expression||Expression
        |Expression&&Expression

    The operators|| and&& may be applied to operands of boolean type.The|| operator denotes logical ‘or’, and the&& operator denotes logical ‘and’.

    They differ from| and& in that the right-hand operand is only evaluated when the left-hand operand does not already determine the result of the expression.That is,|| only evaluates its right-hand operand when the left-hand operand evaluates tofalse, and&& only when it evaluates totrue.

    #![allow(unused)]fn main() {let x = false || true; // truelet y = false && panic!(); // false, doesn't evaluate `panic!()`}

    Type cast expressions

    A type cast expression is denoted with the binary operatoras.

    Executing anas expression casts the value on the left-hand side to the type on the right-hand side.

    An example of anas expression:

    #![allow(unused)]fn main() {fn sum(values: &[f64]) -> f64 { 0.0 }fn len(values: &[f64]) -> i32 { 0 }fn average(values: &[f64]) -> f64 {    let sum: f64 = sum(values);    let size: f64 = len(values) as f64;    sum / size}}

    as can be used to explicitly performcoercions, as well as the following additional casts.Any cast that does not fit either a coercion rule or an entry in the table is a compiler error.Here*T means either*const T or*mut T.m stands for optionalmut inreference types andmut orconst in pointer types.

    Type ofeUCast performed bye as U
    Integer or Float typeInteger or Float typeNumeric cast
    EnumerationInteger typeEnum cast
    bool orcharInteger typePrimitive to integer cast
    u8charu8 tochar cast
    *T*V1Pointer to pointer cast
    *T whereT: SizedInteger typePointer to address cast
    Integer type*V whereV: SizedAddress to pointer cast
    &m₁ [T; n]*m₂ T2Array to pointer cast
    *m₁ [T; n]*m₂ T2Array to pointer cast
    Function itemFunction pointerFunction item to function pointer cast
    Function item*V whereV: SizedFunction item to pointer cast
    Function itemIntegerFunction item to address cast
    Function pointer*V whereV: SizedFunction pointer to pointer cast
    Function pointerIntegerFunction pointer to address cast
    Closure3Function pointerClosure to function pointer cast

    Semantics

    Numeric cast

    • Casting between two integers of the same size (e.g. i32 -> u32) is a no-op(Rust uses 2’s complement for negative values of fixed integers)

      #![allow(unused)]fn main() {assert_eq!(42i8 as u8, 42u8);assert_eq!(-1i8 as u8, 255u8);assert_eq!(255u8 as i8, -1i8);assert_eq!(-1i16 as u16, 65535u16);}
    • Casting from a larger integer to a smaller integer (e.g. u32 -> u8) willtruncate

      #![allow(unused)]fn main() {assert_eq!(42u16 as u8, 42u8);assert_eq!(1234u16 as u8, 210u8);assert_eq!(0xabcdu16 as u8, 0xcdu8);assert_eq!(-42i16 as i8, -42i8);assert_eq!(1234u16 as i8, -46i8);assert_eq!(0xabcdi32 as i8, -51i8);}
    • Casting from a smaller integer to a larger integer (e.g. u8 -> u32) will

      • zero-extend if the source is unsigned
      • sign-extend if the source is signed
      #![allow(unused)]fn main() {assert_eq!(42i8 as i16, 42i16);assert_eq!(-17i8 as i16, -17i16);assert_eq!(0b1000_1010u8 as u16, 0b0000_0000_1000_1010u16, "Zero-extend");assert_eq!(0b0000_1010i8 as i16, 0b0000_0000_0000_1010i16, "Sign-extend 0");assert_eq!(0b1000_1010u8 as i8 as i16, 0b1111_1111_1000_1010u16 as i16, "Sign-extend 1");}
    • Casting from a float to an integer will round the float towards zero

      • NaN will return0
      • Values larger than the maximum integer value, includingINFINITY, will saturate to the maximum value of the integer type.
      • Values smaller than the minimum integer value, includingNEG_INFINITY, will saturate to the minimum value of the integer type.
      #![allow(unused)]fn main() {assert_eq!(42.9f32 as i32, 42);assert_eq!(-42.9f32 as i32, -42);assert_eq!(42_000_000f32 as i32, 42_000_000);assert_eq!(std::f32::NAN as i32, 0);assert_eq!(1_000_000_000_000_000f32 as i32, 0x7fffffffi32);assert_eq!(std::f32::NEG_INFINITY as i32, -0x80000000i32);}
    • Casting from an integer to float will produce the closest possible float *

      • if necessary, rounding is according toroundTiesToEven mode ***
      • on overflow, infinity (of the same sign as the input) is produced
      • note: with the current set of numeric types, overflow can only happenonu128 as f32 for values greater or equal tof32::MAX + (0.5 ULP)
      #![allow(unused)]fn main() {assert_eq!(1337i32 as f32, 1337f32);assert_eq!(123_456_789i32 as f32, 123_456_790f32, "Rounded");assert_eq!(0xffffffff_ffffffff_ffffffff_ffffffff_u128 as f32, std::f32::INFINITY);}
    • Casting from an f32 to an f64 is perfect and lossless

      #![allow(unused)]fn main() {assert_eq!(1_234.5f32 as f64, 1_234.5f64);assert_eq!(std::f32::INFINITY as f64, std::f64::INFINITY);assert!((std::f32::NAN as f64).is_nan());}
    • Casting from an f64 to an f32 will produce the closest possible f32 **

      • if necessary, rounding is according toroundTiesToEven mode ***
      • on overflow, infinity (of the same sign as the input) is produced
      #![allow(unused)]fn main() {assert_eq!(1_234.5f64 as f32, 1_234.5f32);assert_eq!(1_234_567_891.123f64 as f32, 1_234_567_890f32, "Rounded");assert_eq!(std::f64::INFINITY as f32, std::f32::INFINITY);assert!((std::f64::NAN as f32).is_nan());}

    * if integer-to-float casts with this rounding mode and overflow behavior arenot supported natively by the hardware, these casts will likely be slower thanexpected.

    ** if f64-to-f32 casts with this rounding mode and overflow behavior are notsupported natively by the hardware, these casts will likely be slower thanexpected.

    *** as defined in IEEE 754-2008 §4.3.1: pick the nearest floating pointnumber, preferring the one with an even least significant digit if exactlyhalfway between two floating point numbers.

    Enum cast

    Casts an enum to its discriminant, then uses a numeric cast if needed.Casting is limited to the following kinds of enumerations:

    #![allow(unused)]fn main() {enum Enum { A, B, C }assert_eq!(Enum::A as i32, 0);assert_eq!(Enum::B as i32, 1);assert_eq!(Enum::C as i32, 2);}

    Casting is not allowed if the enum implementsDrop.

    Primitive to integer cast

    • false casts to0,true casts to1
    • char casts to the value of the code point, then uses a numeric cast if needed.
    #![allow(unused)]fn main() {assert_eq!(false as i32, 0);assert_eq!(true as i32, 1);assert_eq!('A' as i32, 65);assert_eq!('Ö' as i32, 214);}

    u8 tochar cast

    Casts to thechar with the corresponding code point.

    #![allow(unused)]fn main() {assert_eq!(65u8 as char, 'A');assert_eq!(214u8 as char, 'Ö');}

    Pointer to address cast

    Casting from a raw pointer to an integer produces the machine address of the referenced memory.If the integer type is smaller than the pointer type, the address may be truncated; usingusize avoids this.

    Address to pointer cast

    Casting from an integer to a raw pointer interprets the integer as a memory address and produces a pointer referencing that memory.

    Warning

    This interacts with the Rust memory model, which is still under development.A pointer obtained from this cast may suffer additional restrictions even if it is bitwise equal to a valid pointer.Dereferencing such a pointer may beundefined behavior if aliasing rules are not followed.

    A trivial example of sound address arithmetic:

    #![allow(unused)]fn main() {let mut values: [i32; 2] = [1, 2];let p1: *mut i32 = values.as_mut_ptr();let first_address = p1 as usize;let second_address = first_address + 4; // 4 == size_of::<i32>()let p2 = second_address as *mut i32;unsafe {    *p2 += 1;}assert_eq!(values[1], 3);}

    Pointer-to-pointer cast

    *const T /*mut T can be cast to*const U /*mut U with the following behavior:

    • IfT andU are both sized, the pointer is returned unchanged.
    • IfT andU are both unsized, the pointer is also returned unchanged.In particular, the metadata is preserved exactly.

      For instance, a cast from*const [T] to*const [U] preserves the number of elements.Note that, as a consequence, such casts do not necessarily preserve the size of the pointer’s referent(e.g., casting*const [u16] to*const [u8] will result in a raw pointer which refers to an object of half the size of the original).The same holds forstr and any compound type whose unsized tail is a slice type,such asstruct Foo(i32, [u8]) or(u64, Foo).

    • IfT is unsized andU is sized, the cast discards all metadata that completes the wide pointerT and produces a thin pointerU consisting of the data part of the unsized pointer.

    Assignment expressions

    Anassignment expression moves a value into a specified place.

    An assignment expression consists of amutableassignee expression, theassignee operand, followed by an equals sign (=) and avalue expression, theassigned value operand.

    In its most basic form, an assignee expression is aplace expression, and we discuss this case first.

    The more general case of destructuring assignment is discussed below, but this case always decomposes into sequential assignments to place expressions, which may be considered the more fundamental case.

    Basic assignments

    Evaluating assignment expressions begins by evaluating its operands.The assigned value operand is evaluated first, followed by the assignee expression.

    For destructuring assignment, subexpressions of the assignee expression are evaluated left-to-right.

    Note

    This is different than other expressions in that the right operand is evaluated before the left one.

    It then has the effect of firstdropping the value at the assigned place, unless the place is an uninitialized local variable or an uninitialized field of a local variable.

    Next it eithercopies or moves the assigned value to the assigned place.

    An assignment expression always producesthe unit value.

    Example:

    #![allow(unused)]fn main() {let mut x = 0;let y = 0;x = y;}

    Destructuring assignments

    Destructuring assignment is a counterpart to destructuring pattern matches for variable declaration, permitting assignment to complex values, such as tuples or structs.For instance, we may swap two mutable variables:

    #![allow(unused)]fn main() {let (mut a, mut b) = (0, 1);// Swap `a` and `b` using destructuring assignment.(b, a) = (a, b);}

    In contrast to destructuring declarations usinglet, patterns may not appear on the left-hand side of an assignment due to syntactic ambiguities.Instead, a group of expressions that correspond to patterns are designated to beassignee expressions, and permitted on the left-hand side of an assignment.Assignee expressions are then desugared to pattern matches followed by sequential assignment.

    The desugared patterns must be irrefutable: in particular, this means that only slice patterns whose length is known at compile-time, and the trivial slice[..], are permitted for destructuring assignment.

    The desugaring method is straightforward, and is illustrated best by example.

    #![allow(unused)]fn main() {struct Struct { x: u32, y: u32 }let (mut a, mut b) = (0, 0);(a, b) = (3, 4);[a, b] = [3, 4];Struct { x: a, y: b } = Struct { x: 3, y: 4};// desugars to:{    let (_a, _b) = (3, 4);    a = _a;    b = _b;}{    let [_a, _b] = [3, 4];    a = _a;    b = _b;}{    let Struct { x: _a, y: _b } = Struct { x: 3, y: 4};    a = _a;    b = _b;}}

    Identifiers are not forbidden from being used multiple times in a single assignee expression.

    Underscore expressions and emptyrange expressions may be used to ignore certain values, without binding them.

    Note that default binding modes do not apply for the desugared expression.

    Compound assignment expressions

    Compound assignment expressions combine arithmetic and logical binary operators with assignment expressions.

    For example:

    #![allow(unused)]fn main() {let mut x = 5;x += 1;assert!(x == 6);}

    The syntax of compound assignment is amutableplace expression, theassigned operand, then one of the operators followed by an= as a single token (no whitespace), and then avalue expression, themodifying operand.

    Unlike other place operands, the assigned place operand must be a place expression.

    Attempting to use a value expression is a compiler error rather than promoting it to a temporary.

    Evaluation of compound assignment expressions depends on the types of the operands.

    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.

    use core::{num::Wrapping, ops::AddAssign};trait Equate {}impl<T> Equate for (T, T) {}fn f1(x: (u8,)) {    let mut order = vec![];    // The RHS is evaluated first as both operands are of primitive    // type.    { order.push(2); x }.0 += { order.push(1); x }.0;    assert!(order.is_sorted());}fn f2(x: (Wrapping<u8>,)) {    let mut order = vec![];    // The LHS is evaluated first as `Wrapping<_>` is not a primitive    // type.    { order.push(1); x }.0 += { order.push(2); (0u8,) }.0;    assert!(order.is_sorted());}fn f3<T: AddAssign<u8> + Copy>(x: (T,)) where (T, u8): Equate {    let mut order = vec![];    // The LHS is evaluated first as one of the operands is a generic    // parameter, even though that generic parameter can be unified    // with a primitive type due to the where clause bound.    { order.push(1); x }.0 += { order.push(2); (0u8,) }.0;    assert!(order.is_sorted());}fn main() {    f1((0u8,));    f2((Wrapping(0u8),));    // We supply a primitive type as the generic argument, but this    // does not affect the evaluation order in `f3` when    // monomorphized.    f3::<u8>((0u8,));}

    Note

    This is unusual. Elsewhere left to right evaluation is the norm.

    See theeval order test for more examples.

    Otherwise, this expression is syntactic sugar for using the corresponding trait for the operator (seeexpr.arith-logic.behavior) and calling its method with the left hand side as thereceiver and the right hand side as the next argument.

    For example, the following two statements are equivalent:

    #![allow(unused)]fn main() {use std::ops::AddAssign;fn f<T: AddAssign + Copy>(mut x: T, y: T) {    x += y; // Statement 1.    x.add_assign(y); // Statement 2.}}

    Note

    Surprisingly, desugaring this further to a fully qualified method call is not equivalent, as there is special borrow checker behavior when the mutable reference to the first operand is taken viaautoref.

    #![allow(unused)]fn main() {use std::ops::AddAssign;fn f<T: AddAssign + Copy>(mut x: T) {    // Here we used `x` as both the LHS and the RHS. Because the    // mutable borrow of the LHS needed to call the trait method    // is taken implicitly by autoref, this is OK.    x += x; //~ OK    x.add_assign(x); //~ OK}}
    #![allow(unused)]fn main() {use std::ops::AddAssign;fn f<T: AddAssign + Copy>(mut x: T) {    // We can't desugar the above to the below, as once we take the    // mutable borrow of `x` to pass the first argument, we can't    // pass `x` by value in the second argument because the mutable    // reference is still live.    <T as AddAssign>::add_assign(&mut x, x);    //~^ ERROR cannot use `x` because it was mutably borrowed}}
    #![allow(unused)]fn main() {use std::ops::AddAssign;fn f<T: AddAssign + Copy>(mut x: T) {    // As above.    (&mut x).add_assign(x);    //~^ ERROR cannot use `x` because it was mutably borrowed}}

    As with normal assignment expressions, compound assignment expressions always producethe unit value.

    Warning

    Avoid writing code that depends on the evaluation order of operands in compound assignments as it can be unusual and surprising.


    1. whereT andV have compatible metadata:

      • V: Sized, or
      • Both slice metadata (*[u16] ->*[u8],*str ->*(u8, [u32])), or
      • Both the same trait object metadata, modulo dropping auto traits (*dyn Debug ->*(u16, dyn Debug),*dyn Debug + Send ->*dyn Debug)
        • Note:adding auto traits is only allowed if the principal trait has the auto trait as a super trait (giventrait T: Send {},*dyn T ->*dyn T + Send is valid, but*dyn Debug ->*dyn Debug + Send is not)
        • Note: Generics (including lifetimes) must match (*dyn T<'a, A> ->*dyn T<'b, B> requires'a = 'b andA = B)
    2. only whenm₁ ismut orm₂ isconst. Castingmut reference/pointer toconst pointer is allowed.↩2

    3. only for closures that do not capture (close over) any local variables can be casted to function pointers.


    [8]ページ先頭

    ©2009-2025 Movatter.jp