运算符表达式

语法
OperatorExpression :
      BorrowExpression
   | 解引用表达式 (DereferenceExpression)
   | 错误传播表达式 (ErrorPropagationExpression)
   | 求反表达式 (NegationExpression)
   | 算术或逻辑表达式 (ArithmeticOrLogicalExpression)
   | 比较表达式 (ComparisonExpression)
   | 惰性布尔表达式 (LazyBooleanExpression)
   | 类型转换表达式 (TypeCastExpression)
   | 赋值表达式 (AssignmentExpression)
   | 复合赋值表达式 (CompoundAssignmentExpression)

Rust 语言为内置类型定义了运算符。

下面许多运算符还可以使用 std::opsstd::cmp 中的 trait 进行重载。

溢出

在调试模式下编译时,整数运算符溢出时会发生 panic。可以使用 -C debug-assertions-C overflow-checks 编译器标志更直接地控制此行为。以下情况被视为溢出

  • +* 或二元 - 创建的值大于最大可存储值或小于最小可存储值时。
  • 对任何有符号整数类型的最小负值应用一元 - 运算符,除非操作数是字面量表达式(或独立存在于一个或多个分组表达式内的字面量表达式)。
  • 使用 /%,其中左操作数是有符号整数类型的最小整数,右操作数是 -1。出于历史原因,即使禁用 -C overflow-checks,这些检查也会发生。
  • 使用 <<>>,其中右操作数大于或等于左操作数类型中的位数,或者为负数。

注意

一元 - 后跟字面量表达式的例外情况意味着像 -128_i8let j: i8 = -(128) 这样的形式永远不会导致 panic,并且具有期望的值 -128。

在这些情况下,字面量表达式已经具有其类型的最小负值(例如,128_i8 的值为 -128),因为整数字面量会根据整数字面量表达式中的描述被截断为其类型。

由于使用二进制补码表示,对这些最小负值进行求反会保持值不变。

rustc 中,这些最小负值表达式也会被 overflowing_literals lint 检查忽略。

借用运算符

语法
BorrowExpression :
      (&|&&) 表达式 (Expression)
   | (&|&&) mut 表达式 (Expression)
   | (&|&&) raw const 表达式 (Expression)
   | (&|&&) raw mut 表达式 (Expression)

&(共享借用)和 &mut(可变借用)运算符是一元前缀运算符。

当应用于place 表达式时,此表达式生成一个引用(指针),指向该值所引用的位置。

该内存位置在引用的整个生命周期内也处于借用状态。对于共享借用(&),这意味着该位置不能被修改,但可以被读取或再次共享。对于可变借用(&mut),在借用到期之前,不能以任何方式访问该位置。

&mut 在可变的 place 表达式上下文中评估其操作数。

如果 &&mut 运算符应用于value 表达式,则会创建一个临时值

这些运算符不能被重载。

#![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;
}
}

即使 && 是一个单独的 token(惰性“and”运算符),但在借用表达式的上下文中,它被视为两次借用

#![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 const&raw mut裸指针借用运算符

这些运算符的操作数表达式在 place 表达式上下文中评估。

&raw const expr 然后创建一个指向给定 place 的类型为 *const T 的常量裸指针,而 &raw mut expr 创建一个类型为 *mut T 的可变裸指针。

每当 place 表达式可能评估为一个未正确对齐或未存储由其类型确定的有效值的位置时,或者每当创建引用会引入不正确的别名假设时,必须使用裸指针借用运算符代替常规借用运算符。在这些情况下,使用常规借用运算符会通过创建无效引用而导致未定义行为,但仍然可以构建裸指针。

以下是创建通过 packed 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);
}

以下是创建指向不包含有效值的位置的裸指针的示例

#![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() };
}

解引用运算符

语法
DereferenceExpression :
   * 表达式 (Expression)

*(解引用)运算符也是一元前缀运算符。

当应用于指针时,它表示指向的位置。

如果表达式的类型是 &mut T*mut T,并且是局部变量、局部变量的(嵌套)字段或可变的place 表达式,则由此产生的内存位置可以被赋值。

解引用裸指针需要 unsafe

对于非指针类型,在不可变 place 表达式上下文中,*x 等价于 *std::ops::Deref::deref(&x);在可变 place 表达式上下文中,等价于 *std::ops::DerefMut::deref_mut(&mut x)

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

问号运算符

语法
ErrorPropagationExpression :
   表达式 (Expression) ?

问号运算符(?)用于解包有效值或返回错误值,并将错误传播到调用函数。

它是一元后缀运算符,只能应用于类型 Result<T, E>Option<T>

当应用于 Result<T, E> 类型的值时,它会传播错误。

如果值为 Err(e),则会从包含的函数或闭包中返回 Err(From::from(e))

如果应用于 Ok(x),则会解包该值,评估结果为 x

#![allow(unused)]
fn main() {
use std::num::ParseIntError;
fn try_to_parse() -> Result<i32, ParseIntError> {
    let x: i32 = "123".parse()?; // x = 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())
}

当应用于 Option<T> 类型的值时,它会传播 None

如果值为 None,则会返回 None

如果应用于 Some(x),则会解包该值,评估结果为 x

#![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);
}

? 不能被重载。

求反运算符

语法
NegationExpression :
      - 表达式 (Expression)
   | ! 表达式 (Expression)

这是最后两个一元运算符。

此表总结了它们在基本类型上的行为以及用于重载这些运算符以处理其他类型的 trait。请记住,有符号整数始终使用二进制补码表示。所有这些运算符的操作数都在value 表达式上下文中进行评估,因此会被移动或复制。

符号整数bool浮点数重载 Trait
-求负*求反std::ops::Neg
!按位非逻辑非std::ops::Not

* 仅适用于有符号整数类型。

以下是这些运算符的一些示例

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

算术和逻辑二元运算符

语法
ArithmeticOrLogicalExpression :
      表达式 (Expression) + 表达式 (Expression)
   | 表达式 (Expression) - 表达式 (Expression)
   | 表达式 (Expression) * 表达式 (Expression)
   | 表达式 (Expression) / 表达式 (Expression)
   | 表达式 (Expression) % 表达式 (Expression)
   | 表达式 (Expression) & 表达式 (Expression)
   | 表达式 (Expression) | 表达式 (Expression)
   | 表达式 (Expression) ^ 表达式 (Expression)
   | 表达式 (Expression) << 表达式 (Expression)
   | 表达式 (Expression) >> 表达式 (Expression)

二元运算符表达式都使用中缀表示法书写。

此表总结了算术和逻辑二元运算符在基本类型上的行为以及用于重载这些运算符以处理其他类型的 trait。请记住,有符号整数始终使用二进制补码表示。所有这些运算符的操作数都在value 表达式上下文中进行评估,因此会被移动或复制。

符号整数bool浮点数重载 Trait重载复合赋值 Trait
+加法加法std::ops::Addstd::ops::AddAssign
-减法减法std::ops::Substd::ops::SubAssign
*乘法乘法std::ops::Mulstd::ops::MulAssign
/除法*†除法std::ops::Divstd::ops::DivAssign
%余数**†余数std::ops::Remstd::ops::RemAssign
&按位 AND逻辑 ANDstd::ops::BitAndstd::ops::BitAndAssign
|按位 OR逻辑 ORstd::ops::BitOrstd::ops::BitOrAssign
^按位 XOR逻辑 XORstd::ops::BitXorstd::ops::BitXorAssign
<<左移std::ops::Shlstd::ops::ShlAssign
>>右移***std::ops::Shrstd::ops::ShrAssign

* 整数除法向零取整。

** Rust 使用截断除法定义的余数。给定 remainder = dividend % divisor,余数将与被除数具有相同的符号。

*** 有符号整数类型执行算术右移,无符号整数类型执行逻辑右移。

† 对于整数类型,除以零会 panic。

以下是这些运算符的使用示例。

#![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);
}

比较运算符

语法
ComparisonExpression :
      表达式 (Expression) == 表达式 (Expression)
   | 表达式 (Expression) != 表达式 (Expression)
   | 表达式 (Expression) > 表达式 (Expression)
   | 表达式 (Expression) < 表达式 (Expression)
   | 表达式 (Expression) >= 表达式 (Expression)
   | 表达式 (Expression) <= 表达式 (Expression)

比较运算符也为基本类型和标准库中的许多类型定义。

链式比较运算符时需要使用括号。例如,表达式 a == b == c 是无效的,可以写成 (a == b) == c

与算术和逻辑运算符不同,用于重载这些运算符的 trait 更普遍地用于显示如何比较类型,并且使用这些 trait 作为约束的函数很可能会假定它们定义了实际的比较行为。标准库中的许多函数和宏可以利用这一假设(尽管不能确保安全性)。

与上面的算术和逻辑运算符不同,这些运算符隐式地获取其操作数的共享借用,并在place 表达式上下文中评估它们

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

这意味着操作数不必被移出。

符号含义重载方法
==等于std::cmp::PartialEq::eq
!=不等于std::cmp::PartialEq::ne
>大于std::cmp::PartialOrd::gt
<小于std::cmp::PartialOrd::lt
>=大于等于std::cmp::PartialOrd::ge
<=小于等于std::cmp::PartialOrd::le

以下是比较运算符的使用示例。

#![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");
}

惰性布尔运算符

语法
LazyBooleanExpression :
      表达式 (Expression) || 表达式 (Expression)
   | 表达式 (Expression) && 表达式 (Expression)

运算符 ||&& 可以应用于布尔类型的操作数。|| 运算符表示逻辑“或”,&& 运算符表示逻辑“与”。

它们与 |& 的区别在于,右操作数仅在左操作数不能确定表达式结果时才会被评估。也就是说,|| 仅在其左操作数评估为 false 时才评估其右操作数,而 && 仅在其评估为 true 时才评估其右操作数。

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

类型转换表达式

语法
TypeCastExpression :
   表达式 (Expression) as 无约束类型 (TypeNoBounds)

类型转换表达式使用二元运算符 as 表示。

执行 as 表达式将左侧的值转换为右侧的类型。

as 表达式的示例

#![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 可用于显式执行强制类型转换(coercion)以及以下附加的类型转换。任何不符合强制类型转换规则或表中条目的类型转换都会导致编译器错误。这里 *T 表示 *const T*mut Tm 代表引用类型中可选的 mut 以及指针类型中的 mutconst

e 的类型Ue as U 执行的转换
整数或浮点类型整数或浮点类型数值转换
枚举整数类型枚举转换
boolchar整数类型基本类型到整数转换
u8charu8char 转换
*T*V 1指针到指针转换
*T,其中 T: Sized整数类型指针到地址转换
整数类型*V,其中 V: Sized地址到指针转换
&m₁ [T; n]*m₂ T 2数组到指针转换
*m₁ [T; n]*m₂ T 2数组到指针转换
函数项函数指针函数项到函数指针转换
函数项*V,其中 V: Sized函数项到指针转换
函数项整数函数项到地址转换
函数指针*V,其中 V: Sized函数指针到指针转换
函数指针整数函数指针到地址转换
闭包 3函数指针闭包到函数指针转换
1

其中 TV 具有兼容的元数据

  • V: Sized,或
  • 都是切片元数据(*[u16] -> *[u8], *str -> *(u8, [u32])),或
  • 都是相同的 trait object 元数据,但可以丢弃自动 trait(*dyn Debug -> *(u16, dyn Debug), *dyn Debug + Send -> *dyn Debug
    • 注意:*添加*自动 trait 仅在主 trait 将该自动 trait 作为父 trait 时才允许(例如,给定 trait T: Send {},则 *dyn T -> *dyn T + Send 有效,但 *dyn Debug -> *dyn Debug + Send 无效)
    • 注意:泛型(包括生命周期)必须匹配(*dyn T<'a, A> -> *dyn T<'b, B> 要求 'a = 'bA = B
2

仅当 m₁mutm₂const 时。允许将 mut 引用/指针转换为 const 指针。

3

仅不捕获(close over)任何局部变量的闭包才能转换为函数指针。

语义

数值转换

  • 在相同大小的两个整数之间进行转换(例如 i32 -> u32)是无操作的(Rust 对固定大小整数的负值使用二进制补码表示)

    #![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);
    }
  • 从较大的整数转换为较小的整数(例如 u32 -> u8)会截断

    #![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);
    }
  • 从较小的整数转换为较大的整数(例如 u8 -> u32)会

    • 如果源是无符号类型,则进行零扩展
    • 如果源是有符号类型,则进行符号扩展
    #![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");
    }
  • 从浮点数转换为整数会向零舍入

    • NaN 将返回 0
    • 大于整数最大值的值,包括 INFINITY,将饱和到整数类型的最大值。
    • 小于整数最小值的值,包括 NEG_INFINITY,将饱和到整数类型的最小值。
    #![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);
    }
  • 从整数转换为浮点数将生成最接近的浮点数 *

    • 如有必要,舍入根据 roundTiesToEven 模式进行 ***
    • 溢出时,生成无穷大(与输入符号相同)
    • 注意:对于当前数值类型集合,溢出仅可能发生在 u128 as f32 转换中,对于大于或等于 f32::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);
    }
  • 从 f32 转换为 f64 是完美的且无损的

    #![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());
    }
  • 从 f64 转换为 f32 将生成最接近的 f32 **

    • 如有必要,舍入根据 roundTiesToEven 模式进行 ***
    • 溢出时,生成无穷大(与输入符号相同)
    #![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());
    }

* 如果硬件本身不支持这种舍入模式和溢出行为的整数到浮点转换,则这些转换可能会比预期慢。

** 如果硬件本身不支持这种舍入模式和溢出行为的 f64 到 f32 转换,则这些转换可能会比预期慢。

*** 根据 IEEE 754-2008 §4.3.1 定义:选择最近的浮点数,如果恰好位于两个浮点数之间,则优先选择最低有效位为偶数的那个。

枚举转换

将枚举转换为其判别值(discriminant),然后根据需要使用数值转换。转换仅限于以下类型的枚举

#![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);
}

如果枚举实现了 Drop,则不允许进行转换。

基本类型到整数转换

  • false 转换为 0true 转换为 1
  • char 转换为码点(code point)的值,然后根据需要使用数值转换。
#![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);
}

u8char 转换

转换为具有相应码点的 char

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

指针到地址转换

从裸指针转换为整数会产生被引用内存的机器地址。如果整数类型小于指针类型,地址可能会被截断;使用 usize 可以避免这种情况。

地址到指针转换

从整数转换为裸指针会将整数解释为内存地址,并生成一个引用该内存的指针。

警告

这与仍在开发中的 Rust 内存模型交互。通过此转换获得的指针即使在按位上等于有效指针,也可能受到额外的限制。如果未遵循别名规则,解引用此类指针可能会导致未定义行为

合理的地址算术的简单示例

#![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);
}

指针到指针转换

*const T / *mut T 可以转换为 *const U / *mut U,行为如下

  • 如果 TU 都是 sized 类型,则指针不变返回。
  • 如果 TU 都是 unsized 类型,则指针也不变返回。特别是,元数据被精确保留。

    例如,从 *const [T] 转换为 *const [U] 会保留元素的数量。请注意,因此,此类转换不一定保留指针指向对象的大小(例如,将 *const [u16] 转换为 *const [u8] 将导致裸指针指向一个大小为原对象一半的对象)。str 以及任何其 unsized 尾部是切片类型的复合类型(例如 struct Foo(i32, [u8])(u64, Foo))也是如此。

  • 如果 T 是 unsized 类型而 U 是 sized 类型,转换会丢弃补全宽指针 T 的所有元数据,并生成一个由 unsized 指针的数据部分组成的瘦指针 U

赋值表达式

语法
AssignmentExpression :
   表达式 (Expression) = 表达式 (Expression)

一个赋值表达式 将一个值移动到指定的位置。

赋值表达式由一个可变被赋值者表达式(assignee expression)(即被赋值数操作数(assignee operand))后跟等号(=)和一个value 表达式(即被赋值值操作数(assigned value operand))组成。

在其最基本的形式中,被赋值者表达式是一个place 表达式,我们首先讨论这种情况。

更普遍的解构赋值情况将在下面讨论,但这种情况总是分解为对 place 表达式的顺序赋值,这可以被认为是更基础的情况。

基本赋值

评估赋值表达式首先评估其操作数。先评估被赋值值操作数,然后评估被赋值者表达式。

对于解构赋值,被赋值者表达式的子表达式从左到右评估。

注意

这与其他表达式不同,右操作数在左操作数之前评估。

然后,它会首先丢弃(drop)被赋值位置的值,除非该位置是未初始化的局部变量或未初始化的局部变量字段。

接下来,它将复制或移动被赋值值到被赋值位置。

赋值表达式总是产生单元值(unit value)

示例

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

解构赋值

解构赋值是变量声明中解构模式匹配的对应物,允许对复杂值(例如元组或结构体)进行赋值。例如,我们可以交换两个可变变量

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

与使用 let 进行的解构声明不同,由于语法上的歧义,模式不能出现在赋值语句的左侧。取而代之的是,一组对应于模式的表达式被指定为被赋值者表达式,并允许出现在赋值语句的左侧。被赋值者表达式随后被“脱糖(desugar)”为模式匹配后跟顺序赋值。

“脱糖”后的模式必须是不可反驳的(irrefutable):具体来说,这意味着只有其长度在编译时已知的切片模式以及平凡切片 [..] 允许用于解构赋值。

“脱糖”方法很简单,通过示例可以最好地说明。

#![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;
}
}

在单个被赋值者表达式中不禁止多次使用标识符。

下划线表达式和空范围表达式可用于忽略某些值,而无需绑定它们。

请注意,默认绑定模式不适用于“脱糖”后的表达式。

复合赋值表达式

语法
CompoundAssignmentExpression :
      表达式 (Expression) += 表达式 (Expression)
   | 表达式 (Expression) -= 表达式 (Expression)
   | 表达式 (Expression) *= 表达式 (Expression)
   | 表达式 (Expression) /= 表达式 (Expression)
   | 表达式 (Expression) %= 表达式 (Expression)
   | 表达式 (Expression) &= 表达式 (Expression)
   | 表达式 (Expression) |= 表达式 (Expression)
   | 表达式 (Expression) ^= 表达式 (Expression)
   | 表达式 (Expression) <<= 表达式 (Expression)
   | 表达式 (Expression) >>= 表达式 (Expression)

复合赋值表达式 将算术和逻辑二元运算符与赋值表达式结合起来。

例如

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

复合赋值的语法是一个可变place 表达式(即被赋值数操作数(assigned operand)),后跟其中一个运算符和一个 = 作为一个单独的 token(无空格),然后是一个value 表达式(即修改操作数(modifying operand))。

与其他 place 操作数不同,被赋值 place 操作数必须是一个 place 表达式。

尝试使用 value 表达式会导致编译器错误,而不会将其提升为临时变量。

复合赋值表达式的评估顺序取决于运算符的类型。

如果两种类型都是基本类型,则先评估修改操作数,然后评估被赋值操作数。然后,它将使用被赋值操作数和修改操作数的值执行运算符指定的操作,并将结果值设置到被赋值操作数的位置。

注意

这与其他表达式不同,右操作数在左操作数之前评估。

否则,此表达式是调用运算符对应的重载复合赋值 trait 函数的语法糖(参见本章前面的表格)。会自动获取被赋值操作数的可变借用。

例如,example 中的以下表达式语句是等价的

#![allow(unused)]
fn main() {
struct Addable;
use std::ops::AddAssign;

impl AddAssign<Addable> for Addable {
    /* */
fn add_assign(&mut self, other: Addable) {}
}

fn example() {
let (mut a1, a2) = (Addable, Addable);
  a1 += a2;

let (mut a1, a2) = (Addable, Addable);
  AddAssign::add_assign(&mut a1, a2);
}
}

与赋值表达式一样,复合赋值表达式总是产生单元值(unit value)

警告

操作数的评估顺序取决于操作数的类型:对于基本类型,先评估右侧,而对于非基本类型,先评估左侧。尽量不要编写依赖于复合赋值表达式中操作数评估顺序的代码。有关使用此依赖关系的示例,请参阅此测试