Операторные выражения
Syntax
OperatorExpression →
BorrowExpression
| DereferenceExpression
| TryPropagationExpression
| NegationExpression
| ArithmeticOrLogicalExpression
| ComparisonExpression
| LazyBooleanExpression
| TypeCastExpression
| AssignmentExpression
| CompoundAssignmentExpression
Операторы определены для встроенных типов языком Rust.
Многие из следующих операторов также могут быть перегружены с использованием трейтов из std::ops или std::cmp.
Переполнение
Целочисленные операторы вызывают панику при переполнении при компиляции в режиме отладки.
Флаги компилятора -C debug-assertions и -C overflow-checks могут быть использованы для более прямого контроля над этим.
Следующие ситуации считаются переполнением:
- Когда
+,*или бинарный-создают значение больше максимального или меньше минимального значения, которое может быть сохранено.
- Применение унарного
-к самому отрицательному значению любого знакового целочисленного типа, если операнд не является выражением-литералом (или выражением-литералом, стоящим отдельно внутри одной или нескольких группированных выражений).
- Использование
/или%, где левый аргумент является наименьшим целым числом знакового целочисленного типа, а правый аргумент равен-1. Эти проверки происходят даже когда-C overflow-checksотключен, по историческим причинам.
- Использование
<<или>>, где правый аргумент больше или равен количеству битов в типе левого аргумента, или отрицателен.
Note
Исключение для выражений-литералов после унарного
-означает, что формы типа-128_i8илиlet j: i8 = -(128)никогда не вызывают панику и имеют ожидаемое значение -128.В этих случаях выражение-литерал уже имеет самое отрицательное значение для своего типа (например,
128_i8имеет значение -128), потому что целочисленные литералы усекаются до своего типа в соответствии с описанием в Целочисленные выражения-литералы.Отрицание этих самых отрицательных значений оставляет значение неизменным из-за соглашений о переполнении в дополнительном коде.
В
rustcэти самые отрицательные выражения также игнорируются проверкой линтераoverflowing_literals.
Операторы заимствования
Syntax
BorrowExpression →
( & | && ) Expression
| ( & | && ) mut Expression
| ( & | && ) raw const Expression
| ( & | && ) raw mut Expression
Операторы & (разделяемое заимствование) и &mut (изменяемое заимствование) являются унарными префиксными операторами.
При применении к выражению-месту эти выражения производят ссылку (указатель) на местоположение, на которое ссылается значение.
Область памяти также переводится в состояние заимствования на время жизни ссылки.
Для разделяемого заимствования (&) это подразумевает, что место не может быть изменено, но может быть прочитано или снова разделено.
Для изменяемого заимствования (&mut) место не может быть доступно никаким образом до истечения заимствования.
&mut вычисляет свой операнд в контексте изменяемого выражения-места.
Если операторы & или &mut применяются к выражению-значению, то создается временное значение.
Эти операторы не могут быть перегружены.
#![allow(unused)] fn main() { { // создается временное значение 7, которое длится в этой области видимости. let shared_reference = &7; } let mut array = [-2, 3, 9]; { // Изменяемо заимствует `array` для этой области видимости. // `array` может использоваться только через `mutable_reference`. let mutable_reference = &mut array; } }
Несмотря на то, что && является одним токеном (ленивый оператор ‘and’), при использовании в контексте выражений заимствования он работает как два заимствования:
#![allow(unused)] fn main() { // одинаковые значения: let a = && 10; let a = & & 10; // одинаковые значения: let a = &&&& mut 10; let a = && && mut 10; let a = & & & & mut 10; }
Операторы сырого заимствования
&raw const и &raw mut являются операторами сырого заимствования.
Операнд-выражение этих операторов вычисляется в контексте выражения-места.
&raw const expr затем создает константный сырой указатель типа *const T на данное место, а &raw mut expr создает изменяемый сырой указатель типа *mut T.
Операторы сырого заимствования должны использоваться вместо операторов заимствования всякий раз, когда выражение-место может вычислиться в место, которое не выровнено правильно или не хранит допустимое значение, определяемое его типом, или когда создание ссылки ввело бы некорректные предположения о псевдонимах. В этих ситуациях использование оператора заимствования вызвало бы неопределенное поведение созданием недопустимой ссылки, но сырой указатель все еще может быть создан.
Следующий пример создает сырой указатель на невыровненное место через packed структуру:
#![allow(unused)] fn main() { #[repr(packed)] struct Packed { f1: u8, f2: u16, } let packed = Packed { f1: 1, f2: 2 }; // `&packed.f2` создал бы невыровненную ссылку и, следовательно, было бы неопределенным поведением! 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` создал бы ссылку на неинициализированный `bool`, // и, следовательно, было бы неопределенным поведением! let f1_ptr = unsafe { &raw mut (*uninit.as_mut_ptr()).field }; unsafe { f1_ptr.write(true); } let init = unsafe { uninit.assume_init() }; }
Оператор разыменования
Syntax
DereferenceExpression → * Expression
Оператор * (разыменование) также является унарным префиксным оператором.
При применении к указателю он обозначает указываемое местоположение.
Если выражение имеет тип &mut T или *mut T и является либо локальной переменной, (вложенным) полем локальной переменной, либо является изменяемым выражением-местом, то результирующая область памяти может быть присвоена.
Разыменование сырого указателя требует unsafe.
На не-указательных типах *x эквивалентно *std::ops::Deref::deref(&x) в контексте неизменяемого выражения-места и *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); }
Выражение распространения try
Syntax
TryPropagationExpression → Expression ?
Выражение распространения try использует значение внутреннего выражения и трейт Try, чтобы решить, производить ли значение, и если да, то какое значение производить, или возвращать ли значение вызывающей стороне, и если да, то какое значение возвращать.
Example
#![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()?; // Немедленно возвращает `Err()`. Ok(x + y) // Не выполняется. } 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
Трейт
Tryв настоящее время нестабилен и, следовательно, не может быть реализован для пользовательских типов.Выражение распространения try в настоящее время грубо эквивалентно:
#![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
Оператор распространения try иногда называют оператором вопросительного знака, оператором
?или оператором try.
Оператор распространения try может быть применен к выражениям со следующими типами:
Result<T, E>Result::Ok(val)вычисляется вval.Result::Err(e)возвращаетResult::Err(From::from(e)).
Option<T>Option::Some(val)вычисляется вval.Option::NoneвозвращаетOption::None.
ControlFlow<B, C>ControlFlow::Continue(c)вычисляется вc.ControlFlow::Break(b)возвращаетControlFlow::Break(b).
Poll<Result<T, E>>Poll::Ready(Ok(val))вычисляется вPoll::Ready(val).Poll::Ready(Err(e))возвращаетPoll::Ready(Err(From::from(e))).Poll::Pendingвычисляется вPoll::Pending.
Poll<Option<Result<T, E>>>Poll::Ready(Some(Ok(val)))вычисляется вPoll::Ready(Some(val)).Poll::Ready(Some(Err(e)))возвращаетPoll::Ready(Some(Err(From::from(e)))).Poll::Ready(None)вычисляется вPoll::Ready(None).Poll::Pendingвычисляется вPoll::Pending.
Операторы отрицания
Syntax
NegationExpression →
- Expression
| ! Expression
Это последние два унарных оператора.
Эта таблица суммирует их поведение на примитивных типах и то, какие трейты используются для перегрузки этих операторов для других типов. Помните, что знаковые целые числа всегда представляются с использованием дополнительного кода. Операнды всех этих операторов вычисляются в контексте выражения-значения, поэтому перемещаются или копируются.
| Символ | Целые числа | bool | Числа с плавающей точкой | Трейт перегрузки |
|---|---|---|---|---|
- | Отрицание* | Отрицание | 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); }
Арифметические и логические бинарные операторы
Syntax
ArithmeticOrLogicalExpression →
Expression + Expression
| Expression - Expression
| Expression * Expression
| Expression / Expression
| Expression % Expression
| Expression & Expression
| Expression | Expression
| Expression ^ Expression
| Expression << Expression
| Expression >> Expression
Выражения бинарных операторов все записываются в инфиксной нотации.
Эта таблица суммирует поведение арифметических и логических бинарных операторов на примитивных типах и то, какие трейты используются для перегрузки этих операторов для других типов. Помните, что знаковые целые числа всегда представляются с использованием дополнительного кода. Операнды всех этих операторов вычисляются в контексте выражения-значения, поэтому перемещаются или копируются.
| Символ | Целые числа | bool | Числа с плавающей точкой | Трейт перегрузки | Трейт перегрузки составного присваивания |
|---|---|---|---|---|---|
+ | Сложение | Сложение | std::ops::Add | std::ops::AddAssign | |
- | Вычитание | Вычитание | std::ops::Sub | std::ops::SubAssign | |
* | Умножение | Умножение | std::ops::Mul | std::ops::MulAssign | |
/ | Деление*† | Деление | std::ops::Div | std::ops::DivAssign | |
% | Остаток**† | Остаток | std::ops::Rem | std::ops::RemAssign | |
& | Побитовое И | Логическое И | std::ops::BitAnd | std::ops::BitAndAssign | |
| | Побитовое ИЛИ | Логическое ИЛИ | std::ops::BitOr | std::ops::BitOrAssign | |
^ | Побитовое исключающее ИЛИ | Логическое исключающее ИЛИ | std::ops::BitXor | std::ops::BitXorAssign | |
<< | Левый сдвиг | std::ops::Shl | std::ops::ShlAssign | ||
>> | Правый сдвиг*** | std::ops::Shr | std::ops::ShrAssign |
* Целочисленное деление округляется в сторону нуля.
** Rust использует остаток, определенный с усекающим делением. Для остаток = делимое % делитель, остаток будет иметь тот же знак, что и делимое.
*** Арифметический правый сдвиг для знаковых целочисленных типов, логический правый сдвиг для беззнаковых целочисленных типов.
† Для целочисленных типов деление на ноль вызывает панику.
Вот примеры использования этих операторов.
#![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); }
Операторы сравнения
Syntax
ComparisonExpression →
Expression == Expression
| Expression != Expression
| Expression > Expression
| Expression < Expression
| Expression >= Expression
| Expression <= Expression
Операторы сравнения также определены как для примитивных типов, так и для многих типов в стандартной библиотеке.
Скобки требуются при цепочке операторов сравнения. Например, выражение a == b == c недопустимо и может быть записано как (a == b) == c.
В отличие от арифметических и логических операторов, трейты для перегрузки этих операторов используются более обобщенно, чтобы показать, как тип может сравниваться, и функции, использующие эти трейты как ограничения, вероятно, будут предполагать, что они определяют фактические сравнения. Многие функции и макросы в стандартной библиотеке могут затем использовать это предположение (хотя и не для обеспечения безопасности).
В отличие от арифметических и логических операторов выше, эти операторы неявно принимают разделяемые заимствования своих операндов, вычисляя их в контексте выражения-места:
#![allow(unused)] fn main() { let a = 1; let b = 1; a == b; // эквивалентно ::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"); }
Ленивые логические операторы
Syntax
LazyBooleanExpression →
Expression || Expression
| Expression && Expression
Операторы || и && могут применяться к операндам булева типа.
Оператор || обозначает логическое ‘или’, а оператор && обозначает логическое ‘и’.
Они отличаются от | и & тем, что правый операнд вычисляется только тогда, когда левый операнд еще не определяет результат выражения.
То есть, || вычисляет свой правый операнд только тогда, когда левый операнд вычисляется в false, а && только тогда, когда он вычисляется в true.
#![allow(unused)] fn main() { let x = false || true; // true let y = false && panic!(); // false, не вычисляет `panic!()` }
Выражения приведения типа
Syntax
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 может использоваться для явного выполнения приведений, а также следующих дополнительных приведений.
Любое приведение, которое не подходит ни под правило приведения, ни под запись в таблице, является ошибкой компилятора.
Здесь *T означает либо *const T, либо *mut T. m означает опциональное mut в
типах ссылок и mut или const в типах указателей.
Тип e | U | Приведение, выполняемое e as U |
|---|---|---|
| Целый или Float тип | Целый или Float тип | Числовое приведение |
| Перечисление | Целый тип | Приведение перечисления |
bool или char | Целый тип | Приведение примитива к целому |
u8 | char | Приведение u8 к char |
*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 | Указатель на функцию | Приведение замыкания к указателю на функцию |
Семантика
Числовое приведение
-
Приведение между двумя целыми числами одинакового размера (например, i32 -> u32) является no-op (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, "Расширение нулями"); assert_eq!(0b0000_1010i8 as i16, 0b0000_0000_0000_1010i16, "Расширение знаками 0"); assert_eq!(0b1000_1010u8 as i8 as i16, 0b1111_1111_1000_1010u16 as i16, "Расширение знаками 1"); }
-
Приведение из числа с плавающей точкой к целому числу будет округлять float в сторону нуля
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); }
-
Приведение из целого числа к float произведет ближайший возможный float *
- если необходимо, округление происходит в соответствии с режимом
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, "Округлено"); 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, "Округлено"); assert_eq!(std::f64::INFINITY as f32, std::f32::INFINITY); assert!((std::f64::NAN as f32).is_nan()); } - если необходимо, округление происходит в соответствии с режимом
* если приведения целых чисел к float с этим режимом округления и поведением переполнения не поддерживаются нативно оборудованием, эти приведения, вероятно, будут медленнее, чем ожидалось.
** если приведения f64 к f32 с этим режимом округления и поведением переполнения не поддерживаются нативно оборудованием, эти приведения, вероятно, будут медленнее, чем ожидалось.
*** как определено в IEEE 754-2008 §4.3.1: выбрать ближайшее число с плавающей точкой, предпочитая то с четной младшей значащей цифрой, если ровно посередине между двумя числами с плавающей точкой.
Приведение перечисления
Приводит перечисление к его дискриминанту, затем использует числовое приведение, если нужно. Приведение ограничено следующими видами перечислений:
- Перечисления только с юнитами
- Перечисления без полей без явных дискриминантов, или где только юнит-варианты имеют явные дискриминанты
#![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приводится к0,trueприводится к1charприводится к значению кодовой точки, затем используется числовое приведение, если нужно.
#![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 к char
Приводится к char с соответствующей кодовой точкой.
#![allow(unused)] fn main() { assert_eq!(65u8 as char, 'A'); assert_eq!(214u8 as char, 'Ö'); }
Приведение указателя к адресу
Приведение из сырого указателя к целому числу производит машинный адрес ссылаемой памяти.
Если целочисленный тип меньше типа указателя, адрес может быть усечен; использование usize избегает этого.
Приведение адреса к указателю
Приведение из целого числа к сырому указателю интерпретирует целое число как адрес памяти и производит указатель, ссылающийся на эту память.
Warning
Это взаимодействует с моделью памяти 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 со следующим поведением:
- Если
TиUоба sized, указатель возвращается неизменным.
-
Если
TиUоба unsized, указатель также возвращается неизменным. В частности, метаданные сохраняются точно.Например, приведение из
*const [T]к*const [U]сохраняет количество элементов. Заметьте, что, как следствие, такие приведения не обязательно сохраняют размер объекта, на который указывает указатель (например, приведение*const [u16]к*const [u8]приведет к сырому указателю, который ссылается на объект вдвое меньшего размера, чем исходный). То же верно дляstrи любого составного типа, чей unsized хвост является типом среза, такого какstruct Foo(i32, [u8])или(u64, Foo).
- Если
Tunsized, аUsized, приведение отбрасывает все метаданные, которые дополняют широкий указательT, и производит тонкий указательU, состоящий из части данных unsized указателя.
Выражения присваивания
Syntax
AssignmentExpression → Expression = Expression
Выражение присваивания перемещает значение в указанное место.
Выражение присваивания состоит из изменяемого выражения-приемника, операнда-приемника, за которым следует знак равенства (=) и выражение-значение, операнд присваиваемого значения.
В своей самой базовой форме выражение-приемник является выражением-местом, и мы сначала обсудим этот случай.
Более общий случай деструктурирующего присваивания обсуждается ниже, но этот случай всегда разлагается на последовательные присваивания выражениям-местам, которые могут считаться более фундаментальным случаем.
Базовые присваивания
Вычисление выражений присваивания начинается с вычисления его операндов. Сначала вычисляется операнд присваиваемого значения, затем выражение-приемник.
Для деструктурирующего присваивания подвыражения выражения-приемника вычисляются слева направо.
Note
Это отличается от других выражений тем, что правый операнд вычисляется перед левым.
Затем это имеет эффект сначала сброса значения в присваиваемом месте, если только место не является неинициализированной локальной переменной или неинициализированным полем локальной переменной.
Затем оно либо копирует, либо перемещает присваиваемое значение в присваиваемое место.
Выражение присваивания всегда производит значение unit.
Пример:
#![allow(unused)] fn main() { let mut x = 0; let y = 0; x = y; }
Деструктурирующие присваивания
Деструктурирующее присваивание является аналогом деструктурирующих сопоставлений с образцом для объявления переменных, разрешая присваивание сложным значениям, таким как кортежи или структуры. Например, мы можем поменять местами две изменяемые переменные:
#![allow(unused)] fn main() { let (mut a, mut b) = (0, 1); // Поменяйте `a` и `b` местами, используя деструктурирующее присваивание. (b, a) = (a, b); }
В отличие от деструктурирующих объявлений с использованием let, образцы не могут появляться в левой части присваивания из-за синтаксических неоднозначностей.
Вместо этого группа выражений, соответствующих образцам, обозначается как выражения-приемники и разрешена в левой части присваивания.
Выражения-приемники затем десугарируются в сопоставления с образцом с последующим последовательным присваиванием.
Десугарированные образцы должны быть неопровержимыми: в частности, это означает, что только образцы срезов, чья длина известна на момент компиляции, и тривиальный срез [..], разрешены для деструктурирующего присваивания.
Метод десугаризации прост и лучше всего иллюстрируется примером.
#![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}; // десугарируется в: { 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; } }
Идентификаторы не запрещены от многократного использования в одном выражении-приемнике.
Выражения подчеркивания и пустые выражения диапазона могут использоваться для игнорирования определенных значений без их привязки.
Заметьте, что режимы привязки по умолчанию не применяются для десугарированного выражения.
Note
Десугаризация ограничивает временную область видимости операнда присваиваемого значения (правой части) деструктурирующего присваивания.
В базовом присваивании временное сбрасывается в конце охватывающей временной области видимости. Ниже это оператор. Поэтому присваивание и использование разрешено.
#![allow(unused)] fn main() { fn temp() {} fn f<T>(x: T) -> T { x } let x; (x = f(&temp()), x); // OK }И наоборот, в деструктурирующем присваивании временное сбрасывается в конце оператора
letв десугаризации. Поскольку это происходит до того, как мы попытаемся присвоитьx, ниже это не удается.#![allow(unused)] fn main() { fn temp() {} fn f<T>(x: T) -> T { x } let x; [x] = [f(&temp())]; // ОШИБКА }Это десугарируется в:
#![allow(unused)] fn main() { fn temp() {} fn f<T>(x: T) -> T { x } let x; { let [_x] = [f(&temp())]; // ^ // Временное сбрасывается здесь. x = _x; // ОШИБКА } }
Note
Из-за десугаризации операнд присваиваемого значения (правая часть) деструктурирующего присваивания является расширяющим выражением внутри вновь введенного блока.
Ниже, поскольку временная область видимости расширена до конца этого введенного блока, присваивание разрешено.
#![allow(unused)] fn main() { fn temp() {} let x; [x] = [&temp()]; // OK }Это десугарируется в:
#![allow(unused)] fn main() { fn temp() {} let x; { let [_x] = [&temp()]; x = _x; } // OK }Однако, если мы попытаемся использовать
x, даже в том же операторе, мы получим ошибку, потому что временное сбрасывается в конце этого введенного блока.#![allow(unused)] fn main() { fn temp() {} let x; ([x] = [&temp()], x); // ОШИБКА }Это десугарируется в:
#![allow(unused)] fn main() { fn temp() {} let x; ( { let [_x] = [&temp()]; x = _x; }, // <-- Временное сбрасывается здесь. x, // ОШИБКА ); }
Выражения составного присваивания
Syntax
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); }
Синтаксис составного присваивания: изменяемое выражение-место, операнд присваивания, затем один из операторов, за которым следует = как один токен (без пробелов), и затем выражение-значение, модифицирующий операнд.
В отличие от других операндов-мест, операнд места присваивания должен быть выражением-местом.
Попытка использовать выражение-значение является ошибкой компилятора, а не продвижением его до временного.
Вычисление выражений составного присваивания зависит от типов операндов.
Если типы обоих операндов известны до мономорфизации как примитивные, сначала вычисляется правая часть, затем левая часть, и место, заданное вычислением левой части, изменяется применением оператора к значениям обеих сторон.
use core::{num::Wrapping, ops::AddAssign}; trait Equate {} impl<T> Equate for (T, T) {} fn f1(x: (u8,)) { let mut order = vec![]; // RHS вычисляется первым, так как оба операнда примитивного // типа. { order.push(2); x }.0 += { order.push(1); x }.0; assert!(order.is_sorted()); } fn f2(x: (Wrapping<u8>,)) { let mut order = vec![]; // LHS вычисляется первым, так как `Wrapping<_>` не примитивный // тип. { 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![]; // LHS вычисляется первым, так как один из операндов является обобщенным // параметром, даже если этот обобщенный параметр может быть унифицирован // с примитивным типом из-за ограничения where. { order.push(1); x }.0 += { order.push(2); (0u8,) }.0; assert!(order.is_sorted()); } fn main() { f1((0u8,)); f2((Wrapping(0u8),)); // Мы предоставляем примитивный тип как обобщенный аргумент, но это // не влияет на порядок вычисления в `f3` при // мономорфизации. f3::<u8>((0u8,)); }
Note
Это необычно. В других случаях нормальным является вычисление слева направо.
Смотрите тест порядка вычисления для большего количества примеров.
В противном случае это выражение является синтаксическим сахаром для использования соответствующего трейта для оператора (см. expr.arith-logic.behavior) и вызова его метода с левой частью в качестве получателя и правой частью в качестве следующего аргумента.
Например, следующие два оператора эквивалентны:
#![allow(unused)] fn main() { use std::ops::AddAssign; fn f<T: AddAssign + Copy>(mut x: T, y: T) { x += y; // Оператор 1. x.add_assign(y); // Оператор 2. } }
Note
Удивительно, но дальнейшая десугаризация этого до полностью квалифицированного вызова метода не эквивалентна, поскольку есть особое поведение проверки заимствования, когда изменяемая ссылка на первый операнд берется через autoref.
#![allow(unused)] fn main() { use std::ops::AddAssign; fn f<T: AddAssign + Copy>(mut x: T) { // Здесь мы использовали `x` как LHS и RHS. Поскольку изменяемое // заимствование LHS, необходимое для вызова метода трейта, // берется неявно через autoref, это 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) { // Мы не можем десугарировать вышеприведенное в нижеприведенное, так как как только мы берем // изменяемое заимствование `x` для передачи первого аргумента, мы не можем // передать `x` по значению во втором аргументе, потому что изменяемая // ссылка все еще жива. <T as AddAssign>::add_assign(&mut x, x); //~^ ОШИБКА: нельзя использовать `x`, так как она была изменяемо заимствована } }#![allow(unused)] fn main() { use std::ops::AddAssign; fn f<T: AddAssign + Copy>(mut x: T) { // Как выше. (&mut x).add_assign(x); //~^ ОШИБКА: нельзя использовать `x`, так как она была изменяемо заимствована } }
Как и в случае с обычными выражениями присваивания, выражения составного присваивания всегда производят значение unit.
Warning
Избегайте написания кода, который зависит от порядка вычисления операндов в составных присваиваниях, так как он может быть необычным и удивительным.
-
где
TиVимеют совместимые метаданные:V: Sized, или- Оба с метаданными среза (
*[u16]->*[u8],*str->*(u8, [u32])), или - Оба с одинаковыми метаданными трейт-объекта, по модулю отбрасывания автотрейтов (
*dyn Debug->*(u16, dyn Debug),*dyn Debug + Send->*dyn Debug)- Примечание: добавление автотрейтов разрешено только если главный трейт имеет автотрейт как супертрейт (для
trait T: Send {},*dyn T->*dyn T + Sendдопустимо, но*dyn Debug->*dyn Debug + Sendнет) - Примечание: Обобщения (включая времена жизни) должны совпадать (
*dyn T<'a, A>->*dyn T<'b, B>требует'a = 'bиA = B)
- Примечание: добавление автотрейтов разрешено только если главный трейт имеет автотрейт как супертрейт (для
-
только когда
m₁этоmutилиm₂этоconst. Приведениеmutссылки/указателя кconstуказателю разрешено. ↩ ↩2 -
только замыкания, которые не захватывают (не закрывают) никакие локальные переменные, могут быть приведены к указателям на функцию. ↩