Введение
Эта книга является основным справочником по языку программирования Rust.
Note
Известные ошибки и недочёты в этой книге перечислены в
разделе вопросов на GitHub. Если вы обнаружите случай, когда поведение компилятора не соответствует тексту в книге, создайте вопрос (issue), чтобы мы могли решить, какой вариант является правильным.
Релизы Rust
Новые версии языка Rust выпускаются каждые шесть недель.
Первый стабильный релиз языка — Rust 1.0.0, за ним последовали Rust 1.1.0 и так далее.
Инструменты (rustc, cargo и др.) и документация (GitHub issues, эта книга и др.) выпускаются вместе с релизом языка.
Последнюю версию этой книги, соответствующую последней версии Rust, всегда можно найти по адресу https://doc.rust-lang.org/reference/. Предыдущие версии можно найти, добавив версию Rust перед каталогом “reference”. Например, Справочник для Rust 1.49.0 находится по адресу https://doc.rust-lang.org/1.49.0/reference/.
Чем Справочник не является
Эта книга не служит введением в язык. Предполагается, что читатель уже знаком с основами языка. Для получения таких базовых знаний доступна book.
Эта книга также не является справочником по standard library, входящей в дистрибутив языка. Эти библиотеки документированы отдельно путём извлечения атрибутов документации из их исходного кода. Многие функции, которые можно было бы ожидать как возможности языка, в Rust являются возможностями библиотек, поэтому то, что вы ищете, может находиться там, а не здесь.
Аналогично, эта книга обычно не описывает специфику rustc как инструмента или Cargo.
У rustc есть rustc book.
У Cargo есть cargo book, которая содержит cargo reference.
Есть несколько страниц, таких как linkage, которые всё же описывают, как работает rustc.
Эта книга также служит справочником только по тому, что доступно в стабильной версии Rust. Для получения информации о нестабильных функциях, находящихся в разработке, см. Unstable Book.
Компиляторы Rust, включая rustc, выполняют оптимизации.
Справочник не определяет, какие оптимизации разрешены или запрещены.
Вместо этого рассматривайте скомпилированную программу как “чёрный ящик”.
Её можно исследовать только путём запуска, подачи на вход данных и наблюдения за её выводом.
Всё, что происходит таким образом, должно соответствовать тому, что указано в справочнике.
Как пользоваться этой книгой
Эта книга не предполагает, что вы читаете её последовательно. Каждая глава, как правило, может быть прочитана самостоятельно, но будет содержать перекрёстные ссылки на другие главы для аспектов языка, на которые она ссылается, но не обсуждает.
Есть два основных способа чтения этого документа.
Первый — найти ответ на конкретный вопрос.
Если вы знаете, какая глава отвечает на этот вопрос, вы можете перейти к этой главе через оглавление.
В противном случае вы можете нажать s или щёлкнуть значок лупы на верхней панели, чтобы найти ключевые слова, связанные с вашим вопросом.
Например, предположим, вы хотите узнать, когда удаляется временное значение, созданное в операторе let.
Если вы ещё не знаете, что lifetime of temporaries определено в главе о выражениях, вы можете выполнить поиск по запросу “temporary let”, и первый результат поиска приведёт вас к этому разделу.
Второй способ — в целом углубить свои знания об определённом аспекте языка. В этом случае просто просматривайте оглавление, пока не увидите что-то, о чём хотите узнать больше, и просто начните читать. Если ссылка выглядит интересной, щёлкните по ней и прочитайте соответствующий раздел.
Тем не менее, не существует неправильного способа читать эту книгу. Читайте её так, как, по вашему мнению, вам лучше всего подходит.
Соглашения
Как и все технические книги, эта книга использует определённые соглашения в отображении информации. Эти соглашения описаны здесь.
-
Утверждения, определяющие термин, содержат этот термин курсивом. Всякий раз, когда этот термин используется вне данной главы, он обычно представляет собой ссылку на раздел, содержащий это определение.
Пример термина — это пример определяемого термина.
-
Основной текст описывает последнюю стабильную редакцию (edition). Различия с предыдущими редакциями выделены в специальных блоках:
2018 Edition differences
До редакции 2018 года поведение было таким. Начиная с редакции 2018 года, поведение стало таким.
-
Примечания, содержащие полезную информацию о состоянии книги или указывающие на полезную, но в основном выходящую за рамки книги информацию, помещаются в блоки-примечания.
Note
Это пример примечания.
-
Блоки-примеры показывают пример, демонстрирующий какое-либо правило или отмечающий интересный аспект. Некоторые примеры могут содержать скрытые строки, которые можно просмотреть, нажав на значок глаза, который появляется при наведении курсора или нажатии на пример.
Example
Это пример кода.
#![allow(unused)] fn main() { println!("hello world"); } -
Предупреждения, показывающие некорректное (unsound) поведение в языке или, возможно, запутанные взаимодействия языковых возможностей, помещаются в специальные блоки-предупреждения.
Warning
Это пример предупреждения.
-
Фрагменты кода в тексте выделены
<code>тегами.Более длинные примеры кода помещаются в блок с подсветкой синтаксиса, который имеет элементы управления для копирования, выполнения и показа скрытых строк в правом верхнем углу.
// Это скрытая строка. fn main() { println!("Это пример кода"); }Все примеры написаны для последней редакции, если не указано иное.
-
Грамматика и лексические конструкции описаны в Нотация.
-
Идентификаторы правил появляются перед каждым языковым правилом в квадратных скобках. Эти идентификаторы предоставляют способ ссылаться на конкретное правило в языке и ссылаться на него (например). Идентификатор правила использует точки для разделения секций от наиболее общих к наиболее конкретным (например, destructors.scope.nesting.function-body). На узких экранах имя правила сворачивается и отображается как
[*].На имя правила можно щёлкнуть, чтобы перейти по ссылке на это правило.
Warning
Организация правил в настоящее время находится в процессе изменения. На данный момент эти имена идентификаторов нестабильны между релизами, и ссылки на эти правила могут не работать, если они изменены. Мы намерены стабилизировать их, когда организация устоится, чтобы ссылки на имена правил не ломались между релизами.
-
Правила, для которых существуют связанные тесты, будут содержать ссылку
Testsпод собой (на узких экранах ссылка выглядит как[T]). Нажатие на ссылку вызовет список тестов, на которые можно щёлкнуть для просмотра теста. Например, см. input.encoding.utf8.Связывание правил с тестами — это непрерывный процесс. Обзор см. в главе Сводка тестов.
Участие в разработке
Мы приветствуем любой вклад.
Вы можете внести свой вклад в эту книгу, открыв вопрос (issue) или отправив запрос на принятие изменений (pull request) в the Rust Reference repository.
Если эта книга не отвечает на ваш вопрос, и вы считаете, что ответ на него должен быть в ней, пожалуйста, не стесняйтесь создать вопрос или спросить об этом в потоке t-lang/doc на Zulip.
Знание того, для чего люди чаще всего используют эту книгу, помогает нам направить внимание на то, чтобы сделать эти разделы как можно лучше.
И, конечно, если вы видите что-то неправильное или не нормативное, но не указанное явно как таковое, также, пожалуйста, создайте вопрос.
Нотация
Грамматика
В грамматических фрагментах Лексера и Синтаксиса используются следующие обозначения:
| Обозначение | Примеры | Значение |
|---|---|---|
| ЗАГЛАВНЫЕ | KW_IF, INTEGER_LITERAL | Токен, созданный лексером |
| ItalicCamelCase | LetStatement, Item | Синтаксическая конструкция |
строка | x, while, * | Точный символ(ы) |
| x? | pub? | Необязательный элемент |
| x* | OuterAttribute* | 0 или более повторений x |
| x+ | MacroMatch+ | 1 или более повторений x |
| xa..b | HEX_DIGIT1..6 | от a до b повторений x |
| Правило1 Правило2 | fn Name Parameters | Последовательность правил по порядку |
| | | u8 | u16, Block | Item | Либо одно, либо другое |
| [ ] | [b B] | Любой из перечисленных символов |
| [ - ] | [a-z] | Любой из символов в диапазоне |
| ~[ ] | ~[b B] | Любые символы, кроме перечисленных |
~строка | ~\n, ~*/ | Любые символы, кроме этой последовательности |
| ( ) | (, Parameter)? | Группирует элементы |
| U+xxxx | U+0060 | Одиночный символ Юникода |
| <текст> | <любой ASCII символ кроме CR> | Текстовое описание на английском того, что должно быть сопоставлено |
| Правило суффикс | IDENTIFIER_OR_KEYWORD except crate | Модификация предыдущего правила |
| // Комментарий. | // Однострочный комментарий. | Комментарий до конца строки. |
Последовательности имеют более высокий приоритет, чем альтернатива |.
Продукции таблицы строк
Некоторые правила в грамматике — в частности, унарные операторы, бинарные операторы и ключевые слова — представлены в упрощённой форме: в виде перечня печатаемых строк. Эти случаи образуют подмножество правил, касающихся Токены, и предполагается, что они являются результатом фазы лексического анализа, которая передаёт данные парсеру, управляемая ДКА, работающим над дизъюнкцией всех таких записей таблицы строк.
Когда такая строка в шрифте monospace встречается внутри грамматики, это неявная ссылка на отдельный элемент такой продукции таблицы строк. Более подробную информацию см. в разделе Токены.
Визуализация грамматики
Под каждым блоком грамматики находится кнопка для переключения отображения синтаксической диаграммы. Квадратный элемент представляет собой нетерминальное правило, а скруглённый прямоугольник — терминал.
Лексические структуры
Формат входных данных
Эта глава описывает, как исходный файл интерпретируется как последовательность токенов.
Описание того, как программы организованы в файлы, см. в разделе Крейты и исходные файлы
Кодировка исходного кода
Каждый исходный файл интерпретируется как последовательность символов Юникода, закодированных в UTF-8.
Ошибкой является случай, если файл не является корректным UTF-8.
Удаление метки порядка байт (BOM)
Если первый символ в последовательности — U+FEFF (МЕТКА ПОРЯДКА БАЙТ), он удаляется.
Нормализация CRLF
Каждая пара символов U+000D (CR), сразу за которой следует U+000A (LF), заменяется на один символ U+000A (LF).
Это происходит однократно, а не повторно, поэтому после нормализации в входных данных всё ещё могут существовать U+000D (CR), сразу за которыми следует U+000A (LF) (например, если исходные данные содержали “CR CR LF LF”).
Остальные вхождения символа U+000D (CR) остаются на месте (они рассматриваются как пробельные символы).
Удаление шебанга (shebang)
Если оставшаяся последовательность начинается с символов #!, символы до первого U+000A (LF) включительно удаляются из последовательности.
Например, первая строка следующего файла будет проигнорирована:
#!/usr/bin/env rustx
fn main() {
println!("Hello!");
}
В качестве исключения, если за символами #! (игнорируя находящиеся между ними комментарии или пробельные символы) следует токен [, ничего не удаляется.
Это предотвращает удаление внутреннего атрибута в начале исходного файла.
Note
Макрос стандартной библиотеки
include!применяет удаление метки порядка байт, нормализацию CRLF и удаление шебанга к файлу, который он читает. Макросыinclude_str!иinclude_bytes!этого не делают.
Токенизация
Полученная последовательность символов затем преобразуется в токены, как описано в оставшейся части этой главы.
Ключевые слова
Rust делит ключевые слова на три категории:
Строгие ключевые слова
Эти ключевые слова можно использовать только в соответствующих контекстах. Они не могут быть использованы в качестве имён:
- Элементов items
- [Переменных (Variables)]и параметров функций
- Полей и вариантов (variants)
- Параметров типов (Type parameters)
- Параметров времени жизни или меток циклов (loop labels)
- Макросов (Macros) или атрибутов (attributes)
- Заполнителей макросов (Macro placeholders)
- Крейтов (Crates)
Следующие ключевые слова присутствуют во всех редакциях (editions):
_asasyncawaitbreakconstcontinuecratedynelseenumexternfalsefnforifimplinletloopmatchmodmovemutpubrefreturnselfSelfstaticstructsupertraittruetypeunsafeusewherewhile
2018 Edition differences
Следующие ключевые слова были добавлены в редакции 2018:
asyncawaitdyn
Зарезервированные ключевые слова
Эти ключевые слова ещё не используются, но они зарезервированы для будущего использования. На них распространяются те же ограничения, что и на строгие ключевые слова. Причина этого заключается в том, чтобы обеспечить прямую совместимость текущих программ с будущими версиями Rust, запрещая им использовать эти ключевые слова.
abstractbecomeboxdofinalgenmacrooverrideprivtrytypeofunsizedvirtualyield
2018 Edition differences
Ключевое слово
tryбыло добавлено как зарезервированное в редакции 2018.
2024 Edition differences
Ключевое слово
genбыло добавлено как зарезервированное в редакции 2024.
Слабые ключевые слова
Эти ключевые слова имеют особое значение только в определённых контекстах. Например, можно объявить переменную или метод с именем union.
'staticmacro_rulesrawsafeunion
macro_rulesиспользуется для создания пользовательских Macros.
unionиспользуется для объявления union и является ключевым словом только при использовании в объявлении объединения.
-
'staticиспользуется для статического времени жизни и не может быть использован в качестве обобщённого параметра времени жизни (generic lifetime parameter) или loop label// error[E0262]: invalid lifetime parameter name: `'static` fn invalid_lifetime_parameter<'static>(s: &'static str) -> &'static str { s }
safeиспользуется для функций и статических переменных и имеет значение внутри внешних блоков (external blocks).
rawиспользуется для операторов сырого заимствования (raw borrow operators) и является ключевым словом только при сопоставлении с формой оператора сырого заимствования (например,&raw const exprили&raw mut expr).
2018 Edition differences
В редакции 2015
dynявляется ключевым словом, когда используется в позиции типа, за которым следует путь, не начинающийся с::или<, время жизни, знак вопроса, ключевое словоforили открывающая скобка.Начиная с редакции 2018,
dynбыл повышен до строгого ключевого слова.
Идентификаторы
Lexer
IDENTIFIER_OR_KEYWORD → ( XID_Start | _ ) XID_Continue*
XID_Start → <XID_Start определено в Unicode>
XID_Continue → <XID_Continue определено в Unicode>
RAW_IDENTIFIER → r# IDENTIFIER_OR_KEYWORD
NON_KEYWORD_IDENTIFIER → IDENTIFIER_OR_KEYWORDкроме строгих или зарезервированных ключевых слов
IDENTIFIER → NON_KEYWORD_IDENTIFIER | RAW_IDENTIFIER
RESERVED_RAW_IDENTIFIER → r# ( _ | crate | self | Self | super )
Идентификаторы соответствуют спецификации UAX31 для версии Unicode 16.0, с добавлениями, описанными ниже. Некоторые примеры идентификаторов:
foo_identifierr#trueМосква東京
Используемый профиль из UAX #31:
- Start :=
XID_Start, плюс символ подчёркивания (U+005F) - Continue :=
XID_Continue - Medial := пусто
Note
Идентификаторы, начинающиеся с подчёркивания, обычно используются для обозначения намеренно неиспользуемого идентификатора и отключают предупреждение о неиспользовании в
rustc.
Идентификаторы не могут быть строгими или зарезервированными ключевыми словами без префикса r#, описанного ниже в разделе Сырые идентификаторы.
Символы неразрывного нулевой ширины (ZWNJ U+200C) и соединения нулевой ширины (ZWJ U+200D) не допускаются в идентификаторах.
Идентификаторы ограничены ASCII-подмножеством XID_Start и XID_Continue в следующих ситуациях:
- Объявления
extern crate(за исключением идентификатора в AsClause) - Имена внешних крейтов, на которые ссылается path
- Имена module, загружаемые из файловой системы без атрибута
path - Элементы с атрибутом
no_mangle - Имена элементов во внешних блоках
Нормализация
Идентификаторы нормализуются с использованием Формы нормализации C (NFC), как определено в UAX15. Два идентификатора считаются равными, если равны их формы NFC.
Процедурные (proc-macro) и декларативные (mbe) макросы получают нормализованные идентификаторы на входе.
Raw идентификаторы
Сырой идентификатор похож на обычный идентификатор, но с префиксом r#. (Обратите внимание, что префикс r# не является частью самого идентификатора).
В отличие от обычного идентификатора, сырым идентификатором может быть любое строгое или зарезервированное ключевое слово, кроме перечисленных выше для RESERVED_RAW_IDENTIFIER.
Использование токена RESERVED_RAW_IDENTIFIER является ошибкой.
Комментарии
Lexer
LINE_COMMENT →
// ( ~[/ ! LF] | // ) ~LF*
| //
BLOCK_COMMENT →
/*
( ~[* !] | ** | BLOCK_COMMENT_OR_DOC )
( BLOCK_COMMENT_OR_DOC | ~*/ )*
*/
| /**/
| /***/
INNER_LINE_DOC →
//! ~[LF CR]*
INNER_BLOCK_DOC →
/*! ( BLOCK_COMMENT_OR_DOC | ~[*/ CR] )* */
OUTER_LINE_DOC →
/// ( ~/ ~[LF CR]* )?
OUTER_BLOCK_DOC →
/**
( ~* | BLOCK_COMMENT_OR_DOC )
( BLOCK_COMMENT_OR_DOC | ~[*/ CR] )*
*/
BLOCK_COMMENT_OR_DOC →
BLOCK_COMMENT
| OUTER_BLOCK_DOC
| INNER_BLOCK_DOC
Обычные комментарии (не документационные)
Комментарии следуют общему стилю C++: строчные (//) и
блочные (/* ... */) формы комментариев. Поддерживаются вложенные блочные комментарии.
Обычные комментарии интерпретируются как форма пробельных символов.
Комментарии документации
Строчные комментарии документации, начинающиеся ровно с трёх слешей (///), и блочные
комментарии документации (/** ... */), оба являющиеся внешними комментариями документации, интерпретируются как специальный
синтаксис для doc attributes.
То есть они эквивалентны написанию
#[doc="..."] вокруг тела комментария, т.е. /// Foo превращается в
#[doc="Foo"], а /** Bar */ превращается в #[doc="Bar"]. Следовательно, они должны
появляться перед тем, что принимает внешний атрибут.
Строчные комментарии, начинающиеся с //!, и блочные комментарии /*! ... */ — это
комментарии документации, которые применяются к родительскому элементу комментария, а не к элементу,
который следует за ним.
То есть они эквивалентны написанию #![doc="..."] вокруг
тела комментария. Комментарии //! обычно используются для документирования
модулей, занимающих исходный файл.
Символ U+000D (CR) не допускается в комментариях документации.
Note
Принято, чтобы комментарии документации содержали Markdown, как того ожидает
rustdoc. Однако синтаксис комментариев не учитывает внутреннюю разметку Markdown./** `glob = "*/*.rs";` */завершает комментарий при первом же*/, и оставшийся код вызовет синтаксическую ошибку. Это несколько ограничивает содержимое блочных комментариев документации по сравнению со строчными.
Note
Последовательность
U+000D(CR), сразу за которой следуетU+000A(LF), была бы ранее преобразована в одинU+000A(LF).
Примеры
#![allow(unused)] fn main() { //! Комментарий документации, применяемый к неявному анонимному модулю этого крейта pub mod outer_module { //! - Внутренний строчный комментарий документации //!! - Все ещё внутренний строчный комментарий документации (но с восклицательным знаком в начале) /*! - Внутренний блочный комментарий документации */ /*!! - Все ещё внутренний блочный комментарий документации (но с восклицательным знаком в начале) */ // - Просто комментарий /// - Внешний строчный комментарий документации (ровно 3 слеша) //// - Просто комментарий /* - Просто комментарий */ /** - Внешний блочный комментарий документации (ровно 2 звёздочки) */ /*** - Просто комментарий */ pub mod inner_module {} pub mod nested_comments { /* В Rust /* мы можем /* вкладывать комментарии */ */ */ // Все три типа блочных комментариев могут содержать или быть вложенными в // любой другой тип: /* /* */ /** */ /*! */ */ /*! /* */ /** */ /*! */ */ /** /* */ /** */ /*! */ */ pub mod dummy_item {} } pub mod degenerate_cases { // пустой внутренний строчный комментарий документации //! // пустой внутренний блочный комментарий документации /*!*/ // пустой строчный комментарий // // пустой внешний строчный комментарий документации /// // пустой блочный комментарий /**/ pub mod dummy_item {} // пустой блок с 2 звёздочками не является блоком документации, это блочный комментарий /***/ } /* Следующий пример не разрешён, потому что внешние комментарии документации требуют элемент, который получит документацию */ /// Где мой элемент? mod boo {} } }
Пробельные символы
Lexer
WHITESPACE →
U+0009 // Горизонтальная табуляция, '\t'
| U+000A // Перевод строки (Line feed), '\n'
| U+000B // Вертикальная табуляция
| U+000C // Прогон страницы (Form feed)
| U+000D // Возврат каретки (Carriage return), '\r'
| U+0020 // Пробел, ' '
| U+0085 // Следующая строка (Next line)
| U+200E // Слева направо (Left-to-right mark)
| U+200F // Справа налево (Right-to-left mark)
| U+2028 // Разделитель строк (Line separator)
| U+2029 // Разделитель абзацев (Paragraph separator)
TAB → U+0009 // Горизонтальная табуляция, '\t'
LF → U+000A // Перевод строки (Line feed), '\n'
CR → U+000D // Возврат каретки (Carriage return), '\r'
Пробельный символ — это любая непустая строка, содержащая только символы, которые имеют свойство Юникода Pattern_White_Space.
Rust — это “свободноформатный” язык, что означает, что все формы пробельных символов служат только для разделения токенов в грамматике и не имеют семантического значения.
Программа на Rust имеет идентичное значение, если каждый пробельный элемент заменён на любой другой допустимый пробельный элемент, такой как одиночный символ пробела.
Токены
Lexer
Token →
RESERVED_TOKEN
| RAW_IDENTIFIER
| CHAR_LITERAL
| STRING_LITERAL
| RAW_STRING_LITERAL
| BYTE_LITERAL
| BYTE_STRING_LITERAL
| RAW_BYTE_STRING_LITERAL
| C_STRING_LITERAL
| RAW_C_STRING_LITERAL
| INTEGER_LITERAL
| FLOAT_LITERAL
| LIFETIME_TOKEN
| PUNCTUATION
| IDENTIFIER_OR_KEYWORD
Токены — это примитивные конструкции в грамматике, определяемые регулярными (нерекурсивными) языками. Исходный код Rust может быть разбит на следующие виды токенов:
- Ключевые словаKeywords
- Идентификаторыidentifier
- Литералы
- Времена жизни
- Знаки пунктуации
- Разделители
В грамматике этой документации “простые” токены представлены в форме продукций таблицы строк
и отображаются шрифтом monospace.
Литералы
Литералы — это токены, используемые в литеральных выражениях.
Примеры
Символы и строки
| Пример | Кол-во #1 | Символы | Экранирования | |
|---|---|---|---|---|
| Символьный | 'H' | 0 | Все Unicode | Кавычки & ASCII & Unicode |
| Строковый | "hello" | 0 | Все Unicode | Кавычки & ASCII & Unicode |
| Сырая строка | r#"hello"# | <256 | Все Unicode | N/A |
| Байтовый | b'H' | 0 | Все ASCII | Кавычки & Байтовые |
| Байтовая строка | b"hello" | 0 | Все ASCII | Кавычки & Байтовые |
| Сырая байтовая строка | br#"hello"# | <256 | Все ASCII | N/A |
| C-строка | c"hello" | 0 | Все Unicode | Кавычки & Байтовые & Unicode |
| Сырая C-строка | cr#"hello"# | <256 | Все Unicode | N/A |
Экранирования ASCII
| Имя | |
|---|---|
\x41 | 7-битный код символа (ровно 2 цифры, до 0x7F) |
\n | Перевод строки (Newline) |
\r | Возврат каретки (Carriage return) |
\t | Табуляция (Tab) |
\\ | Обратная косая черта (Backslash) |
\0 | Нуль (Null) |
Байтовые экранирования
| Имя | |
|---|---|
\x7F | 8-битный код символа (ровно 2 цифры) |
\n | Перевод строки (Newline) |
\r | Возврат каретки (Carriage return) |
\t | Табуляция (Tab) |
\\ | Обратная косая черта (Backslash) |
\0 | Нуль (Null) |
Экранирования Unicode
| Имя | |
|---|---|
\u{7FFF} | 24-битный код символа Unicode (до 6 цифр) |
Экранирования кавычек
| Имя | |
|---|---|
\' | Одинарная кавычка |
\" | Двойная кавычка |
Числа
| Числовые литералы2 | Пример | Экспонента |
|---|---|---|
| Десятичное целое | 98_222 | N/A |
| Шестнадцатеричное целое | 0xff | N/A |
| Восьмеричное целое | 0o77 | N/A |
| Двоичное целое | 0b1111_0000 | N/A |
| Число с плавающей точкой | 123.0E+77 | Опционально |
Суффиксы
Суффикс — это последовательность символов, следующая за основной частью литерала (без пробелов между ними), имеющая ту же форму, что и несырой идентификатор или ключевое слово.
Lexer
SUFFIX → IDENTIFIER_OR_KEYWORDкроме _
SUFFIX_NO_E → SUFFIXне начинающийся с e или E
Любой вид литерала (строковый, целочисленный и т.д.) с любым суффиксом является допустимым токеном.
Токен литерала с любым суффиксом может быть передан в макрос без возникновения ошибки.
Сам макрос решит, как интерпретировать такой токен и следует ли выдавать ошибку.
В частности, спецификатор фрагмента literal для макросов по примеру сопоставляет токены литералов с произвольными суффиксами.
#![allow(unused)] fn main() { macro_rules! blackhole { ($tt:tt) => () } macro_rules! blackhole_lit { ($l:literal) => () } blackhole!("string"suffix); // OK blackhole_lit!(1suffix); // OK }
Однако суффиксы на токенах литералов, которые интерпретируются как литеральные выражения или шаблоны, ограничены. Любые суффиксы отвергаются на нечисловых токенах литералов, а числовые токены литералов принимаются только с суффиксами из списка ниже.
| Целые числа | С плавающей точкой |
|---|---|
u8, i8, u16, i16, u32, i32, u64, i64, u128, i128, usize, isize | f32, f64 |
Символьные и строковые литералы
Символьные литералы
Lexer
CHAR_LITERAL →
'
( ~[' \ LF CR TAB] | QUOTE_ESCAPE | ASCII_ESCAPE | UNICODE_ESCAPE )
' SUFFIX?
QUOTE_ESCAPE → \' | \"
ASCII_ESCAPE →
\x OCT_DIGIT HEX_DIGIT
| \n | \r | \t | \\ | \0
UNICODE_ESCAPE →
\u{ ( HEX_DIGIT _* )1..6 }
Символьный литерал — это одиночный символ Unicode, заключённый между двумя
символами U+0027 (одинарная кавычка), за исключением самого U+0027,
который должен быть экранирован предшествующим символом U+005C (\).
Строковые литералы
Lexer
STRING_LITERAL →
" (
~[" \ CR]
| QUOTE_ESCAPE
| ASCII_ESCAPE
| UNICODE_ESCAPE
| STRING_CONTINUE
)* " SUFFIX?
STRING_CONTINUE → \ LF
Строковый литерал — это последовательность любых символов Unicode, заключённая между двумя
символами U+0022 (двойная кавычка), за исключением самого U+0022,
который должен быть экранирован предшествующим символом U+005C (\).
Разрывы строк, представленные символом U+000A (LF), допускаются в строковых литералах.
Символ U+000D (CR) не может появляться в строковом литерале.
Когда неэкранированный символ U+005C (\) встречается непосредственно перед разрывом строки, разрыв строки не появляется в строке, представленной токеном.
Подробнее см. Экранирования продолжения строки.
Символьные экранирования
Некоторые дополнительные экранирования доступны либо в символьных, либо в несырых строковых
литералах. Экранирование начинается с U+005C (\) и продолжается одной из
следующих форм:
- 7-битное экранирование кодовой точки начинается с
U+0078(x) и следует ровно две шестнадцатеричные цифры со значением до0x7F. Оно обозначает символ ASCII со значением, равным предоставленному шестнадцатеричному значению. Более высокие значения не допускаются, поскольку неясно, означают ли они кодовые точки Unicode или байтовые значения.
- 24-битное экранирование кодовой точки начинается с
U+0075(u) и следует до шести шестнадцатеричных цифр, окружённых фигурными скобкамиU+007B({) иU+007D(}). Оно обозначает кодовую точку Unicode, равную предоставленному шестнадцатеричному значению.
- Экранирование пробельного символа — это один из символов
U+006E(n),U+0072(r) илиU+0074(t), обозначающих значения UnicodeU+000A(LF),U+000D(CR) илиU+0009(HT) соответственно.
- Нулевое экранирование — это символ
U+0030(0) и обозначает значение UnicodeU+0000(NUL).
- Экранирование обратной косой черты — это символ
U+005C(\), который должен быть экранирован, чтобы обозначить сам себя.
Сырые строковые литералы
Lexer
RAW_STRING_LITERAL → r RAW_STRING_CONTENT SUFFIX?
RAW_STRING_CONTENT →
" ( ~CR )* (non-greedy) "
| # RAW_STRING_CONTENT #
Сырые строковые литералы не обрабатывают никакие экранирования. Они начинаются с символа
U+0072 (r), за которым следует менее 256 символов U+0023 (#) и
символ U+0022 (двойная кавычка).
Тело сырой строки может содержать любую последовательность символов Unicode, кроме U+000D (CR).
Оно завершается только другим символом U+0022 (двойная кавычка), за которым следует то же количество символов U+0023 (#), которое предшествовало открывающему символу U+0022 (двойная кавычка).
Все символы Unicode, содержащиеся в теле сырой строки, представляют самих себя,
символы U+0022 (двойная кавычка) (кроме случая, когда за ними следует по крайней мере
столько же символов U+0023 (#), сколько было использовано для начала сырого строкового литерала) или
U+005C (\) не имеют никакого специального значения.
Примеры строковых литералов:
#![allow(unused)] fn main() { "foo"; r"foo"; // foo "\"foo\""; r#""foo""#; // "foo" "foo #\"# bar"; r##"foo #"# bar"##; // foo #"# bar "\x52"; "R"; r"R"; // R "\\x52"; r"\x52"; // \x52 }
Байтовые и байтовые строковые литералы
Байтовые литералы
Lexer
BYTE_LITERAL →
b' ( ASCII_FOR_CHAR | BYTE_ESCAPE ) ' SUFFIX?
ASCII_FOR_CHAR →
<любой ASCII (т.е. 0x00 до 0x7F) кроме ', \, LF, CR или TAB>
BYTE_ESCAPE →
\x HEX_DIGIT HEX_DIGIT
| \n | \r | \t | \\ | \0 | \' | \"
Байтовый литерал — это одиночный символ ASCII (в диапазоне U+0000 до U+007F)
или одиночное экранирование, перед которым стоят символы U+0062 (b) и
U+0027 (одинарная кавычка), и после которого следует символ U+0027. Если символ
U+0027 присутствует внутри литерала, он должен быть экранирован предшествующим
символом U+005C (\). Он эквивалентен числовому литералу беззнакового 8-битного целого числа u8.
Байтовые строковые литералы
Lexer
BYTE_STRING_LITERAL →
b" ( ASCII_FOR_STRING | BYTE_ESCAPE | STRING_CONTINUE )* " SUFFIX?
ASCII_FOR_STRING →
<любой ASCII (т.е 0x00 до 0x7F) кроме ", \ или CR>
Несырой байтовый строковый литерал — это последовательность символов ASCII и экранирований,
перед которой стоят символы U+0062 (b) и U+0022 (двойная кавычка), и
после которой следует символ U+0022. Если символ U+0022 присутствует внутри
литерала, он должен быть экранирован предшествующим символом U+005C (\).
Альтернативно, байтовый строковый литерал может быть сырым байтовым строковым литералом, определённым
ниже.
Разрывы строк, представленные символом U+000A (LF), допускаются в байтовых строковых литералах.
Символ U+000D (CR) не может появляться в байтовом строковом литерале.
Когда неэкранированный символ U+005C (\) встречается непосредственно перед разрывом строки, разрыв строки не появляется в строке, представленной токеном.
Подробнее см. Экранирования продолжения строки.
Некоторые дополнительные экранирования доступны либо в байтовых, либо в несырых байтовых строковых
литералах. Экранирование начинается с U+005C (\) и продолжается одной из
следующих форм:
- Байтовое экранирование начинается с
U+0078(x) и следует ровно две шестнадцатеричные цифры. Оно обозначает байт, равный предоставленному шестнадцатеричному значению.
- Экранирование пробельного символа — это один из символов
U+006E(n),U+0072(r) илиU+0074(t), обозначающих байтовые значения0x0A(ASCII LF),0x0D(ASCII CR) или0x09(ASCII HT) соответственно.
- Нулевое экранирование — это символ
U+0030(0) и обозначает байтовое значение0x00(ASCII NUL).
- Экранирование обратной косой черты — это символ
U+005C(\), который должен быть экранирован, чтобы обозначить своё ASCII-представление0x5C.
Сырые байтовые строковые литералы
Lexer
RAW_BYTE_STRING_LITERAL →
br RAW_BYTE_STRING_CONTENT SUFFIX?
RAW_BYTE_STRING_CONTENT →
" ASCII_FOR_RAW* (non-greedy) "
| # RAW_BYTE_STRING_CONTENT #
ASCII_FOR_RAW →
<любой ASCII (т.е. 0x00 до 0x7F) кроме CR>
Сырые байтовые строковые литералы не обрабатывают никакие экранирования. Они начинаются с
символа U+0062 (b), за которым следует U+0072 (r), за которым следует менее 256
символов U+0023 (#) и символ U+0022 (двойная кавычка).
Тело сырой строки может содержать любую последовательность символов ASCII, кроме U+000D (CR).
Оно завершается только другим символом U+0022 (двойная кавычка), за которым следует то же количество символов U+0023 (#), которое предшествовало открывающему символу U+0022 (двойная кавычка).
Сырой байтовый строковый литерал не может содержать никаких не-ASCII байтов.
Все символы, содержащиеся в теле сырой строки, представляют своё ASCII-представление,
символы U+0022 (двойная кавычка) (кроме случая, когда за ними следует по крайней мере
столько же символов U+0023 (#), сколько было использовано для начала сырого строкового литерала) или
U+005C (\) не имеют никакого специального значения.
Примеры байтовых строковых литералов:
#![allow(unused)] fn main() { b"foo"; br"foo"; // foo b"\"foo\""; br#""foo""#; // "foo" b"foo #\"# bar"; br##"foo #"# bar"##; // foo #"# bar b"\x52"; b"R"; br"R"; // R b"\\x52"; br"\x52"; // \x52 }
C-строковые и сырые C-строковые литералы
C-строковые литералы
Lexer
C_STRING_LITERAL →
c" (
~[" \ CR NUL]
| BYTE_ESCAPEкроме \0 или \x00
| UNICODE_ESCAPEкроме \u{0}, \u{00}, …, \u{000000}
| STRING_CONTINUE
)* " SUFFIX?
C-строковый литерал — это последовательность символов Unicode и экранирований,
перед которой стоят символы U+0063 (c) и U+0022 (двойная кавычка), и
после которой следует символ U+0022. Если символ U+0022 присутствует внутри
литерала, он должен быть экранирован предшествующим символом U+005C (\).
Альтернативно, C-строковый литерал может быть сырым C-строковым литералом, определённым ниже.
C-строки неявно завершаются байтом 0x00, поэтому C-строковый литерал
c"" эквивалентен ручному созданию &CStr из байтового строкового
литерала b"\x00". Кроме неявного терминатора, байт 0x00 не
допускается внутри C-строки.
Разрывы строк, представленные символом U+000A (LF), допускаются в C-строковых литералах.
Символ U+000D (CR) не может появляться в C-строковом литерале.
Когда неэкранированный символ U+005C (\) встречается непосредственно перед разрывом строки, разрыв строки не появляется в строке, представленной токеном.
Подробнее см. Экранирования продолжения строки.
Некоторые дополнительные экранирования доступны в несырых C-строковых литералах. Экранирование
начинается с U+005C (\) и продолжается одной из следующих форм:
- Байтовое экранирование начинается с
U+0078(x) и следует ровно две шестнадцатеричные цифры. Оно обозначает байт, равный предоставленному шестнадцатеричному значению.
- 24-битное экранирование кодовой точки начинается с
U+0075(u) и следует до шести шестнадцатеричных цифр, окружённых фигурными скобкамиU+007B({) иU+007D(}). Оно обозначает кодовую точку Unicode, равную предоставленному шестнадцатеричному значению, закодированную как UTF-8.
- Экранирование пробельного символа — это один из символов
U+006E(n),U+0072(r) илиU+0074(t), обозначающих байтовые значения0x0A(ASCII LF),0x0D(ASCII CR) или0x09(ASCII HT) соответственно.
- Экранирование обратной косой черты — это символ
U+005C(\), который должен быть экранирован, чтобы обозначить своё ASCII-представление0x5C.
C-строка представляет байты без определённой кодировки, но C-строковый литерал
может содержать символы Unicode выше U+007F. Такие символы будут заменены
байтами UTF-8 представления этого символа.
Следующие C-строковые литералы эквивалентны:
#![allow(unused)] fn main() { c"æ"; // LATIN SMALL LETTER AE (U+00E6) c"\u{00E6}"; c"\xC3\xA6"; }
2021 Edition differences
C-строковые литералы принимаются в редакции 2021 или позже. В более ранних редакциях токен
c""лексируется какc "".
Сырые C-строковые литералы
Lexer
RAW_C_STRING_LITERAL →
cr RAW_C_STRING_CONTENT SUFFIX?
RAW_C_STRING_CONTENT →
" ( ~[CR NUL] )* (non-greedy) "
| # RAW_C_STRING_CONTENT #
Сырые C-строковые литералы не обрабатывают никакие экранирования. Они начинаются с
символа U+0063 (c), за которым следует U+0072 (r), за которым следует менее 256
символов U+0023 (#) и символ U+0022 (двойная кавычка).
Тело сырой C-строки может содержать любую последовательность символов Unicode, кроме U+0000 (NUL) и U+000D (CR).
Оно завершается только другим символом U+0022 (двойная кавычка), за которым следует то же количество символов U+0023 (#), которое предшествовало открывающему символу U+0022 (двойная кавычка).
Все символы, содержащиеся в теле сырой C-строки, представляют самих себя в кодировке UTF-8.
Символы U+0022 (двойная кавычка) (кроме случая, когда за ними следует по крайней мере
столько же символов U+0023 (#), сколько было использовано для начала сырого C-строкового
литерала) или U+005C (\) не имеют никакого специального значения.
2021 Edition differences
Сырые C-строковые литералы принимаются в редакции 2021 или позже. В более ранних редакциях токен
cr""лексируется какcr "", аcr#""#лексируется какcr #""#(что неграмматично).
Примеры C-строковых и сырых C-строковых литералов
#![allow(unused)] fn main() { c"foo"; cr"foo"; // foo c"\"foo\""; cr#""foo""#; // "foo" c"foo #\"# bar"; cr##"foo #"# bar"##; // foo #"# bar c"\x52"; c"R"; cr"R"; // R c"\\x52"; cr"\x52"; // \x52 }
Целочисленные литералы
Lexer
INTEGER_LITERAL →
( DEC_LITERAL | BIN_LITERAL | OCT_LITERAL | HEX_LITERAL ) SUFFIX_NO_E?
DEC_LITERAL → DEC_DIGIT ( DEC_DIGIT | _ )*
BIN_LITERAL → 0b ( BIN_DIGIT | _ )* BIN_DIGIT ( BIN_DIGIT | _ )*
OCT_LITERAL → 0o ( OCT_DIGIT | _ )* OCT_DIGIT ( OCT_DIGIT | _ )*
HEX_LITERAL → 0x ( HEX_DIGIT | _ )* HEX_DIGIT ( HEX_DIGIT | _ )*
BIN_DIGIT → [0-1]
OCT_DIGIT → [0-7]
DEC_DIGIT → [0-9]
HEX_DIGIT → [0-9 a-f A-F]
Целочисленный литерал имеет одну из четырёх форм:
- Десятичный литерал начинается с десятичной цифры и продолжается любым сочетанием десятичных цифр и знаков подчёркивания.
- Шестнадцатеричный литерал начинается с последовательности символов
U+0030U+0078(0x) и продолжается любым сочетанием (с как минимум одной цифрой) шестнадцатеричных цифр и знаков подчёркивания.
- Восьмеричный литерал начинается с последовательности символов
U+0030U+006F(0o) и продолжается любым сочетанием (с как минимум одной цифрой) восьмеричных цифр и знаков подчёркивания.
- Двоичный литерал начинается с последовательности символов
U+0030U+0062(0b) и продолжается любым сочетанием (с как минимум одной цифрой) двоичных цифр и знаков подчёркивания.
Как и любой литерал, целочисленный литерал может следовать (непосредственно, без пробелов) за суффиксом, как описано выше.
Суффикс не может начинаться с e или E, так как это будет интерпретировано как экспонента литерала с плавающей точкой.
См. Целочисленные литеральные выражения для получения информации о влиянии этих суффиксов.
Примеры целочисленных литералов, которые принимаются как литеральные выражения:
#![allow(unused)] fn main() { #![allow(overflowing_literals)] 123; 123i32; 123u32; 123_u32; 0xff; 0xff_u8; 0x01_f32; // целое число 7986, не число с плавающей точкой 1.0 0x01_e3; // целое число 483, не число с плавающей точкой 1000.0 0o70; 0o70_i16; 0b1111_1111_1001_0000; 0b1111_1111_1001_0000i64; 0b________1; 0usize; // Эти числа слишком велики для своего типа, но принимаются как литеральные выражения. 128_i8; 256_u8; // Это целочисленный литерал, принимаемый как литеральное выражение с плавающей точкой. 5f32; }
Обратите внимание, что -1i8, например, анализируется как два токена: - followed by 1i8.
Примеры целочисленных литералов, которые не принимаются как литеральные выражения:
#![allow(unused)] fn main() { #[cfg(false)] { 0invalidSuffix; 123AFB43; 0b010a; 0xAB_CD_EF_GH; 0b1111_f32; } }
Индекс кортежа
Lexer
TUPLE_INDEX → DEC_LITERAL | BIN_LITERAL | OCT_LITERAL | HEX_LITERAL
Индекс кортежа используется для обращения к полям кортежей, кортежных структур и кортежных вариантов перечислений.
Индексы кортежа сравниваются с токеном литерала напрямую. Индексы кортежа
начинаются с 0, и каждый последующий индекс увеличивает значение на 1 как
десятичное значение. Таким образом, совпадут только десятичные значения, и значение не должно
иметь никаких лишних префиксных символов 0.
Индексы кортежа не могут включать никакие суффиксы (такие как usize).
#![allow(unused)] fn main() { let example = ("dog", "cat", "horse"); let dog = example.0; let cat = example.1; // Следующие примеры недопустимы. let cat = example.01; // ОШИБКА: нет поля с именем `01` let horse = example.0b10; // ОШИБКА: нет поля с именем `0b10` let unicorn = example.0usize; // ОШИБКА: суффиксы в индексе кортежа недопустимы let underscore = example.0_0; // ОШИБКА: нет поля `0_0` в типе `(&str, &str, &str)` }
Литералы с плавающей точкой
Lexer
FLOAT_LITERAL →
DEC_LITERAL .not immediately followed by ., _ or an XID_Start character
| DEC_LITERAL . DEC_LITERAL SUFFIX_NO_E?
| DEC_LITERAL ( . DEC_LITERAL )? FLOAT_EXPONENT SUFFIX?
FLOAT_EXPONENT →
( e | E ) ( + | - )? ( DEC_DIGIT | _ )* DEC_DIGIT ( DEC_DIGIT | _ )*
Литерал с плавающей точкой имеет одну из двух форм:
- Десятичный литерал, за которым следует символ точки
U+002E(.). За этим может следовать другой десятичный литерал, с необязательной экспонентой. - Одиночный десятичный литерал, за которым следует экспонента.
Как и целочисленные литералы, литерал с плавающей точкой может следовать за
суффиксом, при условии, что часть до суффикса не заканчивается на U+002E (.).
Суффикс не может начинаться с e или E, если литерал не включает экспоненту.
См. Выражения литералов с плавающей точкой для получения информации о влиянии этих суффиксов.
Примеры литералов с плавающей точкой, которые принимаются как литеральные выражения:
#![allow(unused)] fn main() { 123.0f64; 0.1f64; 0.1f32; 12E+99_f64; let x: f64 = 2.; }
Последний пример отличается, потому что невозможно использовать синтаксис суффикса
с литералом с плавающей точкой, заканчивающимся на точку. 2.f64 попытается
вызвать метод с именем f64 на 2.
Обратите внимание, что -1.0, например, анализируется как два токена: - followed by 1.0.
Примеры литералов с плавающей точкой, которые не принимаются как литеральные выражения:
#![allow(unused)] fn main() { #[cfg(false)] { 2.0f80; 2e5f80; 2e5e6; 2.0e5e6; 1.3e10u64; } }
Зарезервированные формы, похожие на числовые литералы
Lexer
RESERVED_NUMBER →
BIN_LITERAL [2-9]
| OCT_LITERAL [8-9]
| ( BIN_LITERAL | OCT_LITERAL | HEX_LITERAL ) .not immediately followed by ., _ or an XID_Start character
| ( BIN_LITERAL | OCT_LITERAL ) ( e | E )
| 0b _* <end of input or not BIN_DIGIT>
| 0o _* <end of input or not OCT_DIGIT>
| 0x _* <end of input or not HEX_DIGIT>
| DEC_LITERAL ( . DEC_LITERAL )? ( e | E ) ( + | - )? <end of input or not DEC_DIGIT>
Следующие лексические формы, похожие на числовые литералы, являются зарезервированными формами. Из-за возможной неоднозначности, которую они вызывают, они отвергаются токенизатором вместо того, чтобы интерпретироваться как отдельные токены.
- Несуффиксированный двоичный или восьмеричный литерал, за которым, без пробелов, следует десятичная цифра вне диапазона его системы счисления.
- Несуффиксированный двоичный, восьмеричный или шестнадцатеричный литерал, за которым, без пробелов, следует символ точки (с теми же ограничениями на то, что следует за точкой, как для литералов с плавающей точкой).
- Несуффиксированный двоичный или восьмеричный литерал, за которым, без пробелов, следует символ
eилиE.
- Ввод, который начинается с одного из префиксов системы счисления, но не является допустимым двоичным, восьмеричным или шестнадцатеричным литералом (потому что не содержит цифр).
- Ввод, который имеет форму литерала с плавающей точкой без цифр в экспоненте.
Примеры зарезервированных форм:
#![allow(unused)] fn main() { 0b0102; // это не `0b010` followed by `2` 0o1279; // это не `0o127` followed by `9` 0x80.0; // это не `0x80` followed by `.` и `0` 0b101e; // это не суффиксированный литерал, или `0b101` followed by `e` 0b; // это не целочисленный литерал, или `0` followed by `b` 0b_; // это не целочисленный литерал, или `0` followed by `b_` 2e; // это не литерал с плавающей точкой, или `2` followed by `e` 2.0e; // это не литерал с плавающей точкой, или `2.0` followed by `e` 2em; // это не суффиксированный литерал, или `2` followed by `em` 2.0em; // это не суффиксированный литерал, или `2.0` followed by `em` }
Времена жизни и метки циклов
Lexer
LIFETIME_TOKEN →
' IDENTIFIER_OR_KEYWORDnot immediately followed by '
| RAW_LIFETIME
LIFETIME_OR_LABEL →
' NON_KEYWORD_IDENTIFIERnot immediately followed by '
| RAW_LIFETIME
RAW_LIFETIME →
'r# IDENTIFIER_OR_KEYWORDnot immediately followed by '
RESERVED_RAW_LIFETIME → 'r# ( _ | crate | self | Self | super )not immediately followed by '
Параметры времени жизни и метки циклов используют токены LIFETIME_OR_LABEL. Любой токен LIFETIME_TOKEN будет принят лексером и, например, может быть использован в макросах.
Сырое время жизни похоже на обычное время жизни, но его идентификатор имеет префикс r#. (Обратите внимание, что префикс r# не включается в состав самого времени жизни.)
В отличие от обычного времени жизни, сырым временем жизни может быть любое строгое или зарезервированное ключевое слово, кроме перечисленных выше для RESERVED_RAW_LIFETIME.
Ошибкой является использование токена RESERVED_RAW_LIFETIME.
2021 Edition differences
Сырые времена жизни принимаются в редакции 2021 или позже. В более ранних редакциях токен
'r#ltлексируется как'r # lt.
Знаки пунктуации
Токены пунктуации используются как операторы, разделители и другие части грамматики.
Lexer
PUNCTUATION →
=
| <
| <=
| ==
| !=
| >=
| >
| &&
| ||
| !
| ~
| +
| -
| *
| /
| %
| ^
| &
| |
| <<
| >>
| +=
| -=
| *=
| /=
| %=
| ^=
| &=
| |=
| <<=
| >>=
| @
| .
| ..
| ...
| ..=
| ,
| ;
| :
| ::
| ->
| <-
| =>
| #
| $
| ?
| {
| }
| [
| ]
| (
| )
Note
См. синтаксический указатель для ссылок на то, как используются символы пунктуации.
Разделители
Скобочные знаки пунктуации используются в различных частях грамматики. Открывающая скобка всегда должна быть парной с закрывающей скобкой. Скобки и токены внутри них называются “деревьями токенов” в macros. Три типа скобок:
| Скобка | Тип |
|---|---|
{ } | Фигурные скобки |
[ ] | Квадратные скобки |
( ) | Круглые скобки |
Зарезервированные токены
Несколько форм токенов зарезервированы для будущего использования или чтобы избежать путаницы. Ошибкой является совпадение исходного ввода с одной из этих форм.
Lexer
RESERVED_TOKEN →
RESERVED_GUARDED_STRING_LITERAL
| RESERVED_NUMBER
| RESERVED_POUNDS
| RESERVED_RAW_IDENTIFIER
| RESERVED_RAW_LIFETIME
| RESERVED_TOKEN_DOUBLE_QUOTE
| RESERVED_TOKEN_LIFETIME
| RESERVED_TOKEN_POUND
| RESERVED_TOKEN_SINGLE_QUOTE
Зарезервированные префиксы
Lexer
RESERVED_TOKEN_DOUBLE_QUOTE →
IDENTIFIER_OR_KEYWORDкроме b или c или r или br или cr "
RESERVED_TOKEN_SINGLE_QUOTE →
IDENTIFIER_OR_KEYWORDкроме b '
RESERVED_TOKEN_POUND →
IDENTIFIER_OR_KEYWORDкроме r или br или cr #
RESERVED_TOKEN_LIFETIME →
' IDENTIFIER_OR_KEYWORDкроме r #
Некоторые лексические формы, известные как зарезервированные префиксы, зарезервированы для будущего использования.
Исходный ввод, который в противном случае лексически интерпретировался бы как несырой идентификатор (или ключевое слово), за которым непосредственно следует символ #, ' или " (без пробелов между ними), идентифицируется как зарезервированный префикс.
Обратите внимание, что сырые идентификаторы, сырые строковые литералы и сырые байтовые строковые литералы могут содержать символ #, но не интерпретируются как содержащие зарезервированный префикс.
Аналогично, префиксы r, b, br, c и cr, используемые в сырых строковых литералах, байтовых литералах, байтовых строковых литералах, сырых байтовых строковых литералах, C-строковых литералах и сырых C-строковых литералах, не интерпретируются как зарезервированные префиксы.
Исходный ввод, который в противном случае лексически интерпретировался бы как несырое время жизни (или ключевое слово), за которым непосредственно следует символ # (без пробелов между ними), идентифицируется как зарезервированный префикс времени жизни.
2021 Edition differences
Начиная с редакции 2021, зарезервированные префиксы сообщаются лексером как ошибка (в частности, они не могут быть переданы в макросы).
До редакции 2021, зарезервированные префиксы принимаются лексером и интерпретируются как несколько токенов (например, один токен для идентификатора или ключевого слова, за которым следует токен
#).Примеры, принимаемые во всех редакциях:
#![allow(unused)] fn main() { macro_rules! lexes {($($_:tt)*) => {}} lexes!{a #foo} lexes!{continue 'foo} lexes!{match "..." {}} lexes!{r#let#foo} // три токена: r#let # foo lexes!{'prefix #lt} }Примеры, принимаемые до редакции 2021, но отвергаемые позже:
#![allow(unused)] fn main() { macro_rules! lexes {($($_:tt)*) => {}} lexes!{a#foo} lexes!{continue'foo} lexes!{match"..." {}} lexes!{'prefix#lt} }
Зарезервированные ограничители
Lexer
RESERVED_GUARDED_STRING_LITERAL → #+ STRING_LITERAL
RESERVED_POUNDS → #2..
Зарезервированные ограничители — это синтаксис, зарезервированный для будущего использования, и они вызовут ошибку компиляции, если будут использованы.
Зарезервированный ограниченный строковый литерал — это токен из одного или более U+0023 (#), за которым непосредственно следует STRING_LITERAL.
Зарезервированные решётки — это токен из двух или более U+0023 (#).
2024 Edition differences
До редакции 2024, зарезервированные ограничители принимаются лексером и интерпретируются как несколько токенов. Например, форма
#"foo"#интерпретируется как три токена.##интерпретируется как два токена.
-
Количество символов
#с каждой стороны одного и того же литерала должно быть одинаковым. ↩ -
Все числовые литералы допускают
_как визуальный разделитель:1_234.0E+18f64↩
Макросы
Функциональность и синтаксис Rust могут быть расширены с помощью пользовательских определений,
называемых макросами. Им даются имена, и они вызываются через единообразный
синтаксис: some_extension!(...).
Существует два способа определения новых макросов:
- Макросы по примеру определяют новый синтаксис на более высоком, декларативном уровне.
- Процедурные макросы определяют функциональноподобные макросы, пользовательские наследования (derives) и пользовательские атрибуты с помощью функций, работающих с входными токенами.
Вызов макроса
Syntax
MacroInvocation →
SimplePath ! DelimTokenTree
DelimTokenTree →
( TokenTree* )
| [ TokenTree* ]
| { TokenTree* }
TokenTree →
Tokenкроме разделителей | DelimTokenTree
MacroInvocationSemi →
SimplePath ! ( TokenTree* ) ;
| SimplePath ! [ TokenTree* ] ;
| SimplePath ! { TokenTree* }
Вызов макроса раскрывает макрос во время компиляции и заменяет вызов результатом работы макроса. Макросы могут быть вызваны в следующих ситуациях:
- Элементы, включая ассоциированные элементы
- Транскрайберы
macro_rules
При использовании в качестве элемента или инструкции используется форма MacroInvocationSemi,
где точка с запятой требуется в конце, если не используются фигурные скобки.
Квалификаторы видимости никогда не разрешаются перед вызовом макроса или
определением macro_rules.
#![allow(unused)] fn main() { // Используется как выражение. let x = vec![1,2,3]; // Используется как инструкция. println!("Hello!"); // Используется в шаблоне. macro_rules! pat { ($i:ident) => (Some($i)) } if let pat!(x) = Some(1) { assert_eq!(x, 1); } // Используется в типе. macro_rules! Tuple { { $A:ty, $B:ty } => { ($A, $B) }; } type N2 = Tuple!(i32, i32); // Используется как элемент. use std::cell::RefCell; thread_local!(static FOO: RefCell<u32> = RefCell::new(1)); // Используется как ассоциированный элемент. macro_rules! const_maker { ($t:ty, $v:tt) => { const CONST: $t = $v; }; } trait T { const_maker!{i32, 7} } // Вызовы макросов внутри макросов. macro_rules! example { () => { println!("Макрос внутри макроса!") }; } // Сначала раскрывается внешний макрос `example`, затем внутренний макрос `println`. example!(); }
Макросы по примеру
Syntax
MacroRulesDefinition →
macro_rules ! IDENTIFIER MacroRulesDef
MacroRulesDef →
( MacroRules ) ;
| [ MacroRules ] ;
| { MacroRules }
MacroRules →
MacroRule ( ; MacroRule )* ;?
MacroRule →
MacroMatcher => MacroTranscriber
MacroMatcher →
( MacroMatch* )
| [ MacroMatch* ]
| { MacroMatch* }
MacroMatch →
Tokenкроме $ и разделителей
| MacroMatcher
| $ ( IDENTIFIER_OR_KEYWORDкроме crate | RAW_IDENTIFIER ) : MacroFragSpec
| $ ( MacroMatch+ ) MacroRepSep? MacroRepOp
MacroFragSpec →
block | expr | expr_2021 | ident | item | lifetime | literal
| meta | pat | pat_param | path | stmt | tt | ty | vis
MacroRepSep → Tokenкроме разделителей и MacroRepOp
MacroRepOp → * | + | ?
macro_rules позволяет пользователям определять расширения синтаксиса декларативным способом. Мы называем такие расширения “макросами по примеру” или просто “макросами”.
Каждый макрос по примеру имеет имя и одно или несколько правил. Каждое правило состоит из двух частей: сопоставителя (matcher), описывающего синтаксис, который он сопоставляет, и транскрайбера (transcriber), описывающего синтаксис, который заменит успешно сопоставленный вызов. Как сопоставитель, так и транскрайбер должны быть окружены разделителями. Макросы могут раскрываться в выражения, инструкции, элементы (включая трейты, реализации и внешние элементы), типы или шаблоны.
Транскрибирование
При вызове макроса, раскрыватель макросов ищет вызовы макросов по имени и пробует каждое правило макроса по очереди. Он транскрибирует первое успешное совпадение; если это приводит к ошибке, то последующие совпадения не проверяются.
При сопоставлении не выполняется упреждающий просмотр (lookahead); если компилятор не может однозначно определить, как разбирать вызов макроса по одному токену за раз, то это ошибка. В следующем примере компилятор не заглядывает вперед после идентификатора, чтобы увидеть, является ли следующий токен ), даже если это позволило бы ему однозначно разобрать вызов:
#![allow(unused)] fn main() { macro_rules! ambiguity { ($($i:ident)* $j:ident) => { }; } ambiguity!(error); // Ошибка: локальная неоднозначность }
Как в сопоставителе, так и в транскрайбере, токен $ используется для вызова специального поведения механизма макросов (описано ниже в Метапеременные и Повторы). Токены, не являющиеся частью такого вызова, сопоставляются и транскрибируются буквально, за одним исключением. Исключение состоит в том, что внешние разделители для сопоставителя будут соответствовать любой паре разделителей. Таким образом, например, сопоставитель (()) будет соответствовать {()} но не {{}}. Символ $ не может быть сопоставлен или транскрибирован буквально.
Передача сопоставленного фрагмента
При передаче сопоставленного фрагмента в другой макрос по примеру, сопоставители во втором макросе увидят непрозрачное AST типа фрагмента. Второй макрос не может использовать литеральные токены для сопоставления фрагментов в сопоставителе, только спецификатор фрагмента того же типа. Типы фрагментов ident, lifetime и tt являются исключением и могут быть сопоставлены литеральными токенами. Следующий пример иллюстрирует это ограничение:
#![allow(unused)] fn main() { macro_rules! foo { ($l:expr) => { bar!($l); } // ОШИБКА: ^^ никакие правила не ожидают этот токен в вызове макроса } macro_rules! bar { (3) => {} } foo!(3); }
Следующий пример иллюстрирует, как токены могут быть непосредственно сопоставлены после сопоставления фрагмента tt:
#![allow(unused)] fn main() { // компилируется OK macro_rules! foo { ($l:tt) => { bar!($l); } } macro_rules! bar { (3) => {} } foo!(3); }
Метапеременные
В сопоставителе $ имя : спецификатор-фрагмента сопоставляет фрагмент синтаксиса Rust указанного вида и связывает его с метапеременной $имя.
Допустимые спецификаторы фрагментов:
block: Блочное выражениеexpr: Выражениеexpr_2021: Выражение кроме Выражения с подчеркиванием и Выражения-константного блока (см. macro.decl.meta.edition2024)ident: IDENTIFIER_OR_KEYWORD кроме_, RAW_IDENTIFIER или$crateitem: Элементlifetime: LIFETIME_TOKENliteral: сопоставляет-?Литеральное выражениеmeta: Атрибут, содержимое атрибутаpat: Шаблон (см. macro.decl.meta.edition2021)pat_param: PatternNoTopAltpath: путь в стиле TypePathstmt: Инструкция без завершающей точки с запятой (кроме инструкций элементов, которые требуют точку с запятой)tt: TokenTree (одиночный токен или токены в соответствующих разделителях(),[]или{})ty: Типvis: возможно пустой квалификатор видимости
В транскрайбере на метапеременные ссылаются просто как на $имя, поскольку вид фрагмента указан в сопоставителе. Метапеременные заменяются элементом синтаксиса, который им соответствует.
Метапеременные могут транскрибироваться более одного раза или вообще не транскрибироваться.
Ключевое слово-метапеременная $crate может использоваться для ссылки на текущий крейт.
2021 Edition differences
Начиная с редакции 2021, спецификаторы фрагментов
patсопоставляют OR-шаблоны верхнего уровня (то есть они принимают Pattern).До редакции 2021 они сопоставляют точно такие же фрагменты, как
pat_param(то есть они принимают PatternNoTopAlt).Соответствующая редакция - это та, которая действует для определения
macro_rules!.
2024 Edition differences
До редакции 2024 спецификаторы фрагментов
exprне сопоставляют Выражение с подчеркиванием или Выражение-константный блок на верхнем уровне. Они разрешены внутри подвыражений.Спецификатор фрагмента
expr_2021существует для сохранения обратной совместимости с редакциями до 2024.
Повторы
Как в сопоставителе, так и в транскрайбере, повторы указываются путем помещения токенов, которые нужно повторить, внутри $(…), за которым следует оператор повтора, опционально с разделителем между ними.
Разделителем может быть любой токен, кроме разделителя или одного из операторов повтора, но ; и , являются наиболее распространенными. Например, $( $i:ident ),* представляет любое количество идентификаторов, разделенных запятыми. Вложенные повторы разрешены.
Операторы повтора:
*— указывает любое количество повторений.+— указывает любое количество, но не менее одного.?— указывает на необязательный фрагмент с нулем или одним вхождением.
Поскольку ? представляет не более одного вхождения, он не может использоваться с разделителем.
Повторяющийся фрагмент как сопоставляется, так и транскрибируется в указанное количество фрагментов, разделенных токеном-разделителем. Метапеременные сопоставляются с каждым повторением их соответствующего фрагмента. Например, приведенный выше пример $( $i:ident ),* сопоставляет $i со всеми идентификаторами в списке.
Во время транскрибирования применяются дополнительные ограничения к повторам, чтобы компилятор знал, как правильно их раскрыть:
- Метапеременная должна появляться в точно таком же количестве, виде и порядке вложенности повторов в транскрайбере, как и в сопоставителе. Так, для сопоставителя
$( $i:ident ),*, транскрайберы=> { $i },=> { $( $( $i)* )* }и=> { $( $i )+ }являются незаконными, но=> { $( $i );* }является корректным и заменяет список идентификаторов, разделенных запятыми, на список, разделенный точками с запятой. - Каждый повтор в транскрайбере должен содержать по крайней мере одну метапеременную, чтобы решить, сколько раз его раскрывать. Если несколько метапеременных появляются в одном и том же повторении, они должны быть связаны с одинаковым количеством фрагментов. Например,
( $( $i:ident ),* ; $( $j:ident ),* ) => (( $( ($i,$j) ),* ))должен связывать одинаковое количество фрагментов$iи$j. Это означает, что вызов макроса с(a, b, c; d, e, f)является законным и раскрывается в((a,d), (b,e), (c,f)), но(a, b, c; d, e)является незаконным, поскольку не имеет того же количества. Это требование применяется к каждому уровню вложенных повторов.
Области видимости, экспорт и импорт
По историческим причинам область видимости макросов по примеру работает не совсем так, как у элементов. Макросы имеют две формы области видимости: текстовая область видимости и область видимости на основе путей. Текстовая область видимости основана на порядке появления вещей в исходных файлах или даже across нескольких файлах и является областью видимости по умолчанию. Она подробно объясняется ниже. Область видимости на основе путей работает точно так же, как и область видимости элементов. Область видимости, экспорт и импорт макросов в значительной степени контролируются атрибутами.
Когда макрос вызывается с помощью неквалифицированного идентификатора (не часть многосоставного пути), он сначала ищется в текстовой области видимости. Если это не дает результатов, то он ищется в области видимости на основе путей. Если имя макроса квалифицировано путем, то он ищется только в области видимости на основе путей.
use lazy_static::lazy_static; // Импорт на основе пути.
macro_rules! lazy_static { // Текстовое определение.
(lazy) => {};
}
lazy_static!{lazy} // Текстовый поиск сначала находит наш макрос.
self::lazy_static!{} // Поиск на основе пути игнорирует наш макрос, находит импортированный.
Текстовая область видимости
Текстовая область видимости в значительной степени основана на порядке появления вещей в исходных файлах и работает аналогично области видимости локальных переменных, объявленных с помощью let, за исключением того, что она также применяется на уровне модуля. Когда macro_rules! используется для определения макроса, макрос входит в область видимости после определения (обратите внимание, что он все еще может использоваться рекурсивно, поскольку имена ищутся с места вызова), до тех пор, пока его окружающая область видимости, обычно модуль, не закрыта. Это может входить в дочерние модули и даже охватывать несколько файлов:
//// src/lib.rs
mod has_macro {
// m!{} // Ошибка: m не в области видимости.
macro_rules! m {
() => {};
}
m!{} // OK: появляется после объявления m.
mod uses_macro;
}
// m!{} // Ошибка: m не в области видимости.
//// src/has_macro/uses_macro.rs
m!{} // OK: появляется после объявления m в src/lib.rs
Не является ошибкой определять макрос несколько раз; самое последнее объявление будет затенять предыдущее, если оно не вышло из области видимости.
#![allow(unused)] fn main() { macro_rules! m { (1) => {}; } m!(1); mod inner { m!(1); macro_rules! m { (2) => {}; } // m!(1); // Ошибка: никакое правило не соответствует '1' m!(2); macro_rules! m { (3) => {}; } m!(3); } m!(1); }
Макросы также могут быть объявлены и использованы локально внутри функций и работают аналогично:
#![allow(unused)] fn main() { fn foo() { // m!(); // Ошибка: m не в области видимости. macro_rules! m { () => {}; } m!(); } // m!(); // Ошибка: m не в области видимости. }
Атрибут macro_use
attributes macro_use имеет две цели: он может использоваться на модулях для расширения области видимости макросов, определенных внутри них, и он может использоваться на extern crate для импорта макросов из другого крейта в macro_use прелюдию.
Example
#![allow(unused)] fn main() { #[macro_use] mod inner { macro_rules! m { () => {}; } } m!(); }#[macro_use] extern crate log;
При использовании на модулях атрибут macro_use использует синтаксис MetaWord.
При использовании на extern crate он использует синтаксисы MetaWord и MetaListIdents. Для получения дополнительной информации о том, как эти синтаксисы могут использоваться, см. macro.decl.scope.macro_use.prelude.
Атрибут macro_use может применяться к модулям или extern crate.
Note
rustcигнорирует использование в других позициях, но выдает предупреждения. Это может стать ошибкой в будущем.
Атрибут macro_use не может использоваться на extern crate self.
Атрибут macro_use может использоваться любое количество раз на форме.
Может быть указано несколько экземпляров macro_use в синтаксисе MetaListIdents. Будет импортировано объединение всех указанных макросов.
Note
На модулях
rustcвыдает предупреждения против любых атрибутов MetaWordmacro_use, следующих после первого.На
extern craterustcвыдает предупреждения против любых атрибутовmacro_use, которые не оказывают эффекта из-за того, что не импортируют никакие макросы, не уже импортированные другим атрибутомmacro_use. Если два или более атрибутов MetaListIdentsmacro_useимпортируют один и тот же макрос, против первого выдается предупреждение. Если присутствуют любые атрибуты MetaWordmacro_use, против всех атрибутов MetaListIdentsmacro_useвыдаются предупреждения. Если присутствуют два или более атрибутов MetaWordmacro_use, против следующих после первого выдаются предупреждения.
Когда macro_use используется на модуле, область видимости макросов модуля расширяется за пределы лексической области видимости модуля.
Example
#![allow(unused)] fn main() { #[macro_use] mod inner { macro_rules! m { () => {}; } } m!(); // OK }
Указание macro_use на объявлении extern crate в корне крейта импортирует экспортированные макросы из этого крейта.
Макросы, импортированные таким образом, импортируются в macro_use прелюдию, а не текстуально, что означает, что они могут быть затенены любым другим именем. Макросы, импортированные с помощью macro_use, могут использоваться до оператора импорта.
Note
rustcв настоящее время предпочитает последний импортированный макрос в случае конфликта. Не полагайтесь на это. Это поведение необычно, поскольку импорты в Rust обычно не зависят от порядка. Это поведениеmacro_useможет измениться в будущем.Подробнее см. Rust issue #148025.
При использовании синтаксиса MetaWord импортируются все экспортированные макросы. При использовании синтаксиса MetaListIdents импортируются только указанные макросы.
Example
#[macro_use(lazy_static)] // Или `#[macro_use]` для импорта всех макросов. extern crate lazy_static; lazy_static!{} // self::lazy_static!{} // ОШИБКА: lazy_static не определен в `self`.
Макросы для импорта с помощью macro_use должны быть экспортированы с помощью macro_export.
Атрибут macro_export
Атрибут macro_export экспортирует макрос из крейта и делает его доступным в корне крейта для разрешения на основе путей.
Example
#![allow(unused)] fn main() { self::m!(); // ^^^^ OK: Поиск на основе пути находит `m` в текущем модуле. m!(); // Как выше. mod inner { super::m!(); crate::m!(); } mod mac { #[macro_export] macro_rules! m { () => {}; } } }
Атрибут macro_export использует синтаксисы MetaWord и MetaListIdents. С синтаксисом MetaListIdents он принимает единственное значение local_inner_macros.
Атрибут macro_export может применяться к определениям macro_rules.
Note
rustcигнорирует использование в других позициях, но выдает предупреждения. Это может стать ошибкой в будущем.
Только первое использование macro_export на макросе имеет эффект.
Note
rustcвыдает предупреждения против любого использования после первого.
По умолчанию макросы имеют только текстовую область видимости и не могут быть разрешены по пути. Когда используется атрибут macro_export, макрос становится доступным в корне крейта, и на него можно ссылаться по его пути.
Example
Без
macro_exportмакросы имеют только текстовую область видимости, поэтому разрешение на основе пути макроса завершается неудачей.macro_rules! m { () => {}; } self::m!(); // ОШИБКА crate::m!(); // ОШИБКА fn main() {}С
macro_exportразрешение на основе пути работает.#[macro_export] macro_rules! m { () => {}; } self::m!(); // OK crate::m!(); // OK fn main() {}
Атрибут macro_export вызывает экспорт макроса из корня крейта, так что на него можно ссылаться в других крейтах по пути.
Example
Учитывая следующее в крейте
log:#![allow(unused)] fn main() { #[macro_export] macro_rules! warn { ($message:expr) => { eprintln!("WARN: {}", $message) }; } }Из другого крейта вы можете ссылаться на макрос по пути:
fn main() { log::warn!("example warning"); }
macro_export позволяет использование macro_use на extern crate для импорта макроса в macro_use прелюдию.
Example
Учитывая следующее в крейте
log:#![allow(unused)] fn main() { #[macro_export] macro_rules! warn { ($message:expr) => { eprintln!("WARN: {}", $message) }; } }Использование
macro_useв зависимом крейте позволяет использовать макрос из прелюдии:#[macro_use] extern crate log; pub mod util { pub fn do_thing() { // Разрешается через макрос прелюдии. warn!("example warning"); } }
Добавление local_inner_macros к атрибуту macro_export приводит к тому, что все вызовы макросов с одним сегментом в определении макроса имеют неявный префикс $crate::.
Note
Это предназначено в основном как инструмент для миграции кода, написанного до того, как
$crateбыл добавлен в язык, для работы с импортом макросов на основе путей в Rust 2018. Его использование в новом коде не рекомендуется.
Example
#![allow(unused)] fn main() { #[macro_export(local_inner_macros)] macro_rules! helped { () => { helper!() } // Автоматически преобразуется в $crate::helper!(). } #[macro_export] macro_rules! helper { () => { () } } }
Гигиена
Макросы по примеру имеют гигиену смешанного сайта. Это означает, что метки циклов, метки блоков и локальные переменные ищутся на сайте определения макроса, в то время как другие символы ищутся на сайте вызова макроса. Например:
#![allow(unused)] fn main() { let x = 1; fn func() { unreachable!("this is never called") } macro_rules! check { () => { assert_eq!(x, 1); // Использует `x` с сайта определения. func(); // Использует `func` с сайта вызова. }; } { let x = 2; fn func() { /* не паникует */ } check!(); } }
Метки и локальные переменные, определенные в раскрытии макроса, не разделяются между вызовами, поэтому этот код не компилируется:
#![allow(unused)] fn main() { macro_rules! m { (define) => { let x = 1; }; (refer) => { dbg!(x); }; } m!(define); m!(refer); }
Особым случаем является метапеременная $crate. Она ссылается на крейт, определяющий макрос, и может использоваться в начале пути для поиска элементов или макросов, которые не находятся в области видимости на сайте вызова.
//// Определения в крейте `helper_macro`.
#[macro_export]
macro_rules! helped {
// () => { helper!() } // Это может привести к ошибке из-за того, что 'helper' не в области видимости.
() => { $crate::helper!() }
}
#[macro_export]
macro_rules! helper {
() => { () }
}
//// Использование в другом крейте.
// Обратите внимание, что `helper_macro::helper` не импортирован!
use helper_macro::helped;
fn unit() {
helped!();
}
Обратите внимание, что, поскольку $crate ссылается на текущий крейт, он должен использоваться с полностью квалифицированным путем модуля при ссылке на не-макросные элементы:
#![allow(unused)] fn main() { pub mod inner { #[macro_export] macro_rules! call_foo { () => { $crate::inner::foo() }; } pub fn foo() {} } }
Кроме того, даже though $crate позволяет макросу ссылаться на элементы внутри его собственного крейта при раскрытии, его использование не влияет на видимость. На элемент или макрос все еще должна быть видима с сайта вызова. В следующем примере любая попытка вызвать call_foo!() извне его крейта завершится неудачей, потому что foo() не является публичным.
#![allow(unused)] fn main() { #[macro_export] macro_rules! call_foo { () => { $crate::foo() }; } fn foo() {} }
Note
До Rust 1.30,
$crateиlocal_inner_macrosне поддерживались. Они были добавлены вместе с импортом макросов на основе путей, чтобы гарантировать, что вспомогательные макросы не нужно было вручную импортировать пользователям крейта, экспортирующего макросы. Крейты, написанные для более ранних версий Rust, которые используют вспомогательные макросы, должны быть изменены для использования$crateилиlocal_inner_macros, чтобы хорошо работать с импортом на основе путей.
Ограничения неоднозначности множества следования
Парсер, используемый системой макросов, достаточно мощный, но он ограничен, чтобы предотвратить неоднозначность в текущих или будущих версиях языка.
В частности, в дополнение к правилу о неоднозначных раскрытиях, нетерминал, сопоставленный метапеременной, должен следовать за токеном, который был признан безопасным для использования после такого сопоставления.
В качестве примера, сопоставитель макроса, такой как $i:expr [ , ], теоретически мог бы быть принят в Rust сегодня, поскольку [,] не может быть частью допустимого выражения, и поэтому разбор всегда был бы однозначным. Однако, поскольку [ может начинать trailing выражения, [ не является символом, который можно безопасно исключить как идущий после выражения. Если бы [,] был принят в более поздней версии Rust, этот сопоставитель стал бы неоднозначным или неправильно разбирался, ломая рабочий код. Сопоставители, такие как $i:expr, или $i:expr;, были бы законными, однако, потому что , и ; являются законными разделителями выражений. Конкретные правила:
exprиstmtмогут следовать только за одним из:=>,,или;.
pat_paramможет следовать только за одним из:=>,,,=,|,ifилиin.
patможет следовать только за одним из:=>,,,=,ifилиin.
pathиtyмогут следовать только за одним из:=>,,,=,|,;,:,>,>>,[,{,as,whereили макропеременной со спецификатором фрагментаblock.
visможет следовать только за одним из:,, идентификатором, отличным от не-сырогоpriv, любым токеном, который может начинать тип, или метапеременной со спецификатором фрагментаident,tyилиpath.
- Все остальные спецификаторы фрагментов не имеют ограничений.
2021 Edition differences
До редакции 2021,
patтакже может следовать за|.
Когда involved повторы, то правила применяются к каждому возможному количеству раскрытий, принимая во внимание разделители. Это означает:
- Если повтор включает разделитель, этот разделитель должен быть способен следовать за содержимым повтора.
- Если повтор может повторяться несколько раз (
*или+), то содержимое должно быть способно следовать за самим собой. - Содержимое повтора должно быть способно следовать за тем, что идет до, и то, что идет после, должно быть способно следовать за содержимым повтора.
- Если повтор может сопоставляться ноль раз (
*или?), то то, что идет после, должно быть способно следовать за тем, что идет до.
Для более подробной информации см. формальную спецификацию.
Процедурные макросы
Процедурные макросы позволяют создавать расширения синтаксиса как выполнение функции. Процедурные макросы бывают трех видов:
- Функциональноподобные макросы -
custom!(...) - Макросы наследования (derive) -
#[derive(CustomDerive)] - Макросы атрибутов -
#[CustomAttribute]
Процедурные макросы позволяют запускать код во время компиляции, который работает с синтаксисом Rust, как потребляя, так и производя синтаксис Rust. Можно представить процедурные макросы как функции из AST в другое AST.
Процедурные макросы должны быть определены в корне крейта с типом крейта proc-macro.
Макросы не могут использоваться из крейта, где они определены, и могут использоваться только при импорте в другом крейте.
Note
При использовании Cargo крейты процедурных макросов определяются с помощью ключа
proc-macroв манифесте:[lib] proc-macro = true
Как функции, они должны либо возвращать синтаксис, паниковать или зацикливаться бесконечно. Возвращенный синтаксис либо заменяет, либо добавляет синтаксис в зависимости от вида процедурного макроса. Паники перехватываются компилятором и превращаются в ошибку компиляции. Бесконечные циклы не перехватываются компилятором, что приводит к его зависанию.
Процедурные макросы выполняются во время компиляции и thus имеют те же ресурсы, что и компилятор. Например, стандартный ввод, ошибка и вывод те же, к которым имеет доступ компилятор. Аналогично, доступ к файлам тот же. Из-за этого процедурные макросы имеют те же проблемы безопасности, что и сборочные скрипты Cargo.
Процедурные макросы имеют два способа сообщения об ошибках. Первый - паника. Второй - испускать вызов макроса compile_error.
Крейт proc_macro
Крейты процедурных макросов почти всегда будут линковаться с предоставляемым компилятором крейтом proc_macro. Крейт proc_macro предоставляет типы, необходимые для написания процедурных макросов, и средства для облегчения этого.
Этот крейт в основном содержит тип TokenStream. Процедурные макросы работают с потоками токенов вместо узлов AST, что является гораздо более стабильным интерфейсом со временем как для компилятора, так и для процедурных макросов.
Поток токенов примерно эквивалентен Vec<TokenTree>, где TokenTree можно roughly рассматривать как лексический токен. Например, foo - это токен Ident, . - токен Punct, а 1.2 - токен Literal. Тип TokenStream, в отличие от Vec<TokenTree>, дешев для клонирования.
Все токены имеют связанный Span. Span - это непрозрачное значение, которое не может
быть изменено, но может быть создано. Span представляют протяженность исходного
кода в программе и в основном используются для сообщения об ошибках. Хотя вы
не можете изменить сам Span, вы всегда можете изменить Span, связанный
с любым токеном, например, получив Span от другого токена.
Гигиена процедурных макросов
Процедурные макросы негигиеничны. Это означает, что они ведут себя так, как если бы выходной поток токенов был просто записан inline в код рядом с ним. Это означает, что он подвержен влиянию внешних элементов и также влияет на внешние импорты.
Авторам макросов нужно быть осторожными, чтобы обеспечить работу их макросов в как можно большем количестве контекстов
с учетом этого ограничения. Это часто включает использование абсолютных путей к
элементам в библиотеках (например, ::std::option::Option вместо Option) или
обеспечение того, чтобы сгенерированные функции имели имена, которые вряд ли будут конфликтовать
с другими функциями (например, __internal_foo вместо foo).
Атрибут proc_macro
Атрибут proc_macro определяет функциональноподобный процедурный макрос.
Example
Это определение макроса игнорирует его ввод и испускает функцию
answerв свою область видимости.#![crate_type = "proc-macro"] extern crate proc_macro; use proc_macro::TokenStream; #[proc_macro] pub fn make_answer(_item: TokenStream) -> TokenStream { "fn answer() -> u32 { 42 }".parse().unwrap() }Мы можем использовать его в бинарном крейте для вывода “42” в стандартный вывод.
extern crate proc_macro_examples; use proc_macro_examples::make_answer; make_answer!(); fn main() { println!("{}", answer()); }
Атрибут proc_macro использует синтаксис MetaWord.
Атрибут proc_macro может применяться только к pub функции типа fn(TokenStream) -> TokenStream, где TokenStream происходит из proc_macro крейта. Он должен иметь “Rust” ABI. Другие квалификаторы функций не разрешены. Он должен находиться в корне крейта.
Атрибут proc_macro может быть указан только один раз для функции.
Атрибут proc_macro публично определяет макрос в пространстве имен макросов в корне крейта с тем же именем, что и функция.
Вызов функциональноподобного макроса функциональноподобного процедурного макроса передаст то, что находится внутри разделителей вызова макроса, как входной аргумент TokenStream и заменит весь вызов макроса выходным TokenStream функции.
Функциональноподобные процедурные макросы могут быть вызваны в любой позиции вызова макроса, которая включает:
- Инструкции
- Выражения
- Шаблоны
- Выражения типов
- Позиции элементов, включая элементы в
externблоках - Внутренние и трейтовые реализации
- Определения трейтов
Атрибут proc_macro_derive
Применение атрибута proc_macro_derive к функции определяет макрос наследования (derive), который может быть вызван атрибутом derive. Эти макросы получают поток токенов определения структуры struct, перечисления enum или объединения union и могут испускать новые элементы после него. Они также могут объявлять и использовать вспомогательные атрибуты макросов наследования.
Example
Этот макрос наследования игнорирует его ввод и добавляет токены, определяющие функцию.
#![crate_type = "proc-macro"] extern crate proc_macro; use proc_macro::TokenStream; #[proc_macro_derive(AnswerFn)] pub fn derive_answer_fn(_item: TokenStream) -> TokenStream { "fn answer() -> u32 { 42 }".parse().unwrap() }Чтобы использовать его, мы можем написать:
extern crate proc_macro_examples; use proc_macro_examples::AnswerFn; #[derive(AnswerFn)] struct Struct; fn main() { assert_eq!(42, answer()); }
Синтаксис для атрибута proc_macro_derive:
Syntax
ProcMacroDeriveAttribute →
proc_macro_derive ( DeriveMacroName ( , DeriveMacroAttributes )? ,? )
DeriveMacroAttributes →
attributes ( ( IDENTIFIER ( , IDENTIFIER )* ,? )? )
Имя макроса наследования задается DeriveMacroName. Опциональный аргумент attributes описан в macro.proc.derive.attributes.
Атрибут proc_macro_derive может применяться только к pub функции с Rust ABI, определенной в корне крейта, с типом fn(TokenStream) -> TokenStream, где TokenStream происходит из proc_macro крейта. Функция может быть const и может использовать extern для явного указания Rust ABI, но она не может использовать любые другие квалификаторы (например, она не может быть async или unsafe).
Атрибут proc_macro_derive может использоваться только один раз на функцию.
Атрибут proc_macro_derive публично определяет макрос наследования в пространстве имен макросов в корне крейта.
Входной TokenStream - это поток токенов элемента, к которому применен атрибут derive. Выходной TokenStream должен быть (возможно пустым) набором элементов. Эти элементы добавляются после входного элемента в том же модуле или блоке.
Вспомогательные атрибуты макросов наследования
Макросы наследования могут объявлять вспомогательные атрибуты макросов наследования для использования в области видимости элемента, к которому применен макрос наследования. Эти атрибуты являются инертными. Хотя их цель - быть использованными макросом, который их объявил, они могут быть видны любому макросу.
Вспомогательный атрибут для макроса наследования объявляется путем добавления его идентификатора в список attributes в атрибуте proc_macro_derive.
Example
Это объявляет вспомогательный атрибут, а затем игнорирует его.
#![crate_type="proc-macro"] extern crate proc_macro; use proc_macro::TokenStream; #[proc_macro_derive(WithHelperAttr, attributes(helper))] pub fn derive_with_helper_attr(_item: TokenStream) -> TokenStream { TokenStream::new() }Чтобы использовать его, мы можем написать:
#[derive(WithHelperAttr)] struct Struct { #[helper] field: (), }
Атрибут proc_macro_attribute
Атрибут proc_macro_attribute определяет макрос атрибута, который может использоваться как внешний атрибут.
Example
Этот макрос атрибута принимает входной поток и испускает его как есть, фактически являясь атрибутом-заглушкой.
#![crate_type = "proc-macro"] extern crate proc_macro; use proc_macro::TokenStream; #[proc_macro_attribute] pub fn return_as_is(_attr: TokenStream, item: TokenStream) -> TokenStream { item }
Example
Это показывает, в выводе компилятора, строковое представление
TokenStream, которые видят макросы атрибутов.// my-macro/src/lib.rs extern crate proc_macro; use proc_macro::TokenStream; #[proc_macro_attribute] pub fn show_streams(attr: TokenStream, item: TokenStream) -> TokenStream { println!("attr: \"{attr}\""); println!("item: \"{item}\""); item }// src/lib.rs extern crate my_macro; use my_macro::show_streams; // Пример: Базовая функция. #[show_streams] fn invoke1() {} // вывод: attr: "" // вывод: item: "fn invoke1() {}" // Пример: Атрибут с вводом. #[show_streams(bar)] fn invoke2() {} // вывод: attr: "bar" // вывод: item: "fn invoke2() {}" // Пример: Несколько токенов во вводе. #[show_streams(multiple => tokens)] fn invoke3() {} // вывод: attr: "multiple => tokens" // вывод: item: "fn invoke3() {}" // Пример: Разделители во вводе. #[show_streams { delimiters }] fn invoke4() {} // вывод: attr: "delimiters" // вывод: item: "fn invoke4() {}"
Атрибут proc_macro_attribute использует синтаксис MetaWord.
Атрибут proc_macro_attribute может применяться только к pub функции типа fn(TokenStream, TokenStream) -> TokenStream, где TokenStream происходит из proc_macro крейта. Он должен иметь “Rust” ABI. Другие квалификаторы функций не разрешены. Он должен находиться в корне крейта.
Атрибут proc_macro_attribute может быть указан только один раз для функции.
Атрибут proc_macro_attribute определяет атрибут в пространстве имен макросов в корне крейта с тем же именем, что и функция.
Макросы атрибутов могут использоваться только на:
- Элементах
- Элементах в
externблоках - Внутренних и трейтовых реализациях
- Определениях трейтов
Первый параметр TokenStream - это разделенное дерево токенов, следующее после имени атрибута, но не включая внешние разделители. Если примененный атрибут содержит только имя атрибута или имя атрибута, за которым следуют пустые разделители, TokenStream пуст.
Второй TokenStream - это остальная часть элемента, включая другие атрибуты на элементе.
Элемент, к которому применен атрибут, заменяется нулем или более элементами в возвращенном TokenStream.
Токены декларативных макросов и процедурных макросов
Декларативные макросы macro_rules и процедурные макросы используют похожие, но
разные определения для токенов (или, точнее, TokenTree).
Деревья токенов в macro_rules (соответствующие сопоставителям tt) определяются как:
- Разделенные группы (
(...),{...}, и т.д.) - Все операторы, поддерживаемые языком, как односимвольные, так и
многосимвольные (
+,+=).- Обратите внимание, что этот набор не включает одиночную кавычку
'.
- Обратите внимание, что этот набор не включает одиночную кавычку
- Литералы (
"string",1, и т.д.)- Обратите внимание, что отрицание (например,
-1) никогда не является частью таких литеральных токенов, а является отдельным токеном оператора.
- Обратите внимание, что отрицание (например,
- Идентификаторы, включая ключевые слова (
ident,r#ident,fn) - Времена жизни (
'ident) - Подстановки метапеременных в
macro_rules(например,$my_exprвmacro_rules! mac { ($my_expr: expr) => { $my_expr } }после раскрытияmac, который будет рассматриваться как одно дерево токенов независимо от переданного выражения)
Деревья токенов в процедурных макросах определяются как:
- Разделенные группы (
(...),{...}, и т.д.) - Все знаки пунктуации, используемые в операторах, поддерживаемых языком (
+, но не+=), а также одиночный символ кавычки'(обычно используемый в временах жизни, см. ниже поведение разделения и склеивания времен жизни) - Литералы (
"string",1, и т.д.)- Отрицание (например,
-1) поддерживается как часть целочисленных и литералов с плавающей точкой.
- Отрицание (например,
- Идентификаторы, включая ключевые слова (
ident,r#ident,fn)
Несоответствия между этими двумя определениями учитываются, когда потоки токенов
передаются в процедурные макросы и из них.
Обратите внимание, что преобразования ниже могут происходить лениво, поэтому они могут не произойти, если
токены фактически не проверяются.
При передаче в процедурный макрос:
- Все многосимвольные операторы разбиваются на отдельные символы.
- Времена жизни разбиваются на символ
'и идентификатор. - Ключевое слово-метапеременная
$crateпередается как одиночный идентификатор. - Все другие подстановки метапеременных представляются как их underlying
потоки токенов.
- Такие потоки токенов могут быть обернуты в разделенные группы (
Group) с неявными разделителями (Delimiter::None), когда это необходимо для сохранения приоритетов разбора. - Подстановки
ttиidentникогда не оборачиваются в такие группы и всегда представляются как их underlying деревья токенов.
- Такие потоки токенов могут быть обернуты в разделенные группы (
При испускании из процедурного макроса:
- Символы пунктуации склеиваются в многосимвольные операторы, когда применимо.
- Одиночные кавычки
', соединенные с идентификаторами, склеиваются во времена жизни. - Отрицательные литералы преобразуются в два токена (
-и литерал) возможно обернутых в разделенную группу (Group) с неявными разделителями (Delimiter::None), когда это необходимо для сохранения приоритетов разбора.
Обратите внимание, что ни декларативные, ни процедурные макросы не поддерживают токены документирующих комментариев
(например, /// Doc), поэтому они всегда преобразуются в потоки токенов, представляющие
их эквивалентные атрибуты #[doc = r"str"], когда передаются в макросы.
Крейты и исходные файлы
Syntax
Crate →
InnerAttribute*
Item*
Note
Хотя Rust, как и любой другой язык, может быть реализован как интерпретатором, так и компилятором, единственная существующая реализация — это компилятор, и язык всегда проектировался для компиляции. По этим причинам данный раздел предполагает использование компилятора.
Семантика Rust подчиняется фазовому разграничению между временем компиляции и временем выполнения.1 Семантические правила, имеющие статическую интерпретацию, определяют успех или неудачу компиляции, в то время как семантические правила, имеющие динамическую интерпретацию, определяют поведение программы во время выполнения.
Модель компиляции сосредоточена вокруг артефактов, называемых крейтами. Каждый процесс компиляции обрабатывает один крейт в исходной форме и, в случае успеха, производит один крейт в бинарной форме: либо исполняемый файл, либо библиотеку того или иного вида.2
Крейт — это единица компиляции и линковки, а также версионирования, распространения и загрузки во время выполнения. Крейт содержит дерево вложенных областей видимости модулей. Верхний уровень этого дерева — это модуль, который является анонимным (с точки зрения путей внутри модуля), и любой элемент в пределах крейта имеет канонический путь модуля, обозначающий его расположение в пределах дерева модулей крейта.
Компилятор Rust всегда вызывается с одним исходным файлом в качестве входных данных и
всегда производит один выходной крейт. Обработка этого исходного файла может
привести к загрузке других исходных файлов в качестве модулей. Исходные файлы имеют
расширение .rs.
Исходный файл Rust описывает модуль, имя и расположение которого — в дереве модулей текущего крейта — определяются извне исходного файла: либо явным объявлением модуля в ссылающемся исходном файле, либо именем самого крейта.
Каждый исходный файл является модулем, но не каждому модулю нужен свой собственный исходный файл: определения модулей могут быть вложены в один файл.
Каждый исходный файл содержит последовательность из нуля или более определений Элементов и может опционально начинаться с любого количества атрибутов, которые применяются к содержащему модулю, большинство из которых влияют на поведение компилятора.
Анонимный модуль крейта может иметь дополнительные атрибуты, которые применяются к крейту в целом.
Note
Содержимое файла может предваряться шебангом.
#![allow(unused)] fn main() { // Указание имени крейта. #![crate_name = "projx"] // Указание типа выходного артефакта. #![crate_type = "lib"] // Включение предупреждения. // Это можно сделать в любом модуле, а не только в анонимном модуле крейта. #![warn(non_camel_case_types)] }
Функция main
Крейт, содержащий функцию main, может быть скомпилирован в исполняемый файл.
Если функция main присутствует, она не должна принимать аргументов, не должна объявлять
ограничений типов (трейтов) или времен жизни, не должна иметь where-предложений, и её возвращаемый
тип должен реализовывать типаж Termination.
fn main() {}
fn main() -> ! { std::process::exit(0); }
fn main() -> impl std::process::Termination { std::process::ExitCode::SUCCESS }
Функция main может быть импортирована, например, из внешнего крейта или из текущего.
#![allow(unused)] fn main() { mod foo { pub fn bar() { println!("Hello, world!"); } } use foo::bar as main; }
Note
Типы из стандартной библиотеки, реализующие
Termination:
()!InfallibleExitCodeResult<T, E> where T: Termination, E: Debug
Неперехваченное внешнее раскручивание стека (unwinding)
Когда “внешнее” раскручивание стека (например, исключение, вставленное из кода C++, или panic! в коде Rust, использующем другой обработчик паники) распространяется за пределы функции main, процесс будет безопасно завершён. Это может принять форму аварийного завершения (abort), и в этом случае не гарантируется, что какие-либо вызовы Drop будут выполнены, а вывод ошибки может быть менее информативным, чем если бы среда выполнения была завершена “нативной” Rust-паникой.
Для получения дополнительной информации см. документацию по панике.
Атрибут no_main
Атрибут no_main может быть применён на уровне крейта, чтобы отключить генерацию символа main для исполняемого бинарного файла. Это полезно, когда какой-либо другой связываемый объект определяет main.
Атрибут crate_name
Атрибут crate_name может быть применён на уровне крейта, чтобы указать
имя крейта с помощью синтаксиса MetaNameValueStr.
#![allow(unused)] #![crate_name = "mycrate"] fn main() { }
Имя крейта не должно быть пустым и должно содержать только Юникодные алфавитно-цифровые
символы или символы _ (U+005F).
-
Это разграничение также существовало бы и в интерпретаторе. Статические проверки, такие как синтаксический анализ, проверка типов и линтеры, должны происходить до выполнения программы независимо от того, когда она выполняется. ↩
-
Крейт в некоторой степени аналогичен сборке (assembly) в модели ECMA-335 CLI, библиотеке в SML/NJ Compilation Manager, юниту (unit) в модульной системе Овенса и Флатта или конфигурации в Mesa. ↩
Условная компиляция
Syntax
ConfigurationPredicate →
ConfigurationOption
| ConfigurationAll
| ConfigurationAny
| ConfigurationNot
| true
| false
ConfigurationOption →
IDENTIFIER ( = ( STRING_LITERAL | RAW_STRING_LITERAL ) )?
ConfigurationAll →
all ( ConfigurationPredicateList? )
ConfigurationAny →
any ( ConfigurationPredicateList? )
ConfigurationNot →
not ( ConfigurationPredicate )
ConfigurationPredicateList →
ConfigurationPredicate ( , ConfigurationPredicate )* ,?
Условно компилируемый исходный код — это исходный код, который компилируется только при определённых условиях.
Исходный код может быть сделан условно компилируемым с помощью атрибутов cfg и cfg_attr, а также встроенного макроса cfg.
Решение о компиляции может зависеть от целевой архитектуры компилируемого крейта, произвольных значений, переданных компилятору, и других факторов, подробно описанных ниже.
Каждая форма условной компиляции принимает предикат конфигурации, который вычисляется в true или false. Предикат может быть одним из следующих:
- Опция конфигурации. Предикат истинен, если опция установлена, и ложен, если она не установлена.
all()со списком предикатов конфигурации, разделённых запятыми. Истинен, если все заданные предикаты истинны, или если список пуст.
any()со списком предикатов конфигурации, разделённых запятыми. Истинен, если хотя бы один из заданных предикатов истинен. Если предикатов нет, он ложен.
not()с предикатом конфигурации. Истинен, если его предикат ложен, и ложен, если его предикат истинен.
- Литералы
trueилиfalse, которые всегда истинны или ложны соответственно.
Опции конфигурации — это либо имена, либо пары “ключ-значение”, и они либо установлены, либо не установлены.
Имена записываются как одиночный идентификатор, например, unix.
Пары “ключ-значение” записываются как идентификатор, =, а затем строка, например, target_arch = "x86_64".
Note
Пробелы вокруг
=игнорируются, поэтомуfoo="bar"иfoo = "bar"эквивалентны.
Ключи не должны быть уникальными. Например, feature = "std" и feature = "serde" могут быть установлены одновременно.
Установленные опции конфигурации
Какие опции конфигурации установлены, определяется статически во время компиляции крейта.
Некоторые опции устанавливаются компилятором на основе данных о компиляции.
Другие опции устанавливаются произвольно на основе входных данных, переданных компилятору вне кода.
Невозможно установить опцию конфигурации из исходного кода компилируемого крейта.
Note
Для
rustcпроизвольно устанавливаемые опции конфигурации задаются с помощью флага--cfg. Значения конфигурации для указанной цели можно просмотреть с помощьюrustc --print cfg --target $TARGET.
Note
Опции конфигурации с ключом
feature— это соглашение, используемое Cargo cargo_feature для указания опций времени компиляции и опциональных зависимостей.
target_arch
Опция “ключ-значение”, устанавливаемая один раз с указанием архитектуры ЦП цели. Значение похоже на первый элемент целевого тройника (target triple) платформы, но не идентично ему.
Примеры значений:
"x86""x86_64""mips""powerpc""powerpc64""arm""aarch64"
target_feature
Опция “ключ-значение”, устанавливаемая для каждой доступной особенности платформы для текущей цели компиляции.
Примеры значений:
"avx""avx2""crt-static""rdrand""sse""sse2""sse4.1"
Смотрите атрибут target_feature для получения более подробной информации о доступных
особенностях.
Дополнительная особенность crt-static доступна для опции
target_feature, чтобы указать, что доступна статическая C-среда выполнения.
target_os
Опция “ключ-значение”, устанавливаемая один раз с указанием операционной системы цели. Это значение похоже на второй и третий элемент целевого тройника платформы.
Примеры значений:
"windows""macos""ios""linux""android""freebsd""dragonfly""openbsd""netbsd""none"(типично для встраиваемых (embedded) целей)
target_family
Опция “ключ-значение”, предоставляющая более общее описание цели, такое как семейство
операционных систем или архитектур, к которым цель обычно относится. Любое количество
пар “ключ-значение” target_family может быть установлено.
Примеры значений:
"unix""windows""wasm"- Одновременно
"unix"и"wasm"
unix и windows
unix устанавливается, если установлено target_family = "unix".
windows устанавливается, если установлено target_family = "windows".
target_env
Опция “ключ-значение”, устанавливаемая с дополнительной уточняющей информацией о целевой
платформе, касающейся используемого ABI или libc. По историческим причинам,
это значение определено только как не пустая строка, когда это действительно необходимо для
различения. Таким образом, например, на многих платформах GNU это значение будет
пустым. Это значение похоже на четвёртый элемент целевого тройника платформы.
Одно различие заключается в том, что встраиваемые ABI, такие как gnueabihf, просто
определяют target_env как "gnu".
Примеры значений:
"""gnu""msvc""musl""sgx""sim""macabi"
target_abi
Опция “ключ-значение”, устанавливаемая для дальнейшего различения цели с информацией о целевом ABI.
По историческим причинам, это значение определено только как не пустая строка, когда это действительно необходимо для различения. Таким образом, например, на многих платформах GNU это значение будет пустым.
Примеры значений:
"""llvm""eabihf""abi64"
target_endian
Опция “ключ-значение”, устанавливаемая один раз со значением “little” или “big” в зависимости от порядка байт (endianness) ЦП цели.
target_pointer_width
Опция “ключ-значение”, устанавливаемая один раз с указанием разрядности указателя цели в битах.
Примеры значений:
"16""32""64"
target_vendor
Опция “ключ-значение”, устанавливаемая один раз с указанием производителя цели.
Примеры значений:
"apple""fortanix""pc""unknown"
target_has_atomic
Опция “ключ-значение”, устанавливаемая для каждой разрядности (в битах), которую цель поддерживает для атомарных загрузок, сохранений и операций compare-and-swap.
Когда этот cfg присутствует, все стабильные API core::sync::atomic доступны для
соответствующей атомарной разрядности.
Возможные значения:
"8""16""32""64""128""ptr"
test
Включается при компиляции тестовой обёртки (harness). Выполняется в rustc с помощью
флага --test. Смотрите Тестирование для получения дополнительной информации о поддержке тестирования.
debug_assertions
Включено по умолчанию при компиляции без оптимизаций.
Это может быть использовано для включения дополнительного отладочного кода в разработке, но не в
продакшене. Например, это управляет поведением макроса debug_assert! в стандартной библиотеке.
proc_macro
Устанавливается, когда компилируемый крейт компилируется с типом крейта proc_macro.
panic
Опция “ключ-значение”, устанавливаемая в зависимости от стратегии паники. Обратите внимание, что в будущем могут быть добавлены другие значения.
Примеры значений:
"abort""unwind"
Формы условной компиляции
Атрибут cfg
Атрибут cfg условно включает форму, к которой он прикреплён, на основе предиката конфигурации.
Example
#![allow(unused)] fn main() { // Функция включается в сборку только при компиляции для macOS #[cfg(target_os = "macos")] fn macos_only() { // ... } // Эта функция включается только когда определён foo или bar #[cfg(any(foo, bar))] fn needs_foo_or_bar() { // ... } // Эта функция включается только при компиляции для UNIX-подобной ОС с 32-битной // архитектурой #[cfg(all(unix, target_pointer_width = "32"))] fn on_32bit_unix() { // ... } // Эта функция включается только когда foo не определён #[cfg(not(foo))] fn needs_not_foo() { // ... } // Эта функция включается только когда стратегия паники установлена в unwind #[cfg(panic = "unwind")] fn when_unwinding() { // ... } }
Синтаксис атрибута cfg:
Syntax
CfgAttribute → cfg ( ConfigurationPredicate )
Атрибут cfg может использоваться везде, где разрешены атрибуты.
Атрибут cfg может использоваться любое количество раз для одной формы. Форма, к которой прикреплены атрибуты, не будет включена, если любой из предикатов cfg ложен, за исключением случая, описанного в cfg.attr.crate-level-attrs.
Если предикаты истинны, форма перезаписывается без атрибутов cfg на ней. Если любой предикат ложен, форма удаляется из исходного кода.
Когда крейт-уровневый cfg имеет ложный предикат, сам крейт всё равно существует. Любые атрибуты уровня крейта, предшествующие cfg, сохраняются, а любые атрибуты уровня крейта, следующие за cfg, удаляются, так же как и всё последующее содержимое крейта.
Example
Поведение, при котором предшествующие атрибуты не удаляются, позволяет делать такие вещи, как включать
#![no_std], чтобы избежать линковкиstd, даже если#![cfg(...)]в противном случае удалило содержимое крейта. Например:// Этот атрибут `no_std` сохраняется, даже хотя крейт-уровневый атрибут `cfg` // ложен. #![no_std] #![cfg(false)] // Эта функция не включается. pub fn example() {}
Атрибут cfg_attr
Атрибут cfg_attr условно включает атрибуты на основе предиката конфигурации.
Example
Следующий модуль будет найден либо в
linux.rs, либо вwindows.rsв зависимости от цели.#[cfg_attr(target_os = "linux", path = "linux.rs")] #[cfg_attr(windows, path = "windows.rs")] mod os;
Синтаксис атрибута cfg_attr:
Syntax
CfgAttrAttribute → cfg_attr ( ConfigurationPredicate , CfgAttrs? )
Атрибут cfg_attr может использоваться везде, где разрешены атрибуты.
Атрибут cfg_attr может использоваться любое количество раз на одной форме.
Атрибуты crate_type и crate_name нельзя использовать с cfg_attr.
Когда предикат конфигурации истинен, cfg_attr раскрывается в атрибуты, перечисленные после предиката.
Может быть перечислено ноль, один или более атрибутов. Несколько атрибутов будут раскрыты в отдельные атрибуты.
Example
#[cfg_attr(feature = "magic", sparkles, crackles)] fn bewitched() {} // Когда флаг функциональности `magic` включен, вышеуказанное раскроется в: #[sparkles] #[crackles] fn bewitched() {}
Note
cfg_attrможет раскрываться в другойcfg_attr. Например,#[cfg_attr(target_os = "linux", cfg_attr(feature = "multithreaded", some_other_attribute))]является допустимым. Этот пример был бы эквивалентен#[cfg_attr(all(target_os = "linux", feature ="multithreaded"), some_other_attribute)].
Макрос cfg
Встроенный макрос cfg принимает единственный предикат конфигурации и вычисляется
в литерал true, когда предикат истинен, и в литерал false, когда
он ложен.
Например:
#![allow(unused)] fn main() { let machine_kind = if cfg!(unix) { "unix" } else if cfg!(windows) { "windows" } else { "unknown" }; println!("Я работаю на машине типа {}!", machine_kind); }
Элементы
Syntax
Item →
OuterAttribute* ( VisItem | MacroItem )
VisItem →
Visibility?
(
Module
| ExternCrate
| UseDeclaration
| Function
| TypeAlias
| Struct
| Enumeration
| Union
| ConstantItem
| StaticItem
| Trait
| Implementation
| ExternBlock
)
Элемент - это компонент крейта. Элементы организуются внутри крейта с помощью вложенной системы модулей. Каждый крейт имеет один “внешний” анонимный модуль; все остальные элементы внутри крейта имеют пути в дереве модулей крейта.
Элементы полностью определяются во время компиляции, обычно остаются неизменными в течение выполнения и могут находиться в памяти только для чтения.
Существует несколько видов элементов:
- modules
- объявления
extern crate - объявления
use - определения функций
- определения типов
- определения структур
- определения перечислений
- определения объединений
- константы
- статические элементы
- определения трейтов
- реализации
- внешние блоки
Элементы могут быть объявлены в корне крейта, в модуле или в блочном выражении.
Подмножество элементов, называемых ассоциированными элементами, может быть объявлено в трейтах и реализациях.
Подмножество элементов, называемых внешними элементами, может быть объявлено во внешних блоках.
Элементы могут быть определены в любом порядке, за исключением macro_rules,
который имеет собственное поведение областей видимости.
Разрешение имён элементов позволяет определять элементы до или после того, как на элемент ссылаются в модуле или блоке.
См. области видимости элементов для получения информации о правилах областей видимости элементов.
Модули
Syntax
Module →
unsafe? mod IDENTIFIER ;
| unsafe? mod IDENTIFIER {
InnerAttribute*
Item*
}
Модуль - это контейнер для нуля или более элементов.
Элемент модуля - это модуль, окружённый фигурными скобками, имеющий имя и предваренный
ключевым словом mod. Элемент модуля вводит новый именованный модуль в дерево
модулей, составляющих крейт.
Модули могут быть вложенными произвольным образом.
Пример модуля:
#![allow(unused)] fn main() { mod math { type Complex = (f64, f64); fn sin(f: f64) -> f64 { /* ... */ unimplemented!(); } fn cos(f: f64) -> f64 { /* ... */ unimplemented!(); } fn tan(f: f64) -> f64 { /* ... */ unimplemented!(); } } }
Модули определяются в пространстве имён типов модуля или блока, где они находятся.
Ошибкой является определение нескольких элементов с одинаковым именем в одном пространстве имён внутри модуля. См. главу об областях видимости для получения дополнительной информации об ограничениях и поведении затенения.
Ключевое слово unsafe синтаксически разрешено появляться перед ключевым словом mod,
но оно отклоняется на семантическом уровне. Это позволяет макросам потреблять
синтаксис и использовать ключевое слово unsafe, прежде чем удалить его из
потока токенов.
Имена файлов исходного кода модулей
Модуль без тела загружается из внешнего файла. Когда модуль не имеет
атрибута path, путь к файлу отражает логический путь модуля.
Компоненты пути родительских модулей являются каталогами, а содержимое модуля
находится в файле с именем модуля и расширением .rs.
Например, следующая структура модулей может иметь соответствующую
структуру файловой системы:
| Путь модуля | Путь в файловой системе | Содержимое файла |
|---|---|---|
crate | lib.rs | mod util; |
crate::util | util.rs | mod config; |
crate::util::config | util/config.rs |
Имена файлов модулей также могут быть именем модуля как каталога с
содержимым в файле с именем mod.rs внутри этого каталога. Вышеприведенный пример может
также быть выражен с содержимым crate::util в файле с именем
util/mod.rs. Не допускается наличие одновременно util.rs и util/mod.rs.
Note
До
rustc1.30 использование файловmod.rsбыло способом загрузки модуля с вложенными дочерними элементами. Рекомендуется использовать новое соглашение об именовании, так как оно более последовательно и позволяет избежать наличия многих файлов с именемmod.rsв проекте.
Атрибут path
Каталоги и файлы, используемые для загрузки внешних файлов модулей, могут быть
изменены с помощью атрибута path.
Для атрибутов path на модулях, не находящихся внутри встроенных блоков модулей, путь к файлу
относится к каталогу, в котором находится исходный файл. Например, следующий
фрагмент кода будет использовать пути, показанные в зависимости от его расположения:
#[path = "foo.rs"]
mod c;
| Исходный файл | Расположение файла c | Путь модуля c |
|---|---|---|
src/a/b.rs | src/a/foo.rs | crate::a::b::c |
src/a/mod.rs | src/a/foo.rs | crate::a::c |
Для атрибутов path внутри встроенных блоков модулей относительное расположение
пути к файлу зависит от типа исходного файла, в котором находится
атрибут path. Исходные файлы “mod-rs” - это корневые модули (такие как lib.rs или
main.rs) и модули с файлами с именем mod.rs. Исходные файлы “non-mod-rs” - это
все остальные файлы модулей. Пути для атрибутов path внутри встроенных блоков модулей
в файле mod-rs являются относительными к каталогу файла mod-rs,
включая компоненты встроенного модуля как каталоги. Для файлов non-mod-rs
всё то же самое, за исключением того, что путь начинается с каталога с именем
модуля non-mod-rs. Например, следующий фрагмент кода будет использовать пути,
показанные в зависимости от его расположения:
mod inline {
#[path = "other.rs"]
mod inner;
}
| Исходный файл | Расположение файла inner | Путь модуля inner |
|---|---|---|
src/a/b.rs | src/a/b/inline/other.rs | crate::a::b::inline::inner |
src/a/mod.rs | src/a/inline/other.rs | crate::a::inline::inner |
Пример комбинирования вышеуказанных правил атрибутов path на встроенных модулях
и вложенных модулей внутри (применяется к файлам как mod-rs, так и non-mod-rs):
#[path = "thread_files"]
mod thread {
// Загружает модуль `local_data` из `thread_files/tls.rs` относительно
// каталога этого исходного файла.
#[path = "tls.rs"]
mod local_data;
}
Атрибуты на модулях
Модули, как и все элементы, принимают внешние атрибуты. Они также принимают внутренние
атрибуты: либо после { для модуля с телом, либо в начале
исходного файла, после опциональной BOM и шебанга.
Встроенные атрибуты, которые имеют смысл для модуля: cfg,
deprecated, doc, атрибуты проверки линтов, path и
no_implicit_prelude. Модули также принимают атрибуты макросов.
Объявления внешних крейтов
Syntax
ExternCrate → extern crate CrateRef AsClause? ;
CrateRef → IDENTIFIER | self
AsClause → as ( IDENTIFIER | _ )
Объявление extern crate указывает зависимость от внешнего крейта.
Внешний крейт затем привязывается к объявляющей области видимости как данный идентификатор в пространстве имён типов.
Кроме того, если extern crate появляется в корне крейта, то имя крейта также добавляется в внешнюю прелюдию, делая его автоматически в области видимости во всех модулях.
Предложение as может использоваться для привязки импортируемого крейта к другому имени.
Внешний крейт разрешается в конкретное soname во время компиляции, и
требование связи времени выполнения с этим soname передаётся линковщику для
загрузки во время выполнения. soname разрешается во время компиляции путём сканирования
пути библиотеки компилятора и сопоставления опционального crate_name с
атрибутами crate_name, которые были объявлены на внешнем крейте при его
компиляции. Если crate_name не предоставлен, предполагается атрибут name по умолчанию,
равный идентификатору, указанному в объявлении extern crate.
Крейт self может быть импортирован, что создаёт привязку к текущему крейту.
В этом случае предложение as должно использоваться для указания имени, к которому его привязать.
Три примера объявлений extern crate:
extern crate pcre;
extern crate std; // эквивалентно: extern crate std as std;
extern crate std as ruststd; // связывание с 'std' под другим именем
При именовании крейтов Rust дефисы запрещены. Однако пакеты Cargo могут
использовать их. В таком случае, когда Cargo.toml не указывает имя крейта,
Cargo прозрачно заменит - на _ (см. RFC 940 для более подробной
информации).
Вот пример:
// Импорт пакета Cargo hello-world
extern crate hello_world; // дефис заменён на подчёркивание
Импорты с подчёркиванием
Зависимость от внешнего крейта может быть объявлена без привязки его имени в области видимости
с использованием подчёркивания в форме extern crate foo as _. Это может быть
полезно для крейтов, которые нужно только линковать, но на которые никогда не ссылаются, и
это позволит избежать сообщений о неиспользовании.
Атрибут macro_use работает как обычно и импортирует имена макросов
в macro_use прелюдию.
Атрибут no_link
Атрибут no_link атрибут может быть применён к элементу extern crate для предотвращения линковки крейта.
Note
Это полезно, например, когда нужны только макросы из крейта.
Example
#[no_link] extern crate other_crate; other_crate::some_macro!();
Атрибут no_link использует синтаксис MetaWord.
Атрибут no_link может быть применён только к объявлению extern crate.
Note
rustcигнорирует использование в других позициях, но выдает предупреждение. Это может стать ошибкой в будущем.
Только первое использование no_link на объявлении extern crate имеет эффект.
Note
rustcвыдает предупреждение на любое использование после первого. Это может стать ошибкой в будущем.
Объявления use
Syntax
UseDeclaration → use UseTree ;
UseTree →
( SimplePath? :: )? *
| ( SimplePath? :: )? { ( UseTree ( , UseTree )* ,? )? }
| SimplePath ( as ( IDENTIFIER | _ ) )?
Объявление use создаёт одну или несколько локальных привязок имён, синонимичных
некоторому другому пути. Обычно объявление use используется для сокращения пути,
требуемого для ссылки на элемент модуля. Эти объявления могут появляться в модулях
и блоках, обычно вверху.
Объявление use также иногда называется импортом, или, если оно публичное, реэкспортом.
Объявления use поддерживают ряд удобных сокращений:
- Одновременная привязка списка путей с общим префиксом с использованием
синтаксиса фигурных скобок
use a::b::{c, d, e::f, g::h::i};
- Одновременная привязка списка путей с общим префиксом и их общего
родительского модуля с использованием ключевого слова
self, напримерuse a::b::{self, c, d::e};
- Перепривязка целевого имени как нового локального имени с использованием синтаксиса
use p::q::r as x;. Это также может использоваться с последними двумя функциями:use a::b::{self as ab, c as abc}.
- Привязка всех путей, соответствующих заданному префиксу, с использованием синтаксиса подстановочного знака звездочки
use a::b::*;.
- Вложенные группы предыдущих функций несколько раз, такие как
use a::b::{self as ab, c, d::{*, e::f}};
Пример объявлений use:
use std::collections::hash_map::{self, HashMap}; fn foo<T>(_: T){} fn bar(map1: HashMap<String, usize>, map2: hash_map::HashMap<String, usize>){} fn main() { // объявления use также могут существовать внутри функций use std::option::Option::{Some, None}; // Эквивалентно 'foo(vec![std::option::Option::Some(1.0f64), // std::option::Option::None]);' foo(vec![Some(1.0f64), None]); // И `hash_map`, и `HashMap` находятся в области видимости. let map1 = HashMap::new(); let map2 = hash_map::HashMap::new(); bar(map1, map2); }
Видимость use
Как и элементы, объявления use являются приватными для содержащего модуля,
по умолчанию. Также как и элементы, объявление use может быть публичным, если квалифицировано
ключевым словом pub. Такое объявление use служит для реэкспорта имени.
Публичное объявление use может поэтому перенаправлять некоторое публичное имя на
другое целевое определение: даже определение с приватным каноническим путём,
внутри другого модуля.
Если последовательность таких перенаправлений образует цикл или не может быть разрешена однозначно, они представляют ошибку времени компиляции.
Пример реэкспорта:
mod quux { pub use self::foo::{bar, baz}; pub mod foo { pub fn bar() {} pub fn baz() {} } } fn main() { quux::bar(); quux::baz(); }
В этом примере модуль quux реэкспортирует два публичных имени, определённых в
foo.
Пути use
Пути, которые разрешены в элементе use, следуют грамматике SimplePath и похожи на пути, которые могут использоваться в выражении.
Они могут создавать привязки для:
Они не могут импортировать ассоциированные элементы, обобщённые параметры, локальные переменные, пути с Self или инструментальные атрибуты. Дополнительные ограничения описаны ниже.
use создаст привязки для всех пространств имён из импортированных сущностей, за исключением того, что импорт self будет импортировать только из пространства имён типов (как описано ниже).
Например, следующее иллюстрирует создание привязок для одного и того же имени в двух пространствах имён:
#![allow(unused)] fn main() { mod stuff { pub struct Foo(pub i32); } // Импортирует тип `Foo` и конструктор `Foo`. use stuff::Foo; fn example() { let ctor = Foo; // Использует `Foo` из пространства имён значений. let x: Foo = ctor(123); // Использует `Foo` из пространства имён типов. } }
2018 Edition differences
В редакции 2015 пути
useотносительны к корню крейта. Например:mod foo { pub mod example { pub mod iter {} } pub mod baz { pub fn foobaz() {} } } mod bar { // Разрешает `foo` из корня крейта. use foo::example::iter; // Префикс `::` явно разрешает `foo` // из корня крейта. use ::foo::baz::foobaz; } fn main() {}Редакция 2015 не позволяет объявлениям use ссылаться на внешнюю прелюдию. Таким образом, объявления
extern crateвсё ещё требуются в 2015 для ссылки на внешний крейт в объявленииuse. Начиная с редакции 2018, объявленияuseмогут указывать внешнюю зависимость крейта так же, какextern crate.
Переименования с as
Ключевое слово as может использоваться для изменения имени импортированной сущности.
Например:
#![allow(unused)] fn main() { // Создаёт непубличный псевдоним `bar` для функции `foo`. use inner::foo as bar; mod inner { pub fn foo() {} } }
Синтаксис фигурных скобок
Фигурные скобки могут использоваться в последнем сегменте пути для импорта нескольких сущностей из предыдущего сегмента, или, если нет предыдущих сегментов, из текущей области видимости. Фигурные скобки могут быть вложенными, создавая дерево путей, где каждая группировка сегментов логически комбинируется со своим родителем для создания полного пути.
#![allow(unused)] fn main() { // Создаёт привязки для: // - `std::collections::BTreeSet` // - `std::collections::hash_map` // - `std::collections::hash_map::HashMap` use std::collections::{BTreeSet, hash_map::{self, HashMap}}; }
Пустые фигурные скобки ничего не импортируют, хотя ведущий путь проверяется на доступность.
2018 Edition differences
В редакции 2015 пути относительны к корню крейта, поэтому импорт типа
use {foo, bar};импортирует именаfooиbarиз корня крейта, тогда как начиная с 2018 эти имена относительны к текущей области видимости.
Импорты self
Ключевое слово self может использоваться внутри синтаксиса фигурных скобок для создания привязки родительской сущности под её собственным именем.
mod stuff { pub fn foo() {} pub fn bar() {} } mod example { // Создаёт привязку для `stuff` и `foo`. use crate::stuff::{self, foo}; pub fn baz() { foo(); stuff::bar(); } } fn main() {}
self создаёт привязку только из пространства имён типов родительской сущности.
Например, в следующем импортируется только модуль foo:
mod bar { pub mod foo {} pub fn foo() {} } // Это импортирует только модуль `foo`. Функция `foo` находится в // пространстве имён значений и не импортируется. use bar::foo::{self}; fn main() { foo(); //~ ОШИБКА: `foo` является модулем }
Note
selfтакже может использоваться как первый сегмент пути. Использованиеselfкак первого сегмента и внутри фигурных скобокuseлогически одинаково; это означает текущий модуль родительского сегмента или текущий модуль, если нет родительского сегмента. См.selfв главе о путях для получения дополнительной информации о значении ведущегоself.
Глобальные импорты
Символ * может использоваться как последний сегмент пути use для импорта всех импортируемых сущностей из сущности предыдущего сегмента.
Например:
#![allow(unused)] fn main() { // Создаёт непубличный псевдоним для `bar`. use foo::*; mod foo { fn i_am_private() {} enum Example { V1, V2, } pub fn bar() { // Создаёт локальные псевдонимы для `V1` и `V2` // перечисления `Example`. use Example::*; let x = V1; } } }
Элементы и именованные импорты могут затенять имена из глобальных импортов в том же пространстве имён. То есть, если имя уже определено другим элементом в том же пространстве имён, глобальный импорт будет затенён. Например:
#![allow(unused)] fn main() { // Это создаёт привязку к конструктору кортежной структуры `clashing::Foo`, // но не импортирует её тип, потому что это бы // конфликтовало со структурой `Foo`, определённой здесь. // // Заметьте, что порядок определения здесь неважен. use clashing::*; struct Foo { field: f32, } fn do_stuff() { // Использует конструктор из `clashing::Foo`. let f1 = Foo(123); // Выражение структуры использует тип из // структуры `Foo`, определённой выше. let f2 = Foo { field: 1.0 }; // `Bar` также в области видимости из-за глобального импорта. let z = Bar {}; } mod clashing { pub struct Foo(pub i32); pub struct Bar {} } }
* не может использоваться как первый или промежуточный сегмент.
* не может использоваться для импорта содержимого модуля в себя (например, use self::*;).
2018 Edition differences
В редакции 2015 пути относительны к корню крейта, поэтому импорт типа
use *;допустим, и он означает импорт всего из корня крейта. Это не может использоваться в самом корне крейта.
Импорты с подчёркиванием
Элементы могут быть импортированы без привязки к имени с использованием подчёркивания в
форме use path as _. Это особенно полезно для импорта трейта, чтобы
его методы могли использоваться без импорта символа трейта, например,
если символ трейта может конфликтовать с другим символом. Другой пример - это
линковка внешнего крейта без импорта его имени.
Глобальные импорты звездочкой будут импортировать элементы, импортированные с _, в их неименуемой
форме.
mod foo { pub trait Zoo { fn zoo(&self) {} } impl<T> Zoo for T {} } use self::foo::Zoo as _; struct Zoo; // Импорт с подчёркиванием избегает конфликта имён с этим элементом. fn main() { let z = Zoo; z.zoo(); }
Уникальные, неименуемые символы создаются после раскрытия макросов, чтобы
макросы могли безопасно выдавать несколько ссылок на импорты _. Например,
следующее не должно производить ошибку:
#![allow(unused)] fn main() { macro_rules! m { ($item: item) => { $item $item } } m!(use std as _;); // Это раскрывается в: // use std as _; // use std as _; }
Ограничения
Следующие ограничения действуют для допустимых объявлений use:
use crate;должен использоватьasдля определения имени, к которому привязать корень крейта.
use {self};является ошибкой; должен быть ведущий сегмент при использованииself.
- Как и с любым определением элемента, импорты
useне могут создавать дублирующие привязки одного и того же имени в том же пространстве имён в модуле или блоке.
- Пути
useс$crateне разрешены в раскрытииmacro_rules.
- Пути
useне могут ссылаться на варианты перечислений через псевдоним типа. Например:#![allow(unused)] fn main() { enum MyEnum { MyVariant } type TypeAlias = MyEnum; use MyEnum::MyVariant; //~ OK use TypeAlias::MyVariant; //~ ERROR }
Неоднозначности
Note
Этот раздел не завершён.
Некоторые ситуации являются ошибкой, когда есть неоднозначность в том, на какое имя ссылается объявление use. Это происходит, когда есть два кандидата на имя, которые не разрешаются в одну и ту же сущность.
Глобальные импорты разрешены для импорта конфликтующих имён в том же пространстве имён, пока имя не используется. Например:
mod foo { pub struct Qux; } mod bar { pub struct Qux; } use foo::*; use bar::*; //~ OK, нет конфликта имён. fn main() { // Это было бы ошибкой из-за неоднозначности. //let x = Qux; }
Множественные глобальные импорты разрешены для импорта одного и того же имени, и это имя разрешено использовать, если импорты одного и того же элемента (следующие реэкспортам). Видимость имени - это максимальная видимость импортов. Например:
mod foo { pub struct Qux; } mod bar { pub use super::foo::Qux; } // Оба импортируют один и тот же `Qux`. Видимость `Qux` // это `pub`, потому что это максимальная видимость между // этими двумя объявлениями `use`. pub use bar::*; use foo::*; fn main() { let _: Qux = Qux; }
Функции
Syntax
Function →
FunctionQualifiers fn IDENTIFIER GenericParams?
( FunctionParameters? )
FunctionReturnType? WhereClause?
( BlockExpression | ; )
FunctionQualifiers → const? async?1 ItemSafety?2 ( extern Abi? )?
ItemSafety → safe3 | unsafe
Abi → STRING_LITERAL | RAW_STRING_LITERAL
FunctionParameters →
SelfParam ,?
| ( SelfParam , )? FunctionParam ( , FunctionParam )* ,?
SelfParam → OuterAttribute* ( ShorthandSelf | TypedSelf )
ShorthandSelf → ( & | & Lifetime )? mut? self
FunctionParam → OuterAttribute* ( FunctionParamPattern | ... | Type4 )
FunctionParamPattern → PatternNoTopAlt : ( Type | ... )
FunctionReturnType → -> Type
Функция состоит из блока (это тело функции), наряду с именем, набором параметров и типом возвращаемого значения. Кроме имени, все эти элементы являются опциональными.
Функции объявляются с ключевым словом fn, которое определяет данное имя в пространстве имён значений модуля или блока, где она находится.
Функции могут объявлять набор входных переменных в качестве параметров, через которые вызывающая сторона передаёт аргументы в функцию, и выходной тип значения, которое функция вернёт своей вызывающей стороне при завершении.
Если тип возвращаемого значения не указан явно, это единичный тип.
При ссылке на функцию, она порождает первоклассное значение соответствующего нулевого размера типа элемента функции, которое при вызове вычисляется в прямой вызов функции.
Например, это простая функция:
#![allow(unused)] fn main() { fn answer_to_life_the_universe_and_everything() -> i32 { return 42; } }
Квалификатор safe для функции семантически разрешён только при использовании в блоке extern.
Параметры функций
Параметры функций являются неопровержимыми образцами, поэтому любой образец, который допустим в
привязке let без else, также допустим в качестве параметра:
#![allow(unused)] fn main() { fn first((value, _): (i32, i32)) -> i32 { value } }
Если первый параметр является SelfParam, это указывает, что функция является методом.
Функции с параметром self могут появляться только как ассоциированная
функция в трейте или реализации.
Параметр с токеном ... указывает на вариадическую функцию и может
использоваться только как последний параметр функции внешнего блока. Вариадический
параметр может иметь опциональный идентификатор, такой как args: ....
Тело функции
Блочное тело функции концептуально обёрнуто в другой блок, который сначала связывает образцы
аргументов, а затем return возвращает значение тела функции. Это
означает, что конечное выражение блока, если оно вычисляется, в конечном итоге
возвращается вызывающей стороне. Как обычно, явное выражение возврата внутри
тела функции сократит этот неявный возврат, если будет достигнуто.
Например, функция выше ведёт себя так, как если бы она была написана как:
// argument_0 - это фактический первый аргумент, переданный от вызывающей стороны
let (value, _) = argument_0;
return {
value
};
Функции без блока тела завершаются точкой с запятой. Эта форма может появляться только в трейте или внешнем блоке.
Обобщённые функции
Обобщённая функция позволяет одному или нескольким параметризованным типам появляться в её сигнатуре. Каждый параметр типа должен быть явно объявлен в списке, разделённом запятыми и заключённом в угловые скобки, после имени функции.
#![allow(unused)] fn main() { // foo обобщена по A и B fn foo<A, B>(x: A, y: B) { } }
Внутри сигнатуры и тела функции имя параметра типа может быть использовано как имя типа.
Ограничения трейтов могут быть указаны для параметров
типа, чтобы позволить вызывать методы этого трейта для значений этого
типа. Это указывается с использованием синтаксиса where:
#![allow(unused)] fn main() { use std::fmt::Debug; fn foo<T>(x: T) where T: Debug { } }
Когда на обобщённую функцию ссылаются, её тип инстанцируется на основе
контекста ссылки. Например, вызов функции foo здесь:
#![allow(unused)] fn main() { use std::fmt::Debug; fn foo<T>(x: &[T]) where T: Debug { // детали опущены } foo(&[1, 2]); }
инстанцирует параметр типа T как i32.
Параметры типа также могут быть явно указаны в завершающем компоненте пути
после имени функции. Это может быть необходимо, если нет
достаточного контекста для определения параметров типа. Например,
mem::size_of::<u32>() == 4.
Квалификатор внешней функции
Квалификатор extern позволяет предоставлять определения функций, которые могут
быть вызваны с определённым ABI:
extern "ABI" fn foo() { /* ... */ }
Они часто используются в сочетании с элементами внешнего блока, которые предоставляют объявления функций, которые можно использовать для вызова функций без предоставления их определения:
unsafe extern "ABI" {
unsafe fn foo(); /* нет тела */
safe fn bar(); /* нет тела */
}
unsafe { foo() };
bar();
Когда "extern" Abi?* опущен из FunctionQualifiers в элементах функции,
присваивается ABI "Rust". Например:
#![allow(unused)] fn main() { fn foo() {} }
эквивалентно:
#![allow(unused)] fn main() { extern "Rust" fn foo() {} }
Функции могут вызываться иностранным кодом, и использование ABI, которое отличается от Rust, позволяет, например, предоставлять функции, которые могут быть вызваны из других языков программирования, таких как C:
#![allow(unused)] fn main() { // Объявляет функцию с ABI "C" extern "C" fn new_i32() -> i32 { 0 } // Объявляет функцию с ABI "stdcall" #[cfg(any(windows, target_arch = "x86"))] extern "stdcall" fn new_i32_stdcall() -> i32 { 0 } }
Так же, как с внешним блоком, когда используется ключевое слово extern и "ABI"
опущен, используемый ABI по умолчанию становится "C". То есть это:
#![allow(unused)] fn main() { extern fn new_i32() -> i32 { 0 } let fptr: extern fn() -> i32 = new_i32; }
эквивалентно:
#![allow(unused)] fn main() { extern "C" fn new_i32() -> i32 { 0 } let fptr: extern "C" fn() -> i32 = new_i32; }
Раскрутка стека
Большинство строк ABI имеют два варианта: один с суффиксом -unwind и один без. ABI Rust всегда разрешает раскрутку стека, поэтому нет ABI Rust-unwind. Выбор ABI вместе с обработчиком паники времени выполнения определяет поведение при раскрутке стека из функции.
В таблице ниже показано поведение операции раскрутки стека, достигающей каждого типа границы ABI (объявление или определение функции с использованием соответствующей строки ABI). Обратите внимание, что среда выполнения Rust не затрагивается и не может влиять на любую раскрутку стека, которая происходит полностью внутри среды выполнения другого языка, то есть раскрутки стека, которые выбрасываются и перехватываются без достижения границы ABI Rust.
Столбец panic-unwind относится к панике через макрос panic! и аналогичные механизмы стандартной библиотеки, а также к любым другим операциям Rust, которые вызывают панику, таким как индексация массива вне границ или переполнение целого числа.
Категория ABI “unwinding” относится к "Rust" (неявный ABI функций Rust, не помеченных extern), "C-unwind" и любым другим ABI с -unwind в их имени. Категория ABI “non-unwinding” относится ко всем другим строкам ABI, включая "C" и "stdcall".
Нативная раскрутка стека определяется для каждой цели. На целях, которые поддерживают выбрасывание и перехват исключений C++, это относится к механизму, используемому для реализации этой функции. Некоторые платформы реализуют форму раскрутки стека, называемую “принудительной раскруткой стека”; longjmp на Windows и pthread_exit в glibc реализованы таким образом. Принудительная раскрутка стека явно исключена из столбца “Native unwind” в таблице.
| Среда выполнения паники | ABI | panic-unwind | Нативная раскрутка стека (непринудительная) |
|---|---|---|---|
panic=unwind | unwinding | раскрутка стека | раскрутка стека |
panic=unwind | non-unwinding | прерывание (см. примечания ниже) | неопределённое поведение |
panic=abort | unwinding | panic прерывает без раскрутки стека | прерывание |
panic=abort | non-unwinding | panic прерывает без раскрутки стека | неопределённое поведение |
С panic=unwind, когда panic превращается в прерывание границей ABI без раскрутки стека, либо не будут запущены никакие деструкторы (вызовы Drop), либо все деструкторы до границы ABI будут запущены. Не указано, какое из этих двух поведений произойдёт.
Для других соображений и ограничений относительно раскрутки стека через границы FFI см. соответствующий раздел в документации по Panic.
Константные функции
См. константные функции для определения константных функций.
Асинхронные функции
Функции могут быть квалифицированы как async, и это также может быть объединено с
квалификатором unsafe:
#![allow(unused)] fn main() { async fn regular_example() { } async unsafe fn unsafe_example() { } }
Асинхронные функции не выполняют работу при вызове: вместо этого они захватывают свои аргументы в future. При опросе этот future будет выполнять тело функции.
Асинхронная функция примерно эквивалентна функции,
которая возвращает impl Future и с async move блоком в качестве
своего тела:
#![allow(unused)] fn main() { // Исходный код async fn example(x: &str) -> usize { x.len() } }
грубо эквивалентно:
#![allow(unused)] fn main() { use std::future::Future; // Десугаризованный fn example<'a>(x: &'a str) -> impl Future<Output = usize> + 'a { async move { x.len() } } }
Фактическая десугаризация более сложна:
- Предполагается, что тип возвращаемого значения в десугаризации захватывает все параметры
времени жизни из объявления
async fn. Это можно увидеть в десугаризованном примере выше, который явно переживает и, следовательно, захватывает'a.
async moveблок в теле захватывает все параметры функции, включая те, которые не используются или привязаны к образцу_. Это гарантирует, что параметры функции удаляются в том же порядке, как если бы функция не была асинхронной, за исключением того, что удаление происходит, когда возвращённый future был полностью ожидан.
Для получения дополнительной информации о эффекте async см. async блоки.
2018 Edition differences
Асинхронные функции доступны только начиная с Rust 2018.
Комбинирование async и unsafe
Законно объявлять функцию, которая является одновременно async и unsafe. Результирующая функция небезопасна для вызова и (как и любая асинхронная функция) возвращает future. Этот future - просто обычный future, и поэтому небезопасный контекст не требуется для его “ожидания”:
#![allow(unused)] fn main() { // Возвращает future, который при ожидании разыменовывает `x`. // // Условие звуковости: `x` должен быть безопасен для разыменования до // завершения результирующего future. async unsafe fn unsafe_example(x: *const i32) -> i32 { *x } async fn safe_example() { // Небезопасный блок требуется для первоначального вызова функции: let p = 22; let future = unsafe { unsafe_example(&p) }; // Но здесь небезопасный блок не требуется. Это будет // читать значение `p`: let q = future.await; } }
Обратите внимание, что это поведение является следствием десугаризации в
функцию, которая возвращает impl Future - в этом случае функция,
в которую мы десугаризируем, является unsafe функцией, но возвращаемое значение остаётся
тем же.
Unsafe используется в асинхронной функции точно так же, как он
используется в других функциях: это указывает, что функция накладывает
некоторые дополнительные обязательства на свою вызывающую сторону для обеспечения звуковости. Как и в любой
другой небезопасной функции, эти условия могут распространяться за пределы самого первоначального
вызова - в приведённом выше фрагменте, например, функция unsafe_example
принимала указатель x в качестве аргумента, а затем (при ожидании)
разыменовывала этот указатель. Это подразумевает, что x должен был бы
быть действительным до завершения выполнения future, и это ответственность
вызывающей стороны обеспечить это.
Атрибуты на функциях
Внешние атрибуты разрешены на функциях. Внутренние
атрибуты разрешены непосредственно после { внутри её тела блока.
Этот пример показывает внутренний атрибут на функции. Функция документирована только словом “Example”.
#![allow(unused)] fn main() { fn documented() { #![doc = "Example"] } }
Note
За исключением линтов, идиоматично использовать только внешние атрибуты на элементах функций.
Атрибуты, которые имеют смысл на функции:
cfg_attrcfgcolddeprecateddocexport_nameinlinelink_sectionmust_useno_mangle- Атрибуты проверки линтов
- Атрибуты процедурных макросов
- Атрибуты тестирования
Атрибуты на параметрах функций
Внешние атрибуты разрешены на параметрах функций и
разрешённые встроенные атрибуты ограничены cfg, cfg_attr, allow,
warn, deny и forbid.
#![allow(unused)] fn main() { fn len( #[cfg(windows)] slice: &[u16], #[cfg(not(windows))] slice: &[u8], ) -> usize { slice.len() } }
Инертные вспомогательные атрибуты, используемые атрибутами процедурных макросов, применёнными к элементам, также
разрешены, но будьте осторожны, чтобы не включать эти инертные атрибуты в ваш окончательный TokenStream.
Например, следующий код определяет инертный атрибут some_inert_attribute, который
нигде формально не определён, и процедурный макрос some_proc_macro_attribute отвечает
за обнаружение его присутствия и удаление из выходного потока токенов.
#[some_proc_macro_attribute]
fn foo_oof(#[some_inert_attribute] arg: u8) {
}
-
Квалификатор
asyncне разрешён в редакции 2015. ↩ -
Актуально для редакций ранее Rust 2024: Внутри блоков
externквалификатор функцииsafeилиunsafeразрешён только когдаexternквалифицирован какunsafe. ↩ -
Квалификатор функции
safeсемантически разрешён только внутри блоковextern. ↩ -
Параметры функций только с типом разрешены только в ассоциированной функции элемента трейта в редакции 2015. ↩
Псевдонимы типов
Syntax
TypeAlias →
type IDENTIFIER GenericParams? ( : TypeParamBounds )?
WhereClause?
( = Type WhereClause? )? ;
Псевдоним типа определяет новое имя для существующего типа в пространстве имён типов модуля или блока, где он находится.
Псевдонимы типов объявляются с ключевым словом type.
Каждое значение имеет единственный, конкретный тип, но может реализовывать несколько различных трейтов и может быть совместимо с несколькими различными ограничениями типов.
Например, следующее определяет тип Point как синоним типа
(u8, u8), типа пар 8-битных целых чисел без знака:
#![allow(unused)] fn main() { type Point = (u8, u8); let p: Point = (41, 68); }
Псевдоним типа для кортежной структуры или единичной структуры не может использоваться для квалификации конструктора этого типа:
#![allow(unused)] fn main() { struct MyStruct(u32); use MyStruct as UseAlias; type TypeAlias = MyStruct; let _ = UseAlias(5); // OK let _ = TypeAlias(5); // Не работает }
Псевдоним типа, когда не используется как ассоциированный тип, должен включать Тип и не может включать TypeParamBounds.
Псевдоним типа, когда используется как ассоциированный тип в трейте, не должен включать спецификацию Типа, но может включать TypeParamBounds.
Псевдоним типа, когда используется как ассоциированный тип в реализации трейта, должен включать спецификацию Типа и не может включать TypeParamBounds.
Where-предложения перед знаком равенства в псевдониме типа в реализации трейта (например,
type TypeAlias<T> where T: Foo = Bar<T>) устарели. Where-предложения после
знака равенства (например, type TypeAlias<T> = Bar<T> where T: Foo) предпочтительны.
Структуры
Syntax
Struct →
StructStruct
| TupleStruct
StructStruct →
struct IDENTIFIER GenericParams? WhereClause? ( { StructFields? } | ; )
TupleStruct →
struct IDENTIFIER GenericParams? ( TupleFields? ) WhereClause? ;
StructFields → StructField ( , StructField )* ,?
StructField → OuterAttribute* Visibility? IDENTIFIER : Type
TupleFields → TupleField ( , TupleField )* ,?
Структура - это именованный структурный тип, определённый с помощью ключевого слова struct.
Объявление структуры определяет данное имя в пространстве имён типов модуля или блока, где она находится.
Пример элемента struct и его использования:
#![allow(unused)] fn main() { struct Point {x: i32, y: i32} let p = Point {x: 10, y: 11}; let px: i32 = p.x; }
Кортежная структура - это именованный кортежный тип, который также определяется с помощью ключевого слова struct.
Помимо определения типа, она также определяет конструктор с тем же именем в пространстве имён значений.
Конструктор - это функция, которую можно вызвать для создания нового экземпляра структуры.
Например:
#![allow(unused)] fn main() { struct Point(i32, i32); let p = Point(10, 11); let px: i32 = match p { Point(x, _) => x }; }
Единичная структура - это структура без каких-либо полей, определяемая полным отсутствием списка полей. Такая структура неявно определяет константу своего типа с тем же именем. Например:
#![allow(unused)] fn main() { struct Cookie; let c = [Cookie, Cookie {}, Cookie, Cookie {}]; }
эквивалентно
#![allow(unused)] fn main() { struct Cookie {} const Cookie: Cookie = Cookie {}; let c = [Cookie, Cookie {}, Cookie, Cookie {}]; }
Точное расположение структуры в памяти не специфицировано. Можно указать
конкретное расположение с помощью атрибута repr.
Перечисления
Syntax
Enumeration →
enum IDENTIFIER GenericParams? WhereClause? { EnumVariants? }
EnumVariants → EnumVariant ( , EnumVariant )* ,?
EnumVariant →
OuterAttribute* Visibility?
IDENTIFIER ( EnumVariantTuple | EnumVariantStruct )? EnumVariantDiscriminant?
EnumVariantTuple → ( TupleFields? )
EnumVariantStruct → { StructFields? }
Перечисление, также называемое enum, - это одновременное определение именованного типа перечисления, а также набора конструкторов, которые могут быть использованы для создания или сопоставления с образцом значений соответствующего типа перечисления.
Перечисления объявляются с ключевым словом enum.
Объявление enum определяет тип перечисления в пространстве имён типов модуля или блока, где оно находится.
Пример элемента enum и его использования:
#![allow(unused)] fn main() { enum Animal { Dog, Cat, } let mut a: Animal = Animal::Dog; a = Animal::Cat; }
Конструкторы перечислений могут иметь именованные или неименованные поля:
#![allow(unused)] fn main() { enum Animal { Dog(String, f64), Cat { name: String, weight: f64 }, } let mut a: Animal = Animal::Dog("Cocoa".to_string(), 37.2); a = Animal::Cat { name: "Spotty".to_string(), weight: 2.7 }; }
В этом примере Cat является вариантом перечисления, похожим на структуру, тогда как Dog просто
называется вариантом перечисления.
Перечисление, в котором ни один конструктор не содержит полей, называется бесполым перечислением. Например, это бесполое перечисление:
#![allow(unused)] fn main() { enum Fieldless { Tuple(), Struct{}, Unit, } }
Если бесполое перечисление содержит только единичные варианты, перечисление называется только-единичным перечислением. Например:
#![allow(unused)] fn main() { enum Enum { Foo = 3, Bar = 2, Baz = 1, } }
Конструкторы вариантов похожи на определения структур и могут быть доступны по пути от имени перечисления, включая объявления use.
Каждый вариант определяет свой тип в пространстве имён типов, хотя этот тип не может использоваться как спецификатор типа. Кортежные и единичные варианты также определяют конструктор в пространстве имён значений.
Вариант, похожий на структуру, может быть создан с помощью выражения структуры.
Кортежный вариант может быть создан с помощью вызовного выражения или выражения структуры.
Единичный вариант может быть создан с помощью выражения пути или выражения структуры. Например:
#![allow(unused)] fn main() { enum Examples { UnitLike, TupleLike(i32), StructLike { value: i32 }, } use Examples::*; // Создаёт псевдонимы для всех вариантов. let x = UnitLike; // Выражение пути константного элемента. let x = UnitLike {}; // Выражение структуры. let y = TupleLike(123); // Вызовное выражение. let y = TupleLike { 0: 123 }; // Выражение структуры с использованием целочисленных имён полей. let z = StructLike { value: 123 }; // Выражение структуры. }
Дискриминанты
Каждый экземпляр перечисления имеет дискриминант: целое число, логически связанное с ним, которое используется для определения того, какой вариант он содержит.
При представлении Rust дискриминант интерпретируется как
значение isize. Однако компилятор может использовать меньший тип (или
другие средства различения вариантов) в своём фактическом расположении в памяти.
Присваивание значений дискриминантов
Явные дискриминанты
В двух случаях дискриминант варианта может быть явно установлен
после имени варианта с помощью = и константного выражения:
- если перечисление является “только-единичным”.
-
если используется примитивное представление. Например:
#![allow(unused)] fn main() { #[repr(u8)] enum Enum { Unit = 3, Tuple(u16), Struct { a: u8, b: u16, } = 1, } }
Неявные дискриминанты
Если дискриминант для варианта не указан, то он устанавливается на единицу больше, чем дискриминант предыдущего варианта в объявлении. Если дискриминант первого варианта в объявлении не указан, то он устанавливается в ноль.
#![allow(unused)] fn main() { enum Foo { Bar, // 0 Baz = 123, // 123 Quux, // 124 } let baz_discriminant = Foo::Baz as u32; assert_eq!(baz_discriminant, 123); }
Ограничения
Ошибкой является случай, когда два варианта имеют одинаковый дискриминант.
#![allow(unused)] fn main() { enum SharedDiscriminantError { SharedA = 1, SharedB = 1 } enum SharedDiscriminantError2 { Zero, // 0 One, // 1 OneToo = 1 // 1 (коллизия с предыдущим!) } }
Также ошибкой является наличие неопределённого дискриминанта, когда предыдущий дискриминант является максимальным значением для размера дискриминанта.
#![allow(unused)] fn main() { #[repr(u8)] enum OverflowingDiscriminantError { Max = 255, MaxPlusOne // Был бы 256, но это переполняет перечисление. } #[repr(u8)] enum OverflowingDiscriminantError2 { MaxMinusOne = 254, // 254 Max, // 255 MaxPlusOne // Был бы 256, но это переполняет перечисление. } }
Доступ к дискриминанту
Через mem::discriminant
std::mem::discriminant возвращает непрозрачную ссылку на дискриминант
значения перечисления, которую можно сравнивать. Это нельзя использовать для получения значения
дискриминанта.
Приведение
Если перечисление является только-единичным (без кортежных и структурных вариантов), то его дискриминант может быть напрямую доступен с помощью числового приведения; например:
#![allow(unused)] fn main() { enum Enum { Foo, Bar, Baz, } assert_eq!(0, Enum::Foo as isize); assert_eq!(1, Enum::Bar as isize); assert_eq!(2, Enum::Baz as isize); }
Бесполые перечисления могут быть приведены, если они не имеют явных дискриминантов или где только единичные варианты являются явными.
#![allow(unused)] fn main() { enum Fieldless { Tuple(), Struct{}, Unit, } assert_eq!(0, Fieldless::Tuple() as isize); assert_eq!(1, Fieldless::Struct{} as isize); assert_eq!(2, Fieldless::Unit as isize); #[repr(u8)] enum FieldlessWithDiscriminants { First = 10, Tuple(), Second = 20, Struct{}, Unit, } assert_eq!(10, FieldlessWithDiscriminants::First as u8); assert_eq!(11, FieldlessWithDiscriminants::Tuple() as u8); assert_eq!(20, FieldlessWithDiscriminants::Second as u8); assert_eq!(21, FieldlessWithDiscriminants::Struct{} as u8); assert_eq!(22, FieldlessWithDiscriminants::Unit as u8); }
Приведение указателей
Если перечисление указывает примитивное представление, то дискриминант может быть надёжно доступен через небезопасное приведение указателей:
#![allow(unused)] fn main() { #[repr(u8)] enum Enum { Unit, Tuple(bool), Struct{a: bool}, } impl Enum { fn discriminant(&self) -> u8 { unsafe { *(self as *const Self as *const u8) } } } let unit_like = Enum::Unit; let tuple_like = Enum::Tuple(true); let struct_like = Enum::Struct{a: false}; assert_eq!(0, unit_like.discriminant()); assert_eq!(1, tuple_like.discriminant()); assert_eq!(2, struct_like.discriminant()); }
Перечисления без вариантов
Перечисления без вариантов известны как перечисления с нулевым количеством вариантов. Поскольку у них нет допустимых значений, они не могут быть созданы.
#![allow(unused)] fn main() { enum ZeroVariants {} }
Перечисления с нулевым количеством вариантов эквивалентны типу never, но они не могут быть приведены к другим типам.
#![allow(unused)] fn main() { enum ZeroVariants {} let x: ZeroVariants = panic!(); let y: u32 = x; // ошибка несоответствия типов }
Видимость вариантов
Варианты перечислений синтаксически допускают аннотацию Видимости, но это отклоняется при проверке перечисления. Это позволяет разбирать элементы с унифицированным синтаксисом в различных контекстах, где они используются.
#![allow(unused)] fn main() { macro_rules! mac_variant { ($vis:vis $name:ident) => { enum $name { $vis Unit, $vis Tuple(u8, u16), $vis Struct { f: u8 }, } } } // Пустой `vis` разрешён. mac_variant! { E } // Это разрешено, поскольку удаляется до проверки. #[cfg(false)] enum E { pub U, pub(crate) T(u8), pub(super) T { f: String } } }
Объединения
Syntax
Union →
union IDENTIFIER GenericParams? WhereClause? { StructFields? }
Объявление объединения использует тот же синтаксис, что и объявление структуры, за исключением того,
что используется union вместо struct.
Объявление объединения определяет данное имя в пространстве имён типов модуля или блока, где оно находится.
#![allow(unused)] fn main() { #[repr(C)] union MyUnion { f1: u32, f2: f32, } }
Ключевое свойство объединений заключается в том, что все поля объединения используют общее хранилище. В результате, запись в одно поле объединения может перезаписать другие его поля, и размер объединения определяется размером его наибольшего поля.
Типы полей объединения ограничены следующим подмножеством типов:
- Типы, реализующие
Copy
- Ссылки (
&Tи&mut Tдля произвольногоT)
ManuallyDrop<T>(для произвольногоT)
- Кортежи и массивы, содержащие только разрешённые типы полей объединения
Это ограничение гарантирует, в частности, что поля объединения никогда не нуждаются
в освобождении памяти. Как и для структур и перечислений, можно реализовать Drop для объединения,
чтобы вручную определить, что происходит при его удалении.
Объединения без полей не принимаются компилятором, но могут приниматься макросами.
Инициализация объединения
Значение типа объединения может быть создано с использованием того же синтаксиса, что и для типов структур, за исключением того, что должно быть указано ровно одно поле:
#![allow(unused)] fn main() { union MyUnion { f1: u32, f2: f32 } let u = MyUnion { f1: 1 }; }
Выражение выше создаёт значение типа MyUnion и инициализирует
хранилище, используя поле f1. Доступ к объединению может быть получен с использованием того же синтаксиса,
что и для полей структур:
#![allow(unused)] fn main() { union MyUnion { f1: u32, f2: f32 } let u = MyUnion { f1: 1 }; let f = unsafe { u.f1 }; }
Чтение и запись полей объединения
У объединений нет понятия “активного поля”. Вместо этого каждый доступ к объединению просто интерпретирует хранилище как тип поля, используемого для доступа.
Чтение поля объединения считывает биты объединения как тип поля.
Поля могут иметь ненулевое смещение (кроме случаев, когда используется C-представление); в этом случае считываются биты, начиная со смещения поля.
Ответственность за обеспечение валидности данных для типа поля лежит на программисте. Невыполнение
этого требования приводит к неопределённому поведению. Например, чтение значения 3
из поля логического типа является неопределённым поведением. Фактически,
запись и последующее чтение из объединения с C-представлением аналогично
transmute из типа, использованного для записи, в тип, использованный для чтения.
Следовательно, все операции чтения полей объединения должны размещаться в блоках unsafe:
#![allow(unused)] fn main() { union MyUnion { f1: u32, f2: f32 } let u = MyUnion { f1: 1 }; unsafe { let f = u.f1; } }
Обычно код, использующий объединения, предоставляет безопасные обёртки вокруг небезопасных операций доступа к полям объединения.
В отличие от чтения, записи в поля объединения являются безопасными, поскольку они просто перезаписывают произвольные данные, но не могут вызвать неопределённое поведение. (Заметим, что типы полей объединения никогда не могут иметь деструкторов, поэтому запись в поле объединения никогда не приведёт к неявному освобождению памяти.)
Сопоставление с образцом для объединений
Другой способ доступа к полям объединения - использование сопоставления с образцом.
Сопоставление с образцом для полей объединений использует тот же синтаксис, что и для структур, за исключением того, что образец должен указывать ровно одно поле.
Поскольку сопоставление с образцом аналогично чтению объединения с определённым полем, оно также должно размещаться в блоках unsafe.
#![allow(unused)] fn main() { union MyUnion { f1: u32, f2: f32 } fn f(u: MyUnion) { unsafe { match u { MyUnion { f1: 10 } => { println!("ten"); } MyUnion { f2 } => { println!("{}", f2); } } } } }
Сопоставление с образцом может сопоставлять объединение как поле более крупной структуры. В частности, при использовании объединения Rust для реализации размеченного объединения C через FFI, это позволяет одновременно сопоставлять метку и соответствующее поле:
#![allow(unused)] fn main() { #[repr(u32)] enum Tag { I, F } #[repr(C)] union U { i: i32, f: f32, } #[repr(C)] struct Value { tag: Tag, u: U, } fn is_zero(v: Value) -> bool { unsafe { match v { Value { tag: Tag::I, u: U { i: 0 } } => true, Value { tag: Tag::F, u: U { f: num } } if num == 0.0 => true, _ => false, } } } }
Ссылки на поля объединения
Поскольку поля объединения используют общее хранилище, получение доступа на запись к одному полю объединения может дать доступ на запись ко всем его остальным полям.
Правила проверки заимствований должны быть скорректированы с учётом этого факта. В результате, если одно поле объединения заимствовано, все его остальные поля также считаются заимствованными на то же время жизни.
#![allow(unused)] fn main() { union MyUnion { f1: u32, f2: f32 } // ОШИБКА: нельзя заимствовать `u` (через `u.f2`) как изменяемый более одного раза одновременно fn test() { let mut u = MyUnion { f1: 1 }; unsafe { let b1 = &mut u.f1; // ---- первое изменяемое заимствование происходит здесь (через `u.f1`) let b2 = &mut u.f2; // ^^^^ второе изменяемое заимствование происходит здесь (через `u.f2`) *b1 = 5; } // - первое заимствование заканчивается здесь assert_eq!(unsafe { u.f1 }, 5); } }
Как можно заметить, во многих аспектах (за исключением расположения в памяти, безопасности и владения) объединения ведут себя точно так же, как структуры, во многом как следствие наследования их синтаксической формы от структур. Это также верно для многих неупомянутых аспектов языка Rust (таких как приватность, разрешение имён, вывод типов, обобщённое программирование, реализации трейтов, собственные реализации, когерентность, проверка образцов и т.д., т.д., т.д.).
Константные элементы
Syntax
ConstantItem →
const ( IDENTIFIER | _ ) : Type ( = Expression )? ;
Константный элемент - это опционально именованное константное значение, которое не связано с конкретным местом в памяти программы.
Константы по сути встраиваются везде, где они используются, что означает, что они копируются непосредственно в соответствующий
контекст при использовании. Это включает использование констант из внешних крейтов и
не-Copy типы. Ссылки на одну и ту же константу не обязательно
гарантированно ссылаются на один и тот же адрес памяти.
Объявление константы определяет константное значение в пространстве имён значений модуля или блока, где она находится.
Константы должны быть явно типизированы. Тип должен иметь время жизни 'static: любые
ссылки в инициализаторе должны иметь время жизни 'static. Ссылки
в типе константы по умолчанию имеют время жизни 'static; см. статическое элизирование времени жизни.
Ссылка на константу будет иметь время жизни 'static, если значение константы подходит для
продвижения; в противном случае будет создана временная переменная.
#![allow(unused)] fn main() { const BIT1: u32 = 1 << 0; const BIT2: u32 = 1 << 1; const BITS: [u32; 2] = [BIT1, BIT2]; const STRING: &'static str = "bitstring"; struct BitsNStrings<'a> { mybits: [u32; 2], mystring: &'a str, } const BITS_N_STRINGS: BitsNStrings<'static> = BitsNStrings { mybits: BITS, mystring: STRING, }; }
Финальное значение элемента const, после вычисления инициализатора до значения, которое имеет объявленный тип константы, не может содержать никаких изменяемых ссылок, кроме случаев, описанных ниже.
#![allow(unused)] fn main() { #![allow(static_mut_refs)] static mut S: u8 = 0; const _: &u8 = unsafe { &mut S }; // OK. // ^^^^^^ // Разрешено, так как это приводится к `&S`. }
#![allow(unused)] fn main() { use core::sync::atomic::AtomicU8; static S: AtomicU8 = AtomicU8::new(0); const _: &AtomicU8 = &S; // OK. // ^^ // Разрешено, даже несмотря на то, что разделяемая ссылка указывает на внутренне // изменяемое значение. }
#![allow(unused)] fn main() { #![allow(static_mut_refs)] static mut S: u8 = 0; const _: &mut u8 = unsafe { &mut S }; // ОШИБКА. // ^^^^^^ // Не разрешено, так как изменяемая ссылка появляется в финальном значении. }
Note
Инициализаторы констант можно рассматривать, в большинстве случаев, как встраиваемые везде, где появляется константа. Если бы константа, значение которой содержит изменяемую ссылку на изменяемую статическую переменную, появлялась дважды, и это было бы разрешено, это создало бы две изменяемые ссылки, каждая с временем жизни
'static, на одно и то же место. Это могло бы привести к неопределённому поведению.Константы, которые содержат изменяемые ссылки на временные переменные, чьи области видимости были расширены до конца программы, имеют ту же проблему и дополнительную.
#![allow(unused)] fn main() { const _: &mut u8 = &mut 0; // ОШИБКА. // ^^^^^^ // Не разрешено, так как изменяемая ссылка появляется в финальном значении и // потому что константное выражение содержит изменяемое заимствование // выражения, чья временная область видимости была бы расширена до конца // программы. }Здесь значение
0является временной переменной, чья область видимости расширена до конца программы (см. destructors.scope.lifetime-extension.static). Такие временные переменные не могут быть изменяемо заимствованы в константных выражениях (см. const-eval.const-expr.borrows).Чтобы разрешить это, нам пришлось бы решить, создаёт ли каждое использование константы новое значение
u8или каждое использование разделяет одну и ту же временную переменную с расширенным временем жизни. Последний выбор, хотя ближе к тому, какrustcдумает об этом сегодня, нарушил бы концептуальную модель, что в большинстве случаев инициализатор константы можно рассматривать как встраиваемый везде, где используется константа. Поскольку мы не решили, и из-за другой упомянутой проблемы, это не разрешено.
#![allow(unused)] fn main() { #![allow(static_mut_refs)] static mut S: u8 = 0; const _: &dyn Send = &unsafe { &mut S }; // ОШИБКА. // ^^^^^^ // Не разрешено, так как изменяемая ссылка появляется в финальном значении, // даже несмотря на стирание типа. }
Изменяемые ссылки, где указываемый объект является значением типа нулевого размера, разрешены.
#![allow(unused)] fn main() { #![allow(static_mut_refs)] static mut S: () = (); const _: &mut () = unsafe { &mut S }; // OK. // ^^ Это тип нулевого размера. }
#![allow(unused)] fn main() { #![allow(static_mut_refs)] static mut S: [u8; 0] = [0; 0]; const _: &mut [u8; 0] = unsafe { &mut S }; // OK. // ^^^^^^^ Это тип нулевого размера. }
Note
Это разрешено, так как для значения типа нулевого размера никакие байты не могут быть фактически изменены. Мы должны принять это, так как
&mut []продвигается.
Значения типа объединения не считаются содержащими какие-либо ссылки; для этой цели значение типа объединения обрабатывается как последовательность нетипизированных байтов.
#![allow(unused)] fn main() { #![allow(static_mut_refs)] union U { f: &'static mut u8 } static mut S: u8 = 0; const _: U = unsafe { U { f: &mut S }}; // OK. // ^^^^^^^^^^^^^^^ // Это обрабатывается как последовательность нетипизированных байтов. }
Изменяемые ссылки, содержащиеся внутри изменяемой статической переменной, могут быть ссылаемы в финальном значении константы.
#![allow(unused)] fn main() { #![allow(static_mut_refs)] static mut S: &mut u8 = unsafe { static mut I: u8 = 0; &mut I }; const _: &&mut u8 = unsafe { &S }; // OK. // ^^^^^^^ // Эта изменяемая ссылка происходит из `static mut`. }
Note
Это разрешено, так как отдельно не разрешено читать из изменяемой статической переменной во время вычисления константы. См. const-eval.const-expr.path-static.
Изменяемые ссылки, содержащиеся внутри внешней статической переменной, могут быть ссылаемы в финальном значении константы.
#![allow(unused)] fn main() { #![allow(static_mut_refs)] unsafe extern "C" { static S: &'static mut u8; } const _: &&mut u8 = unsafe { &S }; // OK. // ^^^^^^^ // Эта изменяемая ссылка происходит из внешней статической переменной. }
Note
Это разрешено, так как отдельно не разрешено читать из внешней статической переменной во время вычисления константы. См. const-eval.const-expr.path-static.
Note
Как описано выше, мы принимаем в финальном значении константных элементов разделяемые ссылки на статические элементы, чьи значения имеют внутреннюю изменяемость.
#![allow(unused)] fn main() { use core::sync::atomic::AtomicU8; static S: AtomicU8 = AtomicU8::new(0); const _: &AtomicU8 = &S; // OK. }Однако мы запрещаем аналогичный код, когда внутренне изменяемое значение создаётся в инициализаторе.
#![allow(unused)] fn main() { use core::sync::atomic::AtomicU8; const _: &AtomicU8 = &AtomicU8::new(0); // ОШИБКА. }Здесь
AtomicU8является временной переменной, чья область видимости расширена до конца программы (см. destructors.scope.lifetime-extension.static). Такие временные переменные с внутренней изменяемостью не могут быть заимствованы в константных выражениях (см. const-eval.const-expr.borrows).Чтобы разрешить это, нам пришлось бы решить, создаёт ли каждое использование константы новый
AtomicU8или каждое использование разделяет одну и ту же временную переменную с расширенным временем жизни. Последний выбор, хотя ближе к тому, какrustcдумает об этом сегодня, нарушил бы концептуальную модель, что в большинстве случаев инициализатор константы можно рассматривать как встраиваемый везде, где используется константа. Поскольку мы не решили, это не разрешено.
Константное выражение может быть опущено только в определении трейта.
Константы с деструкторами
Константы могут содержать деструкторы. Деструкторы запускаются, когда значение выходит из области видимости.
#![allow(unused)] fn main() { struct TypeWithDestructor(i32); impl Drop for TypeWithDestructor { fn drop(&mut self) { println!("Dropped. Held {}.", self.0); } } const ZERO_WITH_DESTRUCTOR: TypeWithDestructor = TypeWithDestructor(0); fn create_and_drop_zero_with_destructor() { let x = ZERO_WITH_DESTRUCTOR; // x удаляется в конце функции, вызывая drop. // выводит "Dropped. Held 0.". } }
Безымянные константы
В отличие от ассоциированной константы, свободная константа может быть безымянной с использованием подчёркивания вместо имени. Например:
#![allow(unused)] fn main() { const _: () = { struct _SameNameTwice; }; // OK, хотя это то же имя, что и выше: const _: () = { struct _SameNameTwice; }; }
Как и с импортами с подчёркиванием, макросы могут безопасно выдавать одну и ту же безымянную константу в той же области видимости более одного раза. Например, следующее не должно производить ошибку:
#![allow(unused)] fn main() { macro_rules! m { ($item: item) => { $item $item } } m!(const _: () = ();); // Это раскрывается в: // const _: () = (); // const _: () = (); }
Вычисление
Свободные константы всегда вычисляются во время компиляции для выявления паник. Это происходит даже внутри неиспользуемой функции:
#![allow(unused)] fn main() { // Паника во время компиляции const PANIC: () = std::unimplemented!(); fn unused_generic_function<T>() { // Неудачная проверка во время компиляции const _: () = assert!(usize::BITS == 0); } }
Статические элементы
Syntax
StaticItem →
ItemSafety?1 static mut? IDENTIFIER : Type ( = Expression )? ;
Статический элемент похож на константу, за исключением того, что он представляет выделение памяти в программе, которое инициализируется выражением инициализатора. Все ссылки и сырые указатели на статическую переменную ссылаются на одно и то же выделение памяти.
Статические элементы имеют время жизни static, которое переживает все другие времена жизни в программе Rust.
Статические элементы не вызывают drop в конце программы.
Если static имеет размер не менее 1 байта, это выделение памяти не пересекается со всеми другими такими
выделениями static, а также с выделениями в куче и переменными в стеке. Однако хранилище
неизменяемых static элементов может перекрываться с выделениями, которые сами не имеют уникального адреса, таких как
продвинутые значения и элементы const.
Объявление static определяет статическое значение в пространстве имён значений модуля или блока, где оно находится.
Статический инициализатор является константным выражением, вычисляемым во время компиляции. Статические инициализаторы могут ссылаться на другие статические переменные и читать их. При чтении из изменяемых статических переменных они читают начальное значение этой статической переменной.
Не-mut статические элементы, которые содержат тип, не являющийся внутренне изменяемым, могут
быть размещены в памяти только для чтения.
Весь доступ к статической переменной безопасен, но существует ряд ограничений на статические переменные:
- Тип должен иметь ограничение трейта
Sync, чтобы разрешить потокобезопасный доступ.
Выражение инициализатора должно быть опущено во внешнем блоке и должно быть предоставлено для свободных статических элементов.
Квалификаторы safe и unsafe семантически разрешены только при использовании во внешнем блоке.
Статические переменные и обобщения
Статический элемент, определённый в обобщённой области видимости (например, в общем или стандартном реализации), приведёт к определению ровно одного статического элемента, как если бы статическое определение было вынесено из текущей области видимости в модуль. НЕ будет одного элемента на каждую мономорфизацию.
Этот код:
use std::sync::atomic::{AtomicUsize, Ordering}; trait Tr { fn default_impl() { static COUNTER: AtomicUsize = AtomicUsize::new(0); println!("default_impl: counter was {}", COUNTER.fetch_add(1, Ordering::Relaxed)); } fn blanket_impl(); } struct Ty1 {} struct Ty2 {} impl<T> Tr for T { fn blanket_impl() { static COUNTER: AtomicUsize = AtomicUsize::new(0); println!("blanket_impl: counter was {}", COUNTER.fetch_add(1, Ordering::Relaxed)); } } fn main() { <Ty1 as Tr>::default_impl(); <Ty2 as Tr>::default_impl(); <Ty1 as Tr>::blanket_impl(); <Ty2 as Tr>::blanket_impl(); }
выводит
default_impl: counter was 0
default_impl: counter was 1
blanket_impl: counter was 0
blanket_impl: counter was 1
Изменяемые статические переменные
Если статический элемент объявлен с ключевым словом mut, то программе разрешено
его изменять. Одна из целей Rust - сделать ошибки параллелизма трудными
для возникновения, и это, очевидно, очень большой источник состояний гонки или
других ошибок.
По этой причине unsafe блок требуется при чтении
или записи изменяемой статической переменной. Следует позаботиться о том, чтобы
изменения изменяемой статической переменной были безопасны по отношению к другим потокам,
выполняющимся в том же процессе.
Тем не менее, изменяемые статические переменные всё ещё очень полезны. Они могут использоваться с библиотеками C
и также могут быть привязаны из библиотек C в блоке extern.
#![allow(unused)] fn main() { fn atomic_add(_: *mut u32, _: u32) -> u32 { 2 } static mut LEVELS: u32 = 0; // Это нарушает идею отсутствия разделяемого состояния, и это внутренне не // защищает от состояний гонки, поэтому эта функция `unsafe` unsafe fn bump_levels_unsafe() -> u32 { unsafe { let ret = LEVELS; LEVELS += 1; return ret; } } // В качестве альтернативы `bump_levels_unsafe`, эта функция безопасна, предполагая, // что у нас есть функция atomic_add, которая возвращает старое значение. Эта // функция безопасна только если никакой другой код не обращается к статической переменной неатомарным // способом. Если такие обращения возможны (как в `bump_levels_unsafe`), // то это должно быть `unsafe`, чтобы указать вызывающей стороне, что они // всё ещё должны защищаться от параллельного доступа. fn bump_levels_safe() -> u32 { unsafe { return atomic_add(&raw mut LEVELS, 1); } } }
Изменяемые статические переменные имеют те же ограничения, что и обычные статические переменные, за исключением того, что
тип не должен реализовывать трейт Sync.
Использование статических переменных или констант
Может быть confusing, следует ли использовать элемент constant или элемент static. Константы, как правило, следует предпочитать над статическими переменными, если только одно из следующих не верно:
- Хранятся большие объёмы данных.
- Требуется свойство единственного адреса статических переменных.
- Требуется внутренняя изменяемость.
-
Квалификаторы функций
safeиunsafeсемантически разрешены только внутри блоковextern. ↩
Трейты
Syntax
Trait →
unsafe? trait IDENTIFIER GenericParams? ( : TypeParamBounds? )? WhereClause?
{
InnerAttribute*
AssociatedItem*
}
Трейт описывает абстрактный интерфейс, который могут реализовывать типы. Этот интерфейс состоит из ассоциированных элементов, которые бывают трёх видов:
Объявление трейта определяет трейт в пространстве имён типов модуля или блока, где он находится.
Ассоциированные элементы определяются как члены трейта в соответствующих пространствах имён. Ассоциированные типы определяются в пространстве имён типов. Ассоциированные константы и ассоциированные функции определяются в пространстве имён значений.
Все трейты определяют неявный параметр типа Self, который ссылается на “тип,
который реализует этот интерфейс”. Трейты также могут содержать дополнительные
параметры типа. Эти параметры типа, включая Self, могут быть ограничены
другими трейтами и так далее generics.
Трейты реализуются для конкретных типов через отдельные реализации.
Функции трейта могут опускать тело функции, заменяя его точкой с запятой. Это указывает, что реализация должна определить функцию. Если функция трейта определяет тело, это определение действует как значение по умолчанию для любой реализации, которая его не переопределяет. Аналогично, ассоциированные константы могут опускать знак равенства и выражение, чтобы указать, что реализации должны определить значение константы. Ассоциированные типы никогда не должны определять тип, тип может быть указан только в реализации.
#![allow(unused)] fn main() { // Примеры ассоциированных элементов трейта с определениями и без. trait Example { const CONST_NO_DEFAULT: i32; const CONST_WITH_DEFAULT: i32 = 99; type TypeNoDefault; fn method_without_default(&self); fn method_with_default(&self) {} } }
Функции трейта не могут быть const.
Ограничения трейтов
Универсальные элементы могут использовать трейты как ограничения на свои параметры типа.
Универсальные трейты
Параметры типа могут быть указаны для трейта, чтобы сделать его универсальным. Они появляются после имени трейта, используя тот же синтаксис, что и в универсальных функциях.
#![allow(unused)] fn main() { trait Seq<T> { fn len(&self) -> u32; fn elt_at(&self, n: u32) -> T; fn iter<F>(&self, f: F) where F: Fn(T); } }
Совместимость с dyn
Совместимый с dyn трейт может быть базовым трейтом для объекта трейта. Трейт совместим с dyn, если он обладает следующими качествами:
- Все супертрейты также должны быть совместимы с dyn.
Sizedне должен быть супертрейтом. Другими словами, он не должен требоватьSelf: Sized.
- Он не должен иметь ассоциированных констант.
- Он не должен иметь ассоциированных типов с обобщёнными параметрами.
- Все ассоциированные функции должны либо быть диспетчеризуемыми из объекта трейта, либо быть явно недиспетчеризуемыми:
- Диспетчеризуемые функции должны:
- Не иметь параметров типа (хотя параметры времени жизни разрешены).
- Быть методом, который не использует
Selfкроме как в типе приёмника. - Иметь приёмник одного из следующих типов:
- Не иметь непрозрачный тип возврата; то есть,
- Не быть
async fn(который имеет скрытый типFuture). - Не иметь тип
impl Traitв позиции возврата (fn example(&self) -> impl Trait).
- Не быть
- Не иметь ограничения
where Self: Sized(тип приёмникаSelf(т.е.self) подразумевает это).
- Явно недиспетчеризуемые функции требуют:
- Иметь ограничение
where Self: Sized(тип приёмникаSelf(т.е.self) подразумевает это).
- Иметь ограничение
- Диспетчеризуемые функции должны:
- Трейты
AsyncFn,AsyncFnMutиAsyncFnOnceне совместимы с dyn.
Note
Ранее эта концепция была известна как объектная безопасность.
#![allow(unused)] fn main() { use std::rc::Rc; use std::sync::Arc; use std::pin::Pin; // Примеры методов, совместимых с dyn. trait TraitMethods { fn by_ref(self: &Self) {} fn by_ref_mut(self: &mut Self) {} fn by_box(self: Box<Self>) {} fn by_rc(self: Rc<Self>) {} fn by_arc(self: Arc<Self>) {} fn by_pin(self: Pin<&Self>) {} fn with_lifetime<'a>(self: &'a Self) {} fn nested_pin(self: Pin<Arc<Self>>) {} } struct S; impl TraitMethods for S {} let t: Box<dyn TraitMethods> = Box::new(S); }
#![allow(unused)] fn main() { // Этот трейт совместим с dyn, но эти методы не могут быть диспетчеризованы через объект трейта. trait NonDispatchable { // Не-методы не могут быть диспетчеризованы. fn foo() where Self: Sized {} // Тип Self неизвестен до времени выполнения. fn returns(&self) -> Self where Self: Sized; // `other` может быть другим конкретным типом приёмника. fn param(&self, other: Self) where Self: Sized {} // Обобщённые параметры не совместимы с vtables. fn typed<T>(&self, x: T) where Self: Sized {} } struct S; impl NonDispatchable for S { fn returns(&self) -> Self where Self: Sized { S } } let obj: Box<dyn NonDispatchable> = Box::new(S); obj.returns(); // ОШИБКА: нельзя вызвать с возвратом Self obj.param(S); // ОШИБКА: нельзя вызвать с параметром Self obj.typed(1); // ОШИБКА: нельзя вызвать с обобщённым типом }
#![allow(unused)] fn main() { use std::rc::Rc; // Примеры трейтов, несовместимых с dyn. trait DynIncompatible { const CONST: i32 = 1; // ОШИБКА: не может иметь ассоциированную константу fn foo() {} // ОШИБКА: ассоциированная функция без Sized fn returns(&self) -> Self; // ОШИБКА: Self в типе возврата fn typed<T>(&self, x: T) {} // ОШИБКА: имеет параметры обобщённого типа fn nested(self: Rc<Box<Self>>) {} // ОШИБКА: вложенный приёмник не может быть понижен } struct S; impl DynIncompatible for S { fn returns(&self) -> Self { S } } let obj: Box<dyn DynIncompatible> = Box::new(S); // ОШИБКА }
#![allow(unused)] fn main() { // Трейты с `Self: Sized` несовместимы с dyn. trait TraitWithSize where Self: Sized {} struct S; impl TraitWithSize for S {} let obj: Box<dyn TraitWithSize> = Box::new(S); // ОШИБКА }
#![allow(unused)] fn main() { // Несовместим с dyn, если `Self` является аргументом типа. trait Super<A> {} trait WithSelf: Super<Self> where Self: Sized {} struct S; impl<A> Super<A> for S {} impl WithSelf for S {} let obj: Box<dyn WithSelf> = Box::new(S); // ОШИБКА: нельзя использовать параметр типа `Self` }
Супертрейты
Супертрейты - это трейты, которые должны быть реализованы для типа, чтобы реализовать конкретный трейт. Более того, везде, где generics или объект трейта ограничен трейтом, он имеет доступ к ассоциированным элементам своих супертрейтов.
Супертрейты объявляются через ограничения трейтов на тип Self трейта и
транзитивно через супертрейты трейтов, объявленных в этих ограничениях трейтов.
Ошибкой является случай, когда трейт является своим собственным супертрейтом.
Трейт с супертрейтом называется подтрейтом своего супертрейта.
Следующий пример объявляет Shape супертрейтом Circle.
#![allow(unused)] fn main() { trait Shape { fn area(&self) -> f64; } trait Circle: Shape { fn radius(&self) -> f64; } }
А следующий пример - тот же самый, но с использованием where-предложений.
#![allow(unused)] fn main() { trait Shape { fn area(&self) -> f64; } trait Circle where Self: Shape { fn radius(&self) -> f64; } }
Следующий пример даёт radius реализацию по умолчанию, используя функцию area
из Shape.
#![allow(unused)] fn main() { trait Shape { fn area(&self) -> f64; } trait Circle where Self: Shape { fn radius(&self) -> f64 { // A = pi * r^2 // так что алгебраически, // r = sqrt(A / pi) (self.area() / std::f64::consts::PI).sqrt() } } }
Следующий пример вызывает метод супертрейта на универсальном параметре.
#![allow(unused)] fn main() { trait Shape { fn area(&self) -> f64; } trait Circle: Shape { fn radius(&self) -> f64; } fn print_area_and_radius<C: Circle>(c: C) { // Здесь мы вызываем метод area из супертрейта `Shape` трейта `Circle`. println!("Area: {}", c.area()); println!("Radius: {}", c.radius()); } }
Аналогично, вот пример вызова методов супертрейтов на объектах трейтов.
#![allow(unused)] fn main() { trait Shape { fn area(&self) -> f64; } trait Circle: Shape { fn radius(&self) -> f64; } struct UnitCircle; impl Shape for UnitCircle { fn area(&self) -> f64 { std::f64::consts::PI } } impl Circle for UnitCircle { fn radius(&self) -> f64 { 1.0 } } let circle = UnitCircle; let circle = Box::new(circle) as Box<dyn Circle>; let nonsense = circle.radius() * circle.area(); }
Небезопасные трейты
Элементы трейта, которые начинаются с ключевого слова unsafe, указывают, что реализация
трейта может быть небезопасной. Безопасно использовать корректно реализованный небезопасный трейт.
Реализация трейта также должна начинаться с ключевого слова unsafe.
Sync и Send являются примерами небезопасных трейтов.
Паттерны параметров
Параметры в ассоциированных функциях без тела допускают только паттерны IDENTIFIER или _ универсальный шаблон, а также форму, разрешённую SelfParam. mut IDENTIFIER в настоящее время разрешён, но он устарел и станет жёсткой ошибкой в будущем.
#![allow(unused)] fn main() { trait T { fn f1(&self); fn f2(x: Self, _: i32); } }
#![allow(unused)] fn main() { trait T { fn f2(&x: &i32); // ОШИБКА: паттерны не разрешены в функциях без тел } }
Параметры в ассоциированных функциях с телом допускают только неопровержимые паттерны.
#![allow(unused)] fn main() { trait T { fn f1((a, b): (i32, i32)) {} // OK: паттерн неопровержим } }
#![allow(unused)] fn main() { trait T { fn f1(123: i32) {} // ОШИБКА: паттерн опровержим fn f2(Some(x): Option<i32>) {} // ОШИБКА: паттерн опровержим } }
2018 Edition differences
До редакции 2018 года паттерн для параметра ассоциированной функции был опциональным:
#![allow(unused)] fn main() { // Редакция 2015 trait T { fn f(i32); // OK: идентификаторы параметров не требуются } }Начиная с редакции 2018 года, паттерны больше не являются опциональными.
2018 Edition differences
До редакции 2018 года параметры в ассоциированных функциях с телом ограничивались следующими видами паттернов:
- IDENTIFIER
mutIDENTIFIER_&IDENTIFIER&&IDENTIFIER#![allow(unused)] fn main() { // Редакция 2015 trait T { fn f1((a, b): (i32, i32)) {} // ОШИБКА: паттерн не разрешён } }Начиная с 2018 года, все неопровержимые паттерны разрешены, как описано в items.traits.params.patterns-with-body.
Видимость элементов
Элементы трейта синтаксически допускают аннотацию Видимости, но это
отклоняется при проверке трейта. Это позволяет разбирать элементы с
унифицированным синтаксисом в различных контекстах, где они используются. Например,
пустой фрагмент спецификатора vis макроса может использоваться для элементов трейта, где
правило макроса может использоваться в других ситуациях, где видимость разрешена.
macro_rules! create_method { ($vis:vis $name:ident) => { $vis fn $name(&self) {} }; } trait T1 { // Пустой `vis` разрешён. create_method! { method_of_t1 } } struct S; impl S { // Видимость разрешена здесь. create_method! { pub method_of_s } } impl T1 for S {} fn main() { let s = S; s.method_of_t1(); s.method_of_s(); }
Реализации
Syntax
Implementation → InherentImpl | TraitImpl
InherentImpl →
impl GenericParams? Type WhereClause? {
InnerAttribute*
AssociatedItem*
}
TraitImpl →
unsafe? impl GenericParams? !? TypePath for Type
WhereClause?
{
InnerAttribute*
AssociatedItem*
}
Реализация - это элемент, который связывает элементы с реализующим типом.
Реализации определяются с ключевым словом impl и содержат функции,
которые принадлежат экземпляру типа, который реализуется, или типу
статически.
Существует два типа реализаций:
- собственные реализации (inherent implementations)
- реализации трейтов
Собственные реализации
Собственная реализация определяется как последовательность ключевого слова impl,
объявлений обобщённых типов, пути к именованному типу, where-предложения и
набора ассоциированных элементов в фигурных скобках.
Именованный тип называется реализующим типом, а ассоциированные элементы являются ассоциированными элементами реализующего типа.
Собственные реализации связывают содержащиеся элементы с реализующим типом.
Собственные реализации могут содержать ассоциированные функции (включая методы) и ассоциированные константы.
Они не могут содержать ассоциированные псевдонимы типов.
Путь к ассоциированному элементу - это любой путь к реализующему типу, за которым следует идентификатор ассоциированного элемента как конечный компонент пути.
Тип также может иметь несколько собственных реализаций. Реализующий тип должен быть определён в том же крейте, что и исходное определение типа.
pub mod color { pub struct Color(pub u8, pub u8, pub u8); impl Color { pub const WHITE: Color = Color(255, 255, 255); } } mod values { use super::color::Color; impl Color { pub fn red() -> Color { Color(255, 0, 0) } } } pub use self::color::Color; fn main() { // Фактический путь к реализующему типу и реализации в том же модуле. color::Color::WHITE; // Блоки реализации в разных модулях всё ещё доступны через путь к типу. color::Color::red(); // Переэкспортированные пути к реализующему типу также работают. Color::red(); // Не работает, потому что use в `values` не является pub. // values::Color::red(); }
Реализации трейтов
Реализация трейта определяется так же, как собственная реализация, за исключением того, что
опциональные объявления обобщённых типов следуют после трейта, затем
следует ключевое слово for, затем путь к именованному типу.
Трейт известен как реализуемый трейт. Реализующий тип реализует реализуемый трейт.
Реализация трейта должна определить все не-по-умолчанию ассоциированные элементы, объявленные реализуемым трейтом, может переопределить ассоциированные элементы по умолчанию, определённые реализуемым трейтом, и не может определять никакие другие элементы.
Путь к ассоциированным элементам - это <, затем путь к реализующему
типу, затем as, затем путь к трейту, затем > как компонент пути,
за которым следует компонент пути ассоциированного элемента.
Небезопасные трейты требуют, чтобы реализация трейта начиналась с ключевого слова unsafe.
#![allow(unused)] fn main() { #[derive(Copy, Clone)] struct Point {x: f64, y: f64}; type Surface = i32; struct BoundingBox {x: f64, y: f64, width: f64, height: f64}; trait Shape { fn draw(&self, s: Surface); fn bounding_box(&self) -> BoundingBox; } fn do_draw_circle(s: Surface, c: Circle) { } struct Circle { radius: f64, center: Point, } impl Copy for Circle {} impl Clone for Circle { fn clone(&self) -> Circle { *self } } impl Shape for Circle { fn draw(&self, s: Surface) { do_draw_circle(s, *self); } fn bounding_box(&self) -> BoundingBox { let r = self.radius; BoundingBox { x: self.center.x - r, y: self.center.y - r, width: 2.0 * r, height: 2.0 * r, } } } }
Когерентность реализации трейта
Реализация трейта считается некогерентной, если либо проверка правил сиротности не проходит, либо есть перекрывающиеся экземпляры реализации.
Две реализации трейта перекрываются, когда есть непустое пересечение трейтов, для которых реализация предназначена, и реализации могут быть инстанцированы одним и тем же типом.
Правила сиротности
Правило сиротности гласит, что реализация трейта разрешена только если либо трейт, либо хотя бы один из типов в реализации определён в текущем крейте. Оно предотвращает конфликтующие реализации трейтов в разных крейтах и является ключевым для обеспечения когерентности.
Сиротская реализация - это реализация, которая реализует иностранный трейт для иностранного типа. Если бы они были свободно разрешены, два крейта могли бы реализовать один и тот же трейт для одного и того же типа несовместимыми способами, создавая ситуацию, когда добавление или обновление зависимости могло бы сломать компиляцию из-за конфликтующих реализаций.
Правило сиротности позволяет авторам библиотек добавлять новые реализации к своим трейтам без страха, что они сломают нижестоящий код. Без этих ограничений библиотека не могла бы добавить реализацию типа impl<T: Display> MyTrait for T без потенциального конфликта с нижестоящими реализациями.
Для impl<P1..=Pn> Trait<T1..=Tn> for T0, impl действителен только если
хотя бы одно из следующего верно:
Traitявляется локальным трейтом- Все из
- Хотя бы один из типов
T0..=Tnдолжен быть локальным типом. ПустьTiбудет первым таким типом. - Никакие непокрытые параметры типа
P1..=Pnне могут появляться вT0..Ti(исключаяTi)
- Хотя бы один из типов
Только появление непокрытых параметров типа ограничено.
Заметим, что для целей когерентности фундаментальные типы являются
особыми. T в Box<T> не считается покрытым, и Box<LocalType>
считается локальным.
Обобщённые реализации
Реализация может принимать обобщённые параметры, которые могут использоваться в остальной
части реализации. Параметры реализации записываются непосредственно после
ключевого слова impl.
#![allow(unused)] fn main() { trait Seq<T> { fn dummy(&self, _: T) { } } impl<T> Seq<T> for Vec<T> { /* ... */ } impl Seq<bool> for u32 { /* Рассматриваем целое число как последовательность битов */ } }
Обобщённые параметры ограничивают реализацию, если параметр появляется хотя бы один раз в одном из:
- Реализуемом трейте, если он есть
- Реализующем типе
- Как ассоциированный тип в ограничениях типа, который содержит другой параметр, который ограничивает реализацию
Параметры типа и констант должны всегда ограничивать реализацию. Времена жизни должны ограничивать реализацию, если время жизни используется в ассоциированном типе.
Примеры ограничивающих ситуаций:
#![allow(unused)] fn main() { trait Trait{} trait GenericTrait<T> {} trait HasAssocType { type Ty; } struct Struct; struct GenericStruct<T>(T); struct ConstGenericStruct<const N: usize>([(); N]); // T ограничивает, будучи аргументом GenericTrait. impl<T> GenericTrait<T> for i32 { /* ... */ } // T ограничивает, будучи аргументом GenericStruct impl<T> Trait for GenericStruct<T> { /* ... */ } // Аналогично, N ограничивает, будучи аргументом ConstGenericStruct impl<const N: usize> Trait for ConstGenericStruct<N> { /* ... */ } // T ограничивает, будучи в ассоциированном типе в ограничении для типа `U`, который // сам является обобщённым параметром, ограничивающим трейт. impl<T, U> GenericTrait<U> for u32 where U: HasAssocType<Ty = T> { /* ... */ } // Как предыдущий, кроме того, что тип - `(U, isize)`. `U` появляется внутри типа, // который включает `T`, и не является самим типом. impl<T, U> GenericStruct<U> where (U, isize): HasAssocType<Ty = T> { /* ... */ } }
Примеры неограничивающих ситуаций:
#![allow(unused)] fn main() { // Остальные из этих являются ошибками, поскольку они имеют параметры типа или констант, которые // не ограничивают. // T не ограничивает, поскольку не появляется вообще. impl<T> Struct { /* ... */ } // N не ограничивает по той же причине. impl<const N: usize> Struct { /* ... */ } // Использование T внутри реализации не ограничивает impl. impl<T> Struct { fn uses_t(t: &T) { /* ... */ } } // T используется как ассоциированный тип в ограничениях для U, но U не ограничивает. impl<T, U> Struct where U: HasAssocType<Ty = T> { /* ... */ } // T используется в ограничениях, но не как ассоциированный тип, поэтому не ограничивает. impl<T, U> GenericTrait<U> for u32 where U: GenericTrait<T> {} }
Пример разрешённого неограничивающего параметра времени жизни:
#![allow(unused)] fn main() { struct Struct; impl<'a> Struct {} }
Пример запрещённого неограничивающего параметра времени жизни:
#![allow(unused)] fn main() { struct Struct; trait HasAssocType { type Ty; } impl<'a> HasAssocType for Struct { type Ty = &'a Struct; } }
Атрибуты на реализациях
Реализации могут содержать внешние атрибуты перед ключевым словом impl и
внутренние атрибуты внутри скобок, которые содержат ассоциированные элементы. Внутренние
атрибуты должны идти перед любыми ассоциированными элементами. Атрибуты, которые имеют
значение здесь, это cfg, deprecated, doc и атрибуты проверки линтов.
Внешние блоки
Syntax
ExternBlock →
unsafe?1 extern Abi? {
InnerAttribute*
ExternalItem*
}
ExternalItem →
OuterAttribute* (
MacroInvocationSemi
| Visibility? StaticItem
| Visibility? Function
)
Внешние блоки предоставляют объявления элементов, которые не определены в текущем крейте и являются основой внешнего функционального интерфейса Rust. Они аналогичны непроверенным импортам.
Два вида объявлений элементов разрешены во внешних блоках: функции и статические переменные.
Вызов небезопасных функций или доступ к небезопасным статическим переменным, объявленным во внешних блоках, разрешён только в небезопасном контексте.
Внешний блок определяет свои функции и статические переменные в пространстве имён значений модуля или блока, где он находится.
Ключевое слово unsafe семантически требуется появляться перед ключевым словом extern во внешних блоках.
2024 Edition differences
До редакции 2024 ключевое слово
unsafeбыло опциональным. Квалификаторы элементовsafeиunsafeразрешены только если сам внешний блок помечен какunsafe.
Функции
Функции внутри внешних блоков объявляются так же, как и другие Rust функции, за исключением того, что они не должны иметь тела и вместо этого завершаются точкой с запятой.
Образцы не разрешены в параметрах, могут использоваться только IDENTIFIER или _.
Квалификаторы функций safe и unsafe
разрешены, но другие квалификаторы функций (например, const, async, extern)
не разрешены.
Функции внутри внешних блоков могут вызываться кодом Rust, так же как функции, определённые в Rust. Компилятор Rust автоматически преобразует между ABI Rust и иностранным ABI.
Функция, объявленная во внешнем блоке, неявно является unsafe, если не присутствует
квалификатор функции safe.
При приведении к указателю на функцию, функция, объявленная во внешнем блоке, имеет
тип extern "abi" for<'l1, ..., 'lm> fn(A1, ..., An) -> R, где 'l1,
… 'lm - это её параметры времени жизни, A1, …, An - объявленные типы
её параметров, R - объявленный тип возвращаемого значения.
Статические переменные
Статические переменные внутри внешних блоков объявляются так же, как статические переменные вне внешних блоков, за исключением того, что у них нет выражения, инициализирующего их значение.
Если статический элемент, объявленный во внешнем блоке, не квалифицирован как safe, то доступ к этому элементу является unsafe, независимо от того,
является ли он изменяемым, потому что нет ничего, гарантирующего, что битовый шаблон в памяти статической переменной
действителен для типа, с которым она объявлена, поскольку некоторый произвольный (например, C) код отвечает
за инициализацию статической переменной.
Внешние статические переменные могут быть как неизменяемыми, так и изменяемыми, так же как статические переменные вне внешних блоков.
Неизменяемая статическая переменная должна быть инициализирована до выполнения любого кода Rust. Недостаточно, чтобы
статическая переменная была инициализирована до того, как код Rust прочитает её.
Как только код Rust запускается, изменение неизменяемой статической переменной (изнутри или снаружи Rust) является неопределённым поведением,
за исключением случаев, когда изменение происходит с байтами внутри UnsafeCell.
ABI
Ключевое слово extern может сопровождаться опциональной строкой ABI. ABI определяет соглашение о вызовах функций в блоке. Соглашение о вызовах определяет низкоуровневый интерфейс для функций, такой как размещение аргументов в регистрах или стеке, передача возвращаемых значений и ответственность за очистку стека.
Example
#![allow(unused)] fn main() { // Интерфейс к Windows API. unsafe extern "system" { /* ... */ } }
Если строка ABI не указана, по умолчанию используется "C".
Note
Синтаксис
externбез явного ABI постепенно устаревает, поэтому лучше всегда явно указывать ABI.Для получения более подробной информации см. Rust issue #134986.
Следующие строки ABI поддерживаются на всех платформах:
unsafe extern "Rust"— Нативное соглашение о вызовах для функций и замыканий Rust. Это значение по умолчанию, когда функция объявлена без использованияextern fn. ABI Rust не предлагает гарантий стабильности.
unsafe extern "C"— ABI “C” соответствует ABI по умолчанию, выбранному доминирующим компилятором C для цели.
-
unsafe extern "system"— Это эквивалентноextern "C", кроме Windows x86_32, где это эквивалентно"stdcall".Note
Поскольку правильный базовый ABI на Windows зависит от цели, лучше использовать
extern "system"при попытке связать функции Windows API, которые не используют явно определённый ABI.
extern "C-unwind"иextern "system-unwind"— Идентичны"C"и"system"соответственно, но с разным поведением, когда вызываемая функция раскручивает стек (паникуя или выбрасывая исключение в стиле C++).
Также есть некоторые специфичные для платформы строки ABI:
-
unsafe extern "cdecl"— Соглашение о вызовах, обычно используемое с кодом C на x86_32.- Доступно только для целей x86_32.
- Соответствует
__cdeclв MSVC и__attribute__((cdecl))в GCC и clang.
Note
Для подробностей см.:
-
unsafe extern "stdcall"— Соглашение о вызовах, обычно используемое Win32 API на x86_32.- Доступно только для целей x86_32.
- Соответствует
__stdcallв MSVC и__attribute__((stdcall))в GCC и clang.
Note
Для подробностей см.:
-
unsafe extern "win64"— ABI Windows x64.- Доступно только для целей x86_64.
- “win64” такой же, как ABI “C” на целях Windows x86_64.
- Соответствует
__attribute__((ms_abi))в GCC и clang.
-
unsafe extern "sysv64"— ABI System V.- Доступно только для целей x86_64.
- “sysv64” такой же, как ABI “C” на целях не-Windows x86_64.
- Соответствует
__attribute__((sysv_abi))в GCC и clang.
Note
Для подробностей см.:
-
unsafe extern "aapcs"— ABI с программной эмуляцией чисел с плавающей точкой для ARM.- Доступно только для целей ARM32.
- “aapcs” такой же, как ABI “C” на ARM32 с программной эмуляцией чисел с плавающей точкой.
- Соответствует
__attribute__((pcs("aapcs")))в clang.
Note
Для подробностей см.:
-
unsafe extern "fastcall"— “Быстрая” вариант stdcall, который передаёт некоторые аргументы в регистрах.- Доступно только для целей x86_32.
- Соответствует
__fastcallв MSVC и__attribute__((fastcall))в GCC и clang.
-
unsafe extern "thiscall"— Соглашение о вызовах, обычно используемое для функций-членов классов C++ на x86_32 MSVC.- Доступно только для целей x86_32.
- Соответствует
__thiscallв MSVC и__attribute__((thiscall))в GCC и clang.
Note
Для подробностей см.:
unsafe extern "efiapi"— ABI, используемый для функций UEFI.- Доступно только для целей x86 и ARM (32-битные и 64-битные).
Как "C" и "system", большинство специфичных для платформы строк ABI также имеют соответствующий вариант -unwind; конкретно, это:
"aapcs-unwind""cdecl-unwind""fastcall-unwind""stdcall-unwind""sysv64-unwind""thiscall-unwind""win64-unwind"
Вариадические функции
Функции внутри внешних блоков могут быть вариадическими, указывая ... как
последний аргумент. Вариадический параметр может опционально быть указан с
идентификатором.
#![allow(unused)] fn main() { unsafe extern "C" { unsafe fn foo(...); unsafe fn bar(x: i32, ...); unsafe fn with_name(format: *const u8, args: ...); // БЕЗОПАСНОСТЬ: Эта функция гарантирует, что не будет обращаться к // вариадическим аргументам. safe fn ignores_variadic_arguments(x: i32, ...); } }
Warning
Квалификатор
safeне должен использоваться для функции во внешнем блоке, если эта функция не гарантирует, что она вообще не будет обращаться к вариадическим аргументам. Передача неожиданного количества аргументов или аргументов неожиданного типа в вариадическую функцию может привести к неопределённому поведению.
Вариадические параметры могут быть указаны только внутри внешних блоков со следующими строками ABI или их соответствующими вариантами -unwind:
"aapcs""C""cdecl""efiapi""sysv64""win64"
Атрибуты на внешних блоках
Следующие атрибуты управляют поведением внешних блоков.
Атрибут link
Атрибут link указывает имя нативной библиотеки, с которой
компилятор должен линковаться для элементов внутри блока extern.
Он использует синтаксис MetaListNameValueStr для указания своих входных данных. Ключ name - это
имя нативной библиотеки для линковки. Ключ kind - это опциональное значение, которое
указывает тип библиотеки со следующими возможными значениями:
dylib— Указывает динамическую библиотеку. Это значение по умолчанию, еслиkindне указан.
static— Указывает статическую библиотеку.
framework— Указывает фреймворк macOS. Это действительно только для целей macOS.
raw-dylib— Указывает динамическую библиотеку, для которой компилятор сгенерирует библиотеку импорта для линковки (см.dylibversusraw-dylibниже для подробностей). Это действительно только для целей Windows.
Ключ name должен быть включён, если указан kind.
Опциональный аргумент modifiers - это способ указания модификаторов линковки для
библиотеки для линковки.
Модификаторы указываются как строка, разделённая запятыми, с каждым модификатором, предваренным
либо +, либо -, чтобы указать, что модификатор включён или выключен,
соответственно.
Указание нескольких аргументов modifiers в одном атрибуте link,
или нескольких идентичных модификаторов в том же аргументе modifiers в настоящее время не поддерживается.
Пример: #[link(name = "mylib", kind = "static", modifiers = "+whole-archive")].
Ключ wasm_import_module может использоваться для указания имени модуля WebAssembly
для элементов внутри блока extern при импорте символов из
хост-окружения. Имя модуля по умолчанию - env, если wasm_import_module
не указан.
#[link(name = "crypto")]
unsafe extern {
// …
}
#[link(name = "CoreFoundation", kind = "framework")]
unsafe extern {
// …
}
#[link(wasm_import_module = "foo")]
unsafe extern {
// …
}
Допустимо добавлять атрибут link на пустой внешний блок. Вы можете использовать
это для удовлетворения требований линковки внешних блоков в другом месте вашего
кода (включая вышестоящие крейты) вместо добавления атрибута к каждому внешнему
блоку.
Модификаторы линковки: bundle
Этот модификатор совместим только с типом линковки static.
Использование любого другого типа приведёт к ошибке компилятора.
При сборке rlib или staticlib +bundle означает, что нативная статическая библиотека
будет упакована в архив rlib или staticlib, а затем извлечена оттуда
во время линковки финального бинарника.
При сборке rlib -bundle означает, что нативная статическая библиотека регистрируется как зависимость
этого rlib “по имени”, и объектные файлы из неё включаются только во время линковки финального
бинарника, поиск файла по этому имени также выполняется во время финальной линковки.
При сборке staticlib -bundle означает, что нативная статическая библиотека просто не включается
в архив, и некоторая система сборки более высокого уровня должна будет добавить её позже во время линковки
финального бинарника.
Этот модификатор не имеет эффекта при сборке других целей, таких как исполняемые файлы или динамические библиотеки.
Значение по умолчанию для этого модификатора - +bundle.
Более подробная информация о реализации этого модификатора может быть найдена в
документации bundle для rustc.
Модификаторы линковки: whole-archive
Этот модификатор совместим только с типом линковки static.
Использование любого другого типа приведёт к ошибке компилятора.
+whole-archive означает, что статическая библиотека линкуется как целый архив
без выбрасывания каких-либо объектных файлов.
Значение по умолчанию для этого модификатора - -whole-archive.
Более подробная информация о реализации этого модификатора может быть найдена в
документации whole-archive для rustc.
Модификаторы линковки: verbatim
Этот модификатор совместим со всеми типами линковки.
+verbatim означает, что сам rustc не будет добавлять какие-либо целевые префиксы или суффиксы библиотек
(как lib или .a) к имени библиотеки и постарается запросить то же самое от
линковщика.
-verbatim означает, что rustc либо добавит целеспецифичный префикс и суффикс к имени
библиотеки перед передачей его линковщику, либо не предотвратит неявное добавление его линковщиком.
Значение по умолчанию для этого модификатора - -verbatim.
Более подробная информация о реализации этого модификатора может быть найдена в
документации verbatim для rustc.
dylib versus raw-dylib
На Windows линковка против динамической библиотеки требует, чтобы библиотека импорта была предоставлена линковщику: это специальная статическая библиотека, которая объявляет все символы, экспортируемые динамической библиотекой, таким образом, что линковщик знает, что они должны быть динамически загружены во время выполнения.
Указание kind = "dylib" инструктирует компилятор Rust линковать библиотеку
импорта на основе ключа name. Линковщик затем использует свою обычную логику
разрешения библиотек, чтобы найти эту библиотеку импорта. Альтернативно, указание
kind = "raw-dylib" инструктирует компилятор сгенерировать библиотеку импорта
во время компиляции и предоставить её линковщику вместо этого.
raw-dylib поддерживается только на Windows. Использование его при нацеливании на другие
платформы приведёт к ошибке компилятора.
Ключ import_name_type
На x86 Windows имена функций “декорируются” (т.е., к ним добавляется определённый префикс
и/или суффикс), чтобы указать их соглашение о вызовах. Например,
функция с соглашением о вызовах stdcall с именем fn1, которая не имеет аргументов,
будет декорирована как _fn1@0. Однако PE Format также разрешает именам
не иметь префикса или быть недекорированными. Дополнительно, инструментарии MSVC и GNU
используют разные декорации для одних и тех же соглашений о вызовах, что означает,
по умолчанию некоторые функции Win32 не могут быть вызваны с использованием типа линковки raw-dylib
через инструментарий GNU.
Чтобы разрешить эти различия, при использовании типа линковки raw-dylib вы можете
также указать ключ import_name_type с одним из следующих значений, чтобы
изменить то, как функции именуются в сгенерированной библиотеке импорта:
decorated: Имя функции будет полностью декорировано с использованием формата инструментария MSVC.noprefix: Имя функции будет декорировано с использованием формата инструментария MSVC, но пропуская ведущий?,@или опционально_.undecorated: Имя функции не будет декорировано.
Если ключ import_name_type не указан, то имя функции будет
полностью декорировано с использованием формата целевого инструментария.
Переменные никогда не декорируются, поэтому ключ import_name_type не влияет на
то, как они именуются в сгенерированной библиотеке импорта.
Ключ import_name_type поддерживается только на x86 Windows. Использование его при
нацеливании на другие платформы приведёт к ошибке компилятора.
Атрибут link_name
Атрибут link_name атрибут может быть применён к объявлениям внутри блока extern для указания символа для импорта для данной функции или статической переменной.
Example
#![allow(unused)] fn main() { unsafe extern "C" { #[link_name = "actual_symbol_name"] safe fn name_in_rust(); } }
Атрибут link_name использует синтаксис MetaNameValueStr.
Атрибут link_name может быть применён только к функции или статическому элементу во внешнем блоке.
Note
rustcигнорирует использование в других позициях, но выдает предупреждение. Это может стать ошибкой в будущем.
Только последнее использование link_name на элементе имеет эффект.
Note
rustcвыдает предупреждение на любое использование, предшествующее последнему. Это может стать ошибкой в будущем.
Атрибут link_name не может использоваться с атрибутом link_ordinal.
Атрибут link_ordinal
Атрибут link_ordinal может быть применён к объявлениям внутри внешнего
блока для указания числового ординала, который следует использовать при генерации библиотеки импорта
для линковки. Ординал - это уникальное число для каждого символа, экспортируемого динамической
библиотекой на Windows, и может использоваться при загрузке библиотеки для нахождения
этого символа вместо необходимости искать его по имени.
Warning
link_ordinalдолжен использоваться только в случаях, когда ординал символа известен как стабильный: если ординал символа не установлен явно при сборке содержащего его бинарника, то ему будет автоматически назначен ординал, и этот назначенный ординал может изменяться между сборками бинарника.
#![allow(unused)] fn main() { #[cfg(all(windows, target_arch = "x86"))] #[link(name = "exporter", kind = "raw-dylib")] unsafe extern "stdcall" { #[link_ordinal(15)] safe fn imported_function_stdcall(i: i32); } }
Этот атрибут используется только с типом линковки raw-dylib.
Использование любого другого типа приведёт к ошибке компилятора.
Использование этого атрибута с атрибутом link_name приведёт к
ошибке компилятора.
Атрибуты на параметрах функций
Атрибуты на параметрах внешних функций следуют тем же правилам и ограничениям, что и обычные параметры функций.
-
Начиная с редакции 2024, ключевое слово
unsafeтребуется семантически. ↩
Обобщённые параметры
Syntax
GenericParams → < ( GenericParam ( , GenericParam )* ,? )? >
GenericParam → OuterAttribute* ( LifetimeParam | TypeParam | ConstParam )
LifetimeParam → Lifetime ( : LifetimeBounds )?
TypeParam → IDENTIFIER ( : TypeParamBounds? )? ( = Type )?
ConstParam →
const IDENTIFIER : Type
( = ( BlockExpression | IDENTIFIER | -? LiteralExpression ) )?
Функции, псевдонимы типов, структуры, перечисления, объединения, трейты и
реализации могут быть параметризованы типами, константами и временами жизни. Эти
параметры перечисляются в угловых скобках (<...>),
обычно сразу после имени элемента и перед его определением. Для
реализаций, которые не имеют имени, они идут непосредственно после impl.
Порядок обобщённых параметров ограничен: сначала параметры времени жизни, затем параметры типов и констант вперемешку.
Одно и то же имя параметра не может быть объявлено более одного раза в списке GenericParams.
Некоторые примеры элементов с параметрами типов, констант и времён жизни:
#![allow(unused)] fn main() { fn foo<'a, T>() {} trait A<U> {} struct Ref<'a, T> where T: 'a { r: &'a T } struct InnerArray<T, const N: usize>([T; N]); struct EitherOrderWorks<const N: bool, U>(U); }
Обобщённые параметры находятся в области видимости внутри определения элемента, где они объявлены. Они не находятся в области видимости для элементов, объявленных внутри тела функции, как описано в объявлениях элементов. См. области видимости обобщённых параметров для получения более подробной информации.
Ссылки, сырые указатели, массивы, срезы, кортежи и указатели на функции также имеют параметры времени жизни или типов, но не ссылаются на них с помощью синтаксиса путей.
'_ и 'static не являются допустимыми именами параметров времени жизни.
Константные обобщения
Параметры константных обобщений позволяют элементам быть обобщёнными по значениям констант.
Идентификатор константы вводит имя в пространстве имён значений для параметра константы, и все экземпляры элемента должны быть созданы со значением заданного типа.
Единственные допустимые типы для параметров констант: u8, u16, u32, u64, u128, usize,
i8, i16, i32, i64, i128, isize, char и bool.
Параметры констант могут использоваться везде, где может использоваться константный элемент, за исключением того, что при использовании в типе или выражении повторения массива они должны быть автономными (как описано ниже). То есть они разрешены в следующих местах:
- Как применённая константа к любому типу, который является частью сигнатуры рассматриваемого элемента.
- Как часть константного выражения, используемого для определения ассоциированной константы, или как параметр для ассоциированного типа.
- Как значение в любом выражении времени выполнения в теле любых функций в элементе.
- Как параметр для любого типа, используемого в теле любых функций в элементе.
- Как часть типа любых полей в элементе.
#![allow(unused)] fn main() { // Примеры, где могут использоваться параметры константных обобщений. // Используется в сигнатуре самого элемента. fn foo<const N: usize>(arr: [i32; N]) { // Используется как тип внутри тела функции. let x: [i32; N]; // Используется как выражение. println!("{}", N * 2); } // Используется как поле структуры. struct Foo<const N: usize>([i32; N]); impl<const N: usize> Foo<N> { // Используется как ассоциированная константа. const CONST: usize = N * 4; } trait Trait { type Output; } impl<const N: usize> Trait for Foo<N> { // Используется как ассоциированный тип. type Output = [i32; N]; } }
#![allow(unused)] fn main() { // Примеры, где параметры константных обобщений не могут использоваться. fn foo<const N: usize>() { // Нельзя использовать в определениях элементов внутри тела функции. const BAD_CONST: [usize; N] = [1; N]; static BAD_STATIC: [usize; N] = [1; N]; fn inner(bad_arg: [usize; N]) { let bad_value = N * 2; } type BadAlias = [usize; N]; struct BadStruct([usize; N]); } }
В качестве дополнительного ограничения, параметры констант могут появляться только как автономные
аргументы внутри типа или выражения повторения массива. В этих контекстах
они могут использоваться только как выражение пути с одним сегментом, возможно внутри
блока (такого как N или {N}). То есть они не могут комбинироваться с другими
выражениями.
#![allow(unused)] fn main() { // Примеры, где параметры констант не могут использоваться. // Не разрешено комбинировать в других выражениях в типах, таких как // арифметическое выражение в типе возврата здесь. fn bad_function<const N: usize>() -> [u8; {N + 1}] { // Аналогично не разрешено для выражений повторения массива. [1; {N + 1}] } }
Аргумент константы в пути указывает значение константы для использования в этом элементе.
Аргумент должен быть либо выведенной константой, либо константным выражением типа, приписанного к параметру константы. Константное выражение должно быть блочным выражением (окружённым фигурными скобками), если только это не одиночный сегмент пути (IDENTIFIER) или литерал (с возможным ведущим токеном -).
Note
Это синтаксическое ограничение необходимо, чтобы избежать необходимости бесконечного просмотра вперёд при разборе выражения внутри типа.
#![allow(unused)] fn main() { struct S<const N: i64>; const C: i64 = 1; fn f<const N: i64>() -> S<N> { S } let _ = f::<1>(); // Литерал. let _ = f::<-1>(); // Отрицательный литерал. let _ = f::<{ 1 + 2 }>(); // Константное выражение. let _ = f::<C>(); // Одиночный сегмент пути. let _ = f::<{ C + 1 }>(); // Константное выражение. let _: S<1> = f::<_>(); // Выведенная константа. let _: S<1> = f::<(((_)))>(); // Выведенная константа. }
Note
В списке обобщённых аргументов выведенная константа разбирается как выведенный тип, но затем семантически обрабатывается как отдельный вид аргумента константного обобщения.
Там, где ожидается аргумент константы, может использоваться _ (опционально окружённый любым количеством соответствующих скобок), называемый выведенной константой (правила путей, правила выражений массива), вместо этого. Это просит компилятор вывести аргумент константы, если это возможно, на основе окружающей информации.
#![allow(unused)] fn main() { fn make_buf<const N: usize>() -> [u8; N] { [0; _] // ^ Выводит `N`. } let _: [u8; 1024] = make_buf::<_>(); // ^ Выводит `1024`. }
Note
Выведенная константа семантически не является выражением и поэтому не принимается внутри фигурных скобок.
#![allow(unused)] fn main() { fn f<const N: usize>() -> [u8; N] { [0; _] } let _: [_; 1] = f::<{ _ }>(); // ^ ОШИБКА: `_` не разрешён здесь }
Выведенная константа не может использоваться в сигнатурах элементов.
#![allow(unused)] fn main() { fn f<const N: usize>(x: [u8; N]) -> [u8; _] { x } // ^ ОШИБКА: не разрешено }
Когда есть неоднозначность, может ли обобщённый аргумент быть разрешён как аргумент типа или константы, он всегда разрешается как тип. Размещение аргумента в блочном выражении может заставить его интерпретироваться как аргумент константы.
#![allow(unused)] fn main() { type N = u32; struct Foo<const N: usize>; // Следующее является ошибкой, потому что `N` интерпретируется как псевдоним типа `N`. fn foo<const N: usize>() -> Foo<N> { todo!() } // ОШИБКА // Можно исправить, обернув в фигурные скобки, чтобы заставить интерпретировать как параметр // константы `N`: fn bar<const N: usize>() -> Foo<{ N }> { todo!() } // ok }
В отличие от параметров типов и времён жизни, параметры констант могут быть объявлены без использования внутри параметризованного элемента, за исключением реализаций, как описано в обобщённых реализациях:
#![allow(unused)] fn main() { // ok struct Foo<const N: usize>; enum Bar<const M: usize> { A, B } // ОШИБКА: неиспользуемый параметр struct Baz<T>; struct Biz<'a>; struct Unconstrained; impl<const N: usize> Unconstrained {} }
При разрешении обязательства ограничения трейта, исчерпываемость всех
реализаций параметров констант не учитывается при определении, удовлетворено ли
ограничение. Например, в следующем, даже though все возможные
значения констант для типа bool реализованы, это всё равно ошибка, что
ограничение трейта не удовлетворено:
#![allow(unused)] fn main() { struct Foo<const B: bool>; trait Bar {} impl Bar for Foo<true> {} impl Bar for Foo<false> {} fn needs_bar(_: impl Bar) {} fn generic<const B: bool>() { let v = Foo::<B>; needs_bar(v); // ОШИБКА: ограничение трейта `Foo<B>: Bar` не удовлетворено } }
Where-предложения
Syntax
WhereClause → where ( WhereClauseItem , )* WhereClauseItem?
WhereClauseItem →
LifetimeWhereClauseItem
| TypeBoundWhereClauseItem
LifetimeWhereClauseItem → Lifetime : LifetimeBounds
TypeBoundWhereClauseItem → ForLifetimes? Type : TypeParamBounds?
Where-предложения предоставляют другой способ указания ограничений на параметры типов и времён жизни, а также способ указания ограничений на типы, которые не являются параметрами типов.
Ключевое слово for может использоваться для введения ранжированных времён жизни. Оно
разрешает только параметры LifetimeParam.
#![allow(unused)] fn main() { struct A<T> where T: Iterator, // Можно использовать A<T: Iterator> вместо этого T::Item: Copy, // Ограничение на ассоциированный тип String: PartialEq<T>, // Ограничение на `String`, используя параметр типа i32: Default, // Разрешено, но бесполезно { f: T, } }
Атрибуты
Параметры обобщённых времён жизни и типов допускают атрибуты на них. Нет встроенных атрибутов, которые что-либо делают в этой позиции, хотя пользовательские атрибуты производных могут придавать этому значение.
Этот пример показывает использование пользовательского атрибута производного для изменения значения обобщённого параметра.
// Предположим, что derive для MyFlexibleClone объявил `my_flexible_clone` как
// атрибут, который он понимает.
#[derive(MyFlexibleClone)]
struct Foo<#[my_flexible_clone(unbounded)] H> {
a: *const H
}
Ассоциированные элементы
Syntax
AssociatedItem →
OuterAttribute* (
MacroInvocationSemi
| ( Visibility? ( TypeAlias | ConstantItem | Function ) )
)
Ассоциированные элементы - это элементы, объявленные в трейтах или определённые в реализациях. Они называются так, потому что определяются на ассоциированном типе — типе в реализации.
Они являются подмножеством видов элементов, которые можно объявить в модуле. Конкретно, существуют ассоциированные функции (включая методы), ассоциированные типы и ассоциированные константы.
Ассоциированные элементы полезны, когда ассоциированный элемент логически связан с
ассоциирующим элементом. Например, метод is_some на Option внутренне
связан с Option, поэтому должен быть ассоциированным.
Каждый вид ассоциированного элемента бывает двух разновидностей: определения, которые содержат фактическую реализацию, и объявления, которые объявляют сигнатуры для определений.
Именно объявления составляют контракт трейтов и то, что доступно на обобщённых типах.
Ассоциированные функции и методы
Ассоциированные функции - это функции, ассоциированные с типом.
Объявление ассоциированной функции объявляет сигнатуру для определения
ассоциированной функции. Оно записывается как элемент функции, за исключением того, что
тело функции заменяется на ;.
Идентификатор - это имя функции.
Обобщения, список параметров, тип возвращаемого значения и where-предложение ассоциированной функции должны быть такими же, как в объявлении ассоциированной функции.
Определение ассоциированной функции определяет функцию, ассоциированную с другим типом. Оно записывается так же, как элемент функции.
Note
Распространённый пример - ассоциированная функция с именем
new, которая возвращает значение типа, с которым она ассоциирована.
struct Struct { field: i32 } impl Struct { fn new() -> Struct { Struct { field: 0i32 } } } fn main () { let _struct = Struct::new(); }
Когда ассоциированная функция объявлена в трейте, функция также может быть
вызвана с путём, который является путём к трейту, дополненным именем
трейта. Когда это происходит, она заменяется на <_ as Trait>::function_name.
#![allow(unused)] fn main() { trait Num { fn from_i32(n: i32) -> Self; } impl Num for f64 { fn from_i32(n: i32) -> f64 { n as f64 } } // Эти 4 варианта эквивалентны в данном случае. let _: f64 = Num::from_i32(42); let _: f64 = <_ as Num>::from_i32(42); let _: f64 = <f64 as Num>::from_i32(42); let _: f64 = f64::from_i32(42); }
Методы
Ассоциированные функции, первый параметр которых назван self, называются методами
и могут быть вызваны с использованием оператора вызова метода, например, x.foo(), а
также обычной нотации вызова функции.
Если тип параметра self указан, он ограничен типами, разрешающимися
в один из сгенерированных следующей грамматикой (где 'lt обозначает некоторое произвольное
время жизни):
P = &'lt S | &'lt mut S | Box<S> | Rc<S> | Arc<S> | Pin<P>
S = Self | P
Терминал Self в этой грамматике обозначает тип, разрешающийся в реализующий тип.
Это также может включать контекстный псевдоним типа Self, другие псевдонимы типов
или проекции ассоциированных типов, разрешающиеся в реализующий тип.
#![allow(unused)] fn main() { use std::rc::Rc; use std::sync::Arc; use std::pin::Pin; // Примеры методов, реализованных на структуре `Example`. struct Example; type Alias = Example; trait Trait { type Output; } impl Trait for Example { type Output = Example; } impl Example { fn by_value(self: Self) {} fn by_ref(self: &Self) {} fn by_ref_mut(self: &mut Self) {} fn by_box(self: Box<Self>) {} fn by_rc(self: Rc<Self>) {} fn by_arc(self: Arc<Self>) {} fn by_pin(self: Pin<&Self>) {} fn explicit_type(self: Arc<Example>) {} fn with_lifetime<'a>(self: &'a Self) {} fn nested<'a>(self: &mut &'a Arc<Rc<Box<Alias>>>) {} fn via_projection(self: <Example as Trait>::Output) {} } }
Может использоваться сокращённый синтаксис без указания типа, который имеет следующие эквиваленты:
| Сокращение | Эквивалент |
|---|---|
self | self: Self |
&'lifetime self | self: &'lifetime Self |
&'lifetime mut self | self: &'lifetime mut Self |
Note
Времена жизни могут быть, и обычно опускаются с этим сокращением.
Если параметр self предварен mut, он становится изменяемой переменной,
аналогично обычным параметрам, использующим mut идентификаторный образец. Например:
#![allow(unused)] fn main() { trait Changer: Sized { fn change(mut self) {} fn modify(mut self: Box<Self>) {} } }
В качестве примера методов в трейте рассмотрим следующее:
#![allow(unused)] fn main() { type Surface = i32; type BoundingBox = i32; trait Shape { fn draw(&self, surface: Surface); fn bounding_box(&self) -> BoundingBox; } }
Это определяет трейт с двумя методами. Все значения, которые имеют реализации
этого трейта, пока трейт находится в области видимости, могут иметь свои методы draw и
bounding_box вызванными.
#![allow(unused)] fn main() { type Surface = i32; type BoundingBox = i32; trait Shape { fn draw(&self, surface: Surface); fn bounding_box(&self) -> BoundingBox; } struct Circle { // ... } impl Shape for Circle { // ... fn draw(&self, _: Surface) {} fn bounding_box(&self) -> BoundingBox { 0i32 } } impl Circle { fn new() -> Circle { Circle{} } } let circle_shape = Circle::new(); let bounding_box = circle_shape.bounding_box(); }
2018 Edition differences
В редакции 2015 можно было объявлять методы трейта с анонимными параметрами (например,
fn foo(u8)). Это устарело и является ошибкой начиная с редакции 2018. Все параметры должны иметь имя аргумента.
Атрибуты на параметрах методов
Атрибуты на параметрах методов следуют тем же правилам и ограничениям, что и обычные параметры функций.
Ассоциированные типы
Ассоциированные типы - это псевдонимы типов, ассоциированные с другим типом.
Ассоциированные типы не могут быть определены в собственных реализациях и не могут иметь реализацию по умолчанию в трейтах.
Объявление ассоциированного типа объявляет сигнатуру для определений ассоциированного типа.
Оно записывается в одной из следующих форм, где Assoc - это
имя ассоциированного типа, Params - это разделённый запятыми список параметров типа,
времени жизни или констант, Bounds - это разделённый плюсами список ограничений трейтов,
которым должен удовлетворять ассоциированный тип, и WhereBounds - это разделённый запятыми список
ограничений, которым должны удовлетворять параметры:
type Assoc;
type Assoc: Bounds;
type Assoc<Params>;
type Assoc<Params>: Bounds;
type Assoc<Params> where WhereBounds;
type Assoc<Params>: Bounds where WhereBounds;
Идентификатор - это имя объявленного псевдонима типа.
Опциональные ограничения трейтов должны выполняться реализациями псевдонима типа.
Существует неявное ограничение Sized на ассоциированные типы, которое может быть ослаблено с использованием специального ограничения ?Sized.
Определение ассоциированного типа определяет псевдоним типа для реализации трейта на типе.
Они записываются аналогично объявлению ассоциированного типа, но не могут содержать Bounds, но вместо этого должны содержать Type:
type Assoc = Type;
type Assoc<Params> = Type; // тип `Type` здесь может ссылаться на `Params`
type Assoc<Params> = Type where WhereBounds;
type Assoc<Params> where WhereBounds = Type; // устарело, предпочтительнее форма выше
Если тип Item имеет ассоциированный тип Assoc из трейта Trait, то
<Item as Trait>::Assoc - это тип, который является псевдонимом типа, указанного в
определении ассоциированного типа.
Более того, если Item является параметром типа, то Item::Assoc может использоваться в параметрах типа.
Ассоциированные типы могут включать обобщённые параметры и where-предложения; они
часто называются обобщёнными ассоциированными типами, или GATs. Если тип Thing
имеет ассоциированный тип Item из трейта Trait с обобщениями <'a>, то
тип может быть назван как <Thing as Trait>::Item<'x>, где 'x - некоторое время жизни
в области видимости. В этом случае 'x будет использоваться везде, где 'a появляется в определениях
ассоциированных типов в impls.
trait AssociatedType { // Объявление ассоциированного типа type Assoc; } struct Struct; struct OtherStruct; impl AssociatedType for Struct { // Определение ассоциированного типа type Assoc = OtherStruct; } impl OtherStruct { fn new() -> OtherStruct { OtherStruct } } fn main() { // Использование ассоциированного типа для ссылки на OtherStruct как <Struct as AssociatedType>::Assoc let _other_struct: OtherStruct = <Struct as AssociatedType>::Assoc::new(); }
Пример ассоциированных типов с обобщениями и where-предложениями:
struct ArrayLender<'a, T>(&'a mut [T; 16]); trait Lend { // Объявление обобщённого ассоциированного типа type Lender<'a> where Self: 'a; fn lend<'a>(&'a mut self) -> Self::Lender<'a>; } impl<T> Lend for [T; 16] { // Определение обобщённого ассоциированного типа type Lender<'a> = ArrayLender<'a, T> where Self: 'a; fn lend<'a>(&'a mut self) -> Self::Lender<'a> { ArrayLender(self) } } fn borrow<'a, T: Lend>(array: &'a mut T) -> <T as Lend>::Lender<'a> { array.lend() } fn main() { let mut array = [0usize; 16]; let lender = borrow(&mut array); }
Пример контейнера с ассоциированными типами
Рассмотрим следующий пример трейта Container. Заметьте, что тип
доступен для использования в сигнатурах методов:
#![allow(unused)] fn main() { trait Container { type E; fn empty() -> Self; fn insert(&mut self, elem: Self::E); } }
Для того чтобы тип реализовал этот трейт, он должен не только предоставить
реализации для каждого метода, но он должен указать тип E. Вот
реализация Container для типа стандартной библиотеки Vec:
#![allow(unused)] fn main() { trait Container { type E; fn empty() -> Self; fn insert(&mut self, elem: Self::E); } impl<T> Container for Vec<T> { type E = T; fn empty() -> Vec<T> { Vec::new() } fn insert(&mut self, x: T) { self.push(x); } } }
Связь между Bounds и WhereBounds
В этом примере:
#![allow(unused)] fn main() { use std::fmt::Debug; trait Example { type Output<T>: Ord where T: Debug; } }
Для ссылки на ассоциированный тип типа <X as Example>::Output<Y>, сам ассоциированный тип должен быть Ord, и тип Y должен быть Debug.
Обязательные where-предложения на обобщённых ассоциированных типах
Объявления обобщённых ассоциированных типов в трейтах в настоящее время могут требовать список where-предложений, зависящих от функций в трейте и того, как используется GAT. Эти правила могут быть ослаблены в будущем; обновления можно найти в репозитории инициативы обобщённых ассоциированных типов.
Короче говоря, эти where-предложения требуются для максимизации разрешённых определений ассоциированного типа в impls. Для этого любые предложения, которые могут быть доказаны, что выполняются на функциях (используя параметры функции или трейта) где GAT появляется как вход или выход, также должны быть записаны на самом GAT.
#![allow(unused)] fn main() { trait LendingIterator { type Item<'x> where Self: 'x; fn next<'a>(&'a mut self) -> Self::Item<'a>; } }
В вышеприведённом, на функции next, мы можем доказать, что Self: 'a, из-за
подразумеваемых ограничений от &'a mut self; поэтому мы должны записать эквивалентное
ограничение на самом GAT: where Self: 'x.
Когда есть несколько функций в трейте, которые используют GAT, то используется пересечение ограничений из разных функций, а не объединение.
#![allow(unused)] fn main() { trait Check<T> { type Checker<'x>; fn create_checker<'a>(item: &'a T) -> Self::Checker<'a>; fn do_check(checker: Self::Checker<'_>); } }
В этом примере никакие ограничения не требуются на type Checker<'a>;. Хотя мы
знаем, что T: 'a на create_checker, мы не знаем этого на do_check. Однако,
если do_check был бы закомментирован, то ограничение where T: 'x было бы требуется
на Checker.
Ограничения на ассоциированные типы также распространяют обязательные where-предложения.
#![allow(unused)] fn main() { trait Iterable { type Item<'a> where Self: 'a; type Iterator<'a>: Iterator<Item = Self::Item<'a>> where Self: 'a; fn iter<'a>(&'a self) -> Self::Iterator<'a>; } }
Здесь where Self: 'a требуется на Item из-за iter. Однако Item
используется в ограничениях Iterator, предложение where Self: 'a также требуется
там.
Наконец, любые явные использования 'static на GATs в трейте не учитываются в
обязательных ограничениях.
#![allow(unused)] fn main() { trait StaticReturn { type Y<'a>; fn foo(&self) -> Self::Y<'static>; } }
Ассоциированные константы
Ассоциированные константы - это константы, ассоциированные с типом.
Объявление ассоциированной константы объявляет сигнатуру для определений
ассоциированной константы. Оно записывается как const, затем идентификатор,
затем :, затем тип, завершаемый ;.
Идентификатор - это имя константы, используемое в пути. Тип - это тип, который определение должно реализовать.
Определение ассоциированной константы определяет константу, ассоциированную с типом. Оно записывается так же, как константный элемент.
Определения ассоциированных констант проходят вычисление констант только когда на них ссылаются. Более того, определения, которые включают обобщённые параметры, вычисляются после мономорфизации.
struct Struct; struct GenericStruct<const ID: i32>; impl Struct { // Определение не вычисляется немедленно const PANIC: () = panic!("compile-time panic"); } impl<const ID: i32> GenericStruct<ID> { // Определение не вычисляется немедленно const NON_ZERO: () = if ID == 0 { panic!("contradiction") }; } fn main() { // Ссылка на Struct::PANIC вызывает ошибку компиляции let _ = Struct::PANIC; // Нормально, ID не 0 let _ = GenericStruct::<1>::NON_ZERO; // Ошибка компиляции от вычисления NON_ZERO с ID=0 let _ = GenericStruct::<0>::NON_ZERO; }
Примеры ассоциированных констант
Базовый пример:
trait ConstantId { const ID: i32; } struct Struct; impl ConstantId for Struct { const ID: i32 = 1; } fn main() { assert_eq!(1, Struct::ID); }
Использование значений по умолчанию:
trait ConstantIdDefault { const ID: i32 = 1; } struct Struct; struct OtherStruct; impl ConstantIdDefault for Struct {} impl ConstantIdDefault for OtherStruct { const ID: i32 = 5; } fn main() { assert_eq!(1, Struct::ID); assert_eq!(5, OtherStruct::ID); }
Атрибуты
Syntax
InnerAttribute → # ! [ Attr ]
OuterAttribute → # [ Attr ]
Attr →
SimplePath AttrInput?
| unsafe ( SimplePath AttrInput? )
Атрибут — это общие метаданные произвольной формы, которые интерпретируются в соответствии с именем, соглашениями, языком и версией компилятора. Атрибуты основаны на атрибутах из ECMA-335, а синтаксис заимствован из ECMA-334 (C#).
Внутренние атрибуты, записываемые с восклицательным знаком (!) после решётки (#), применяются к форме, внутри которой объявлен атрибут.
Example
#![allow(unused)] fn main() { // Общие метаданные, применяемые к окружающему модулю или крейту. #![crate_type = "lib"] // Внутренний атрибут применяется ко всей функции. fn some_unused_variables() { #![allow(unused_variables)] let x = (); let y = (); let z = (); } }
Внешние атрибуты, записываемые без восклицательного знака после решётки, применяются к форме, следующей за атрибутом.
Example
#![allow(unused)] fn main() { // Функция, помеченная как модульный тест #[test] fn test_foo() { /* ... */ } // Условно компилируемый модуль #[cfg(target_os = "linux")] mod bar { /* ... */ } // Атрибут для подавления предупреждения/ошибки #[allow(non_camel_case_types)] type int8_t = i8; }
Атрибут состоит из пути к атрибуту, за которым следует необязательное дерево токенов-разделителей, интерпретация которого определяется атрибутом. Атрибуты, отличные от атрибутов-макросов, также позволяют вводу быть знаком равенства (=), за которым следует выражение. Для получения более подробной информации см. синтаксис метаэлементов ниже.
Применение атрибута может быть небезопасным. Чтобы избежать неопределённого поведения при использовании этих атрибутов, должны быть выполнены определённые обязательства, которые компилятор не может проверить. Чтобы утверждать, что они выполнены, атрибут оборачивается в unsafe(..), например #[unsafe(no_mangle)].
Следующие атрибуты являются небезопасными:
Атрибуты можно классифицировать на следующие виды:
- Встроенные атрибуты
- Атрибуты-макросы процедур
- Вспомогательные атрибуты макросов-производных
- Инструментальные атрибуты
Атрибуты могут применяться ко многим формам в языке:
- Все объявления элементов принимают внешние атрибуты, в то время как внешние блоки, функции, реализации и модули принимают внутренние атрибуты.
- Большинство операторов принимают внешние атрибуты (см. Атрибуты выражений для ограничений на операторы выражений).
- Блочные выражения принимают внешние и внутренние атрибуты, но только когда они являются внешним выражением оператора выражения или конечным выражением другого блочного выражения.
- Варианты перечислений и поля структур и объединений принимают внешние атрибуты.
- Ветви выражений сопоставления принимают внешние атрибуты.
- Параметры обобщённого времени жизни или типа принимают внешние атрибуты.
- Выражения принимают внешние атрибуты в ограниченных ситуациях, см. Атрибуты выражений для подробностей.
- Параметры функций, замыканий и указателей на функции принимают внешние атрибуты. Это включает атрибуты на вариативных параметрах, обозначенных
...в указателях на функции и внешних блоках.
Синтаксис метаэлементов атрибутов
“Метаэлемент” — это синтаксис, используемый для правила Attr большинством встроенных атрибутов. Он имеет следующую грамматику:
Syntax
MetaItem →
SimplePath
| SimplePath = Expression
| SimplePath ( MetaSeq? )
MetaSeq →
MetaItemInner ( , MetaItemInner )* ,?
Expressions in meta items must macro Выражения в метаэлементах должны раскрываться в макросах в литеральные выражения, которые не должны включать суффиксы целочисленных или плавающих типов. Выражения, не являющиеся литеральными, будут синтаксически приняты (и могут быть переданы в процедурные макросы), но будут отклонены после разбора.
Обратите внимание, что если атрибут появляется внутри другого макроса, он будет раскрыт после этого внешнего макроса. Например, следующий код сначала раскроет процедурный макрос Serialize, который должен сохранить вызов include_str!, чтобы его можно было раскрыть:
#[derive(Serialize)]
struct Foo {
#[doc = include_str!("x.md")]
x: u32
}
Кроме того, макросы в атрибутах будут раскрыты только после всех других атрибутов, применённых к элементу:
#[macro_attr1] // раскрывается первым
#[doc = mac!()] // `mac!` раскрывается четвёртым.
#[macro_attr2] // раскрывается вторым
#[derive(MacroDerive1, MacroDerive2)] // раскрывается третьим
fn foo() {}
Различные встроенные атрибуты используют разные подмножества синтаксиса метаэлементов для указания своего ввода. Следующие правила грамматики показывают некоторые часто используемые формы:
Syntax
MetaWord →
IDENTIFIER
MetaNameValueStr →
IDENTIFIER = ( STRING_LITERAL | RAW_STRING_LITERAL )
MetaListPaths →
IDENTIFIER ( ( SimplePath ( , SimplePath )* ,? )? )
MetaListIdents →
IDENTIFIER ( ( IDENTIFIER ( , IDENTIFIER )* ,? )? )
MetaListNameValueStr →
IDENTIFIER ( ( MetaNameValueStr ( , MetaNameValueStr )* ,? )? )
Некоторые примеры метаэлементов:
| Стиль | Пример |
|---|---|
| MetaWord | no_std |
| MetaNameValueStr | doc = "example" |
| MetaListPaths | allow(unused, clippy::inline_always) |
| MetaListIdents | macro_use(foo, bar) |
| MetaListNameValueStr | link(name = "CoreFoundation", kind = "framework") |
Активные и инертные атрибуты
Атрибут является либо активным, либо инертным. Во время обработки атрибутов активные атрибуты удаляют себя из формы, на которой они находятся, в то время как инертные атрибуты остаются на ней.
Атрибуты cfg и cfg_attr являются активными.
Атрибуты-макросы процедур являются активными. Все остальные атрибуты являются инертными.
Инструментальные атрибуты
Компилятор может разрешать атрибуты для внешних инструментов, где каждый инструмент находится в своём собственном модуле в прелюдии инструментов. Первый сегмент пути атрибута — это имя инструмента, за которым следует один или несколько дополнительных сегментов, интерпретация которых зависит от инструмента.
Когда инструмент не используется, атрибуты инструмента принимаются без предупреждения. Когда инструмент используется, он отвечает за обработку и интерпретацию своих атрибутов.
Инструментальные атрибуты недоступны, если используется атрибут no_implicit_prelude.
#![allow(unused)] fn main() { // Указывает инструменту rustfmt не форматировать следующий элемент. #[rustfmt::skip] struct S { } // Управляет порогом "цикломатической сложности" для инструмента clippy. #[clippy::cyclomatic_complexity = "100"] pub fn f() {} }
Note
В настоящее время
rustcраспознаёт инструменты “clippy”, “rustfmt”, “diagnostic”, “miri” и “rust_analyzer”.
Указатель встроенных атрибутов
Ниже приведён указатель всех встроенных атрибутов.
-
Условная компиляция
-
Тестирование
test— Помечает функцию как тест.ignore— Отключает тестовую функцию.should_panic— Указывает, что тест должен вызывать панику.
-
Производные (Derive)
derive— Автоматические реализации трейтов.automatically_derived— Маркер для реализаций, созданныхderive.
-
Макросы
macro_export— Экспортирует макросmacro_rulesдля использования между крейтами.macro_use— Расширяет видимость макросов или импортирует макросы из других крейтов.proc_macro— Определяет функциональный макрос.proc_macro_derive— Определяет производный макрос.proc_macro_attribute— Определяет атрибут-макрос.
-
Диагностика
allow,expect,warn,deny,forbid— Изменяет уровень проверок по умолчанию.deprecated— Генерирует предупреждения об устаревании.must_use— Генерирует проверку для неиспользуемых значений.diagnostic::on_unimplemented— Подсказывает компилятору выдавать определённое сообщение об ошибке, если трейт не реализован.diagnostic::do_not_recommend— Подсказывает компилятору не показывать определённую реализацию трейта в сообщениях об ошибках.
-
ABI, компоновка, символы и FFI
link— Указывает собственную библиотеку для компоновки с блокомextern.link_name— Указывает имя символа для функций или статиков в блокеextern.link_ordinal— Указывает порядковый номер символа для функций или статиков в блокеextern.no_link— Предотвращает компоновку внешнего крейта.repr— Управляет размещением типа в памяти.crate_type— Указывает тип крейта (библиотека, исполняемый файл и т.д.).no_main— Отключает генерацию символаmain.export_name— Указывает экспортируемое имя символа для функции или статика.link_section— Указывает раздел объектного файла для функции или статика.no_mangle— Отключает кодирование имён символов.used— Принуждает компилятор сохранять статический элемент в выходном объектном файле.crate_name— Указывает имя крейта.
-
Генерация кода
inline— Подсказка для встраивания кода.cold— Подсказка, что функция вряд ли будет вызвана.naked— Запрещает компилятору генерировать пролог и эпилог функции.no_builtins— Отключает использование определённых встроенных функций.target_feature— Настраивает генерацию кода для конкретной платформы.track_caller— Передаёт местоположение родительского вызова вstd::panic::Location::caller().instruction_set— Указывает набор инструкций, используемый для генерации кода функции.
-
Документация
doc— Определяет документацию. См. Книгу Rustdoc для получения дополнительной информации. Doc-комментарии преобразуются в атрибутыdoc.
-
Прелюдии (Preludes)
no_std— Удаляет std из прелюдии.no_implicit_prelude— Отключает поиск в прелюдии внутри модуля.
-
Модули
path— Указывает имя файла для модуля.
-
Ограничения
recursion_limit— Устанавливает максимальный предел рекурсии для определённых операций во время компиляции.type_length_limit— Устанавливает максимальный размер полиморфного типа.
-
Время выполнения
panic_handler— Устанавливает функцию для обработки паник.global_allocator— Устанавливает глобальный распределитель памяти.windows_subsystem— Указывает подсистему Windows для компоновки.
-
Функциональные возможности (Features)
feature— Используется для включения нестабильных или экспериментальных функций компилятора. См. Нестабильную книгу для функций, реализованных вrustc.
-
Система типов
non_exhaustive— Указывает, что в будущем к типу будут добавлены поля/варианты.
-
Отладчик
debugger_visualizer— Встраивает файл, определяющий вывод отладчика для типа.collapse_debuginfo— Управляет тем, как вызовы макросов кодируются в отладочной информации.
Атрибуты тестирования
Следующие атрибуты используются для указания функций для выполнения тестов. Компиляция крейта в режиме “test” включает построение тестовых функций вместе с тестовым harness’ом для выполнения тестов. Включение тестового режима также включает условную компиляцию test.
Атрибут test
Атрибут test атрибуты помечает функцию для выполнения в качестве теста.
Example
#![allow(unused)] fn main() { pub fn add(left: u64, right: u64) -> u64 { left + right } #[test] fn it_works() { let result = add(2, 2); assert_eq!(result, 4); } }
Атрибут test использует синтаксис MetaWord.
Атрибут test может применяться только к свободным функциям, которые являются моноформными, не принимают аргументов и возвращают тип, реализующий трейт Termination.
Note
Некоторые типы, реализующие трейт
Termination, включают:
()Result<T, E> where T: Termination, E: Debug
Только первое использование test на функции имеет эффект.
Note
rustcвыдаёт предупреждения при любом использовании после первого. В будущем это может стать ошибкой.
Атрибут test экспортируется из прелюдии стандартной библиотеки как std::prelude::v1::test.
Эти функции компилируются только в тестовом режиме.
Note
Тестовый режим включается передачей аргумента
--testвrustcили использованиемcargo test.
Test harness вызывает метод report возвращаемого значения и классифицирует тест как пройденный или проваленный в зависимости от того, представляет ли результирующий ExitCode успешное завершение.
В частности:
- Тесты, возвращающие
(), проходят, пока они завершаются и не паникуют. - Тесты, возвращающие
Result<(), E>, проходят, пока они возвращаютOk(()). - Тесты, возвращающие
ExitCode::SUCCESS, проходят, а тесты, возвращающиеExitCode::FAILURE, проваливаются. - Тесты, которые не завершаются, не проходят и не проваливаются.
Example
#![allow(unused)] fn main() { use std::io; fn setup_the_thing() -> io::Result<i32> { Ok(1) } fn do_the_thing(s: &i32) -> io::Result<()> { Ok(()) } #[test] fn test_the_thing() -> io::Result<()> { let state = setup_the_thing()?; // ожидается успех do_the_thing(&state)?; // ожидается успех Ok(()) } }
Атрибут ignore
Атрибут ignore атрибуты может использоваться с атрибутом test, чтобы указать test harness’у не выполнять эту функцию как тест.
Example
#![allow(unused)] fn main() { #[test] #[ignore] fn check_thing() { // … } }
Note
Test harness
rustcподдерживает флаг--include-ignoredдля принудительного запуска игнорируемых тестов.
Атрибут ignore использует синтаксисы MetaWord и MetaNameValueStr.
Форма MetaNameValueStr атрибута ignore предоставляет способ указать причину, по которой тест игнорируется.
Example
#![allow(unused)] fn main() { #[test] #[ignore = "not yet implemented"] fn mytest() { // … } }
Атрибут ignore может применяться только к функциям, аннотированным атрибутом test.
Note
rustcигнорирует использование в других позициях, но выдаёт предупреждения. В будущем это может стать ошибкой.
Только первое использование ignore на функции имеет эффект.
Note
rustcвыдаёт предупреждения при любом использовании после первого. В будущем это может стать ошибкой.
Игнорируемые тесты всё равно компилируются в тестовом режиме, но не выполняются.
Атрибут should_panic
Атрибут should_panic атрибуты приводит к тому, что тест проходит только если тестовая функция, к которой применён атрибут, вызывает панику.
Example
#![allow(unused)] fn main() { #[test] #[should_panic(expected = "values don't match")] fn mytest() { assert_eq!(1, 2, "values don't match"); } }
Атрибут should_panic имеет следующие формы:
-
Example
#![allow(unused)] fn main() { #[test] #[should_panic] fn mytest() { panic!("error: some message, and more"); } } -
MetaNameValueStr — Указанная строка должна присутствовать в сообщении паники для прохождения теста.
Example
#![allow(unused)] fn main() { #[test] #[should_panic = "some message"] fn mytest() { panic!("error: some message, and more"); } } -
MetaListNameValueStr — Как и в синтаксисе MetaNameValueStr, указанная строка должна присутствовать в сообщении паники.
Example
#![allow(unused)] fn main() { #[test] #[should_panic(expected = "some message")] fn mytest() { panic!("error: some message, and more"); } }
Атрибут should_panic может применяться только к функциям, аннотированным атрибутом test.
Note
rustcигнорирует использование в других позициях, но выдаёт предупреждения. В будущем это может стать ошибкой.
Только первое использование should_panic на функции имеет эффект.
Note
rustcвыдаёт предупреждения о совместимости с будущими версиями при любом использовании после первого. В будущем это может стать ошибкой.
Когда используется форма MetaNameValueStr или форма MetaListNameValueStr с ключом expected, указанная строка должна присутствовать где-либо в сообщении паники для прохождения теста.
Возвращаемый тип тестовой функции должен быть ().
Derive (Автоматическая реализация)
Атрибут derive вызывает один или несколько derive macros, позволяя автоматически генерировать новые элементы для структур данных. Вы можете создавать derive макросы с помощью procedural macros.
Example
Макрос
PartialEqсоздаёт реализацию типажаPartialEqдляFoo<T> where T: PartialEq. МакросCloneаналогично создаёт реализацию дляClone.#![allow(unused)] fn main() { #[derive(PartialEq, Clone)] struct Foo<T> { a: i32, b: T, } }Сгенерированные
implблоки эквивалентны:#![allow(unused)] fn main() { struct Foo<T> { a: i32, b: T } impl<T: PartialEq> PartialEq for Foo<T> { fn eq(&self, other: &Foo<T>) -> bool { self.a == other.a && self.b == other.b } } impl<T: Clone> Clone for Foo<T> { fn clone(&self) -> Self { Foo { a: self.a.clone(), b: self.b.clone() } } } }
Атрибут derive использует синтаксис MetaListPaths для указания списка путей к derive macros, которые нужно вызвать.
Атрибут derive может применяться только к структурам, перечислениям и объединениям.
Атрибут derive может использоваться на одном элементе любое количество раз. Все макросы, перечисленные во всех атрибутах, будут вызваны.
Атрибут derive экспортируется в прелюдию стандартной библиотеки как core::prelude::v1::derive.
Встроенные (built-in) derive макросы определены в языковой прелюдии. Список встроенных макросов:
Встроенные derive макросы включают атрибут automatically_derived в генерируемые ими реализации.
Во время раскрытия макросов для каждого элемента в списке derives вызывается соответствующий derive макрос, который раскрывается в ноль или более элементов.
Атрибут automatically_derived
Атрибут automatically_derived используется для пометки реализации, указывая, что она была автоматически создана derive macros. Он не имеет прямого эффекта, но может использоваться инструментами и диагностическими линтами для обнаружения этих автоматически сгенерированных реализаций.
Example
Для
#[derive(Clone)]наstruct Example, derive macros может создать:#![allow(unused)] fn main() { struct Example; #[automatically_derived] impl ::core::clone::Clone for Example { #[inline] fn clone(&self) -> Self { Example } } }
Атрибут automatically_derived использует синтаксис MetaWord.
Атрибут automatically_derived может применяться только к реализациям.
Note
rustcигнорирует использование в других позициях, но выдает предупреждение (lint). В будущем это может стать ошибкой.
Использование automatically_derived более одного раза на реализации имеет тот же эффект, что и однократное использование.
Note
rustcвыдает предупреждение на любое использование после первого.
Атрибут automatically_derived не имеет поведения.
Диагностические атрибуты
Следующие атрибуты используются для управления или генерации диагностических сообщений во время компиляции.
Атрибуты проверки линтов (lint)
Проверка линта определяет потенциально нежелательный шаблон кодирования, такой как недостижимый код или пропущенная документация.
Атрибуты линтов allow, expect, warn, deny и forbid используют синтаксис MetaListPaths для указания списка имён линтов, чтобы изменить уровень линта для сущности, к которой применяется атрибут.
Для любой проверки линта C:
#[allow(C)]отключает проверку дляC, чтобы нарушения не сообщались.
#[expect(C)]указывает, что ожидается срабатывание линтаC. Атрибут подавит срабатываниеCили выдаст предупреждение, если ожидание не выполнено.
#[warn(C)]предупреждает о нарушенияхC, но продолжает компиляцию.
#[deny(C)]сигнализирует об ошибке после обнаружения нарушенияC.
#[forbid(C)]аналогиченdeny(C), но также запрещает изменение уровня линта впоследствии.
Note
Проверки линтов, поддерживаемые
rustc, можно найти с помощьюrustc -W help, вместе с их настройками по умолчанию, и они документированы в книге rustc.
#![allow(unused)] fn main() { pub mod m1 { // Отсутствие документации игнорируется здесь #[allow(missing_docs)] pub fn undocumented_one() -> i32 { 1 } // Отсутствие документации вызывает предупреждение здесь #[warn(missing_docs)] pub fn undocumented_too() -> i32 { 2 } // Отсутствие документации вызывает ошибку здесь #[deny(missing_docs)] pub fn undocumented_end() -> i32 { 3 } } }
Атрибуты линтов могут переопределять уровень, указанный в предыдущем атрибуте, до тех пор, пока уровень не пытается изменить запрещённый линт (за исключением deny, который разрешён внутри контекста forbid, но игнорируется). Предыдущие атрибуты — это атрибуты из более высокого уровня в синтаксическом дереве или из предыдущего атрибута на той же сущности, перечисленные в порядке следования исходного кода слева направо.
Этот пример показывает, как можно использовать allow и warn для включения и выключения конкретной проверки:
#![allow(unused)] fn main() { #[warn(missing_docs)] pub mod m2 { #[allow(missing_docs)] pub mod nested { // Отсутствие документации игнорируется здесь pub fn undocumented_one() -> i32 { 1 } // Отсутствие документации вызывает предупреждение здесь, // несмотря на разрешение выше. #[warn(missing_docs)] pub fn undocumented_two() -> i32 { 2 } } // Отсутствие документации вызывает предупреждение здесь pub fn undocumented_too() -> i32 { 3 } } }
Этот пример показывает, как можно использовать forbid для запрета использования allow или expect для этой проверки линта:
#![allow(unused)] fn main() { #[forbid(missing_docs)] pub mod m3 { // Попытка изменить уровень предупреждения вызывает ошибку здесь #[allow(missing_docs)] /// Returns 2. pub fn undocumented_too() -> i32 { 2 } } }
Note
rustcпозволяет устанавливать уровни линтов через командную строку, а также поддерживает установку пределов для сообщаемых линтов.
Причины линтов
Все атрибуты линтов поддерживают дополнительный параметр reason, чтобы предоставить контекст, почему был добавлен определённый атрибут. Эта причина будет отображена как часть сообщения линта, если линт сработает на заданном уровне.
#![allow(unused)] fn main() { // `keyword_idents` разрешён по умолчанию. Здесь мы запрещаем его, // чтобы избежать миграции идентификаторов при обновлении редакции. #![deny( keyword_idents, reason = "мы хотим избежать этих идентификаторов для совместимости с будущим" )] // Это имя было разрешено в редакции Rust 2015. Мы всё ещё стремимся избегать // этого для совместимости с будущим и чтобы не путать конечных пользователей. fn dyn() {} }
Вот другой пример, где линт разрешён с причиной:
#![allow(unused)] fn main() { use std::path::PathBuf; pub fn get_path() -> PathBuf { // Параметр `reason` в атрибутах `allow` служит документацией для читателя. #[allow(unused_mut, reason = "это изменяется только на некоторых платформах")] let mut file_name = PathBuf::from("git"); #[cfg(target_os = "windows")] file_name.set_extension("exe"); file_name } }
Атрибут #[expect]
Атрибут #[expect(C)] создаёт ожидание для линта C. Ожидание будет выполнено, если атрибут #[warn(C)] в том же месте привёл бы к срабатыванию линта. Если ожидание не выполнено, потому что линт C не сработал, линт unfulfilled_lint_expectations будет выдан на этом атрибуте.
fn main() { // Этот атрибут `#[expect]` создаёт ожидание, что линт `unused_variables` // сработает на следующем операторе. Это ожидание не выполнено, так как // переменная `question` используется макросом `println!`. // Поэтому линт `unfulfilled_lint_expectations` будет выдан на атрибуте. #[expect(unused_variables)] let question = "who lives in a pineapple under the sea?"; println!("{question}"); // Этот атрибут `#[expect]` создаёт ожидание, которое будет выполнено, так как // переменная `answer` никогда не используется. Линт `unused_variables`, который обычно // сработал бы, подавлен. Никакого предупреждения для оператора или атрибута не будет. #[expect(unused_variables)] let answer = "SpongeBob SquarePants!"; }
Ожидание линта выполняется только срабатываниями линтов, которые были подавлены атрибутом expect. Если уровень линта изменён в области видимости другими атрибутами уровня, такими как allow или warn, срабатывание линта будет обработано соответствующим образом, и ожидание останется невыполненным.
#![allow(unused)] fn main() { #[expect(unused_variables)] fn select_song() { // Это вызовет линт `unused_variables` на уровне предупреждения, // как определено атрибутом `warn`. Это не выполнит ожидание выше функции. #[warn(unused_variables)] let song_name = "Crab Rave"; // Атрибут `allow` подавляет срабатывание линта. Это не выполнит // ожидание, так как оно было подавлено атрибутом `allow`, // а не атрибутом `expect` выше функции. #[allow(unused_variables)] let song_creator = "Noisestorm"; // Этот атрибут `expect` подавит срабатывание линта `unused_variables` // на переменной. Атрибут `expect` выше функции всё равно не будет // выполнен, так как это срабатывание линта было подавлено локальным // атрибутом expect. #[expect(unused_variables)] let song_version = "Monstercat Release"; } }
Если атрибут expect содержит несколько линтов, каждый из них ожидается отдельно. Для группы линтов достаточно, чтобы один линт внутри группы сработал:
#![allow(unused)] fn main() { // Это ожидание будет выполнено неиспользуемым значением внутри функции, // так как сработавший линт `unused_variables` находится внутри группы линтов `unused`. #[expect(unused)] pub fn thoughts() { let unused = "I'm running out of examples"; } pub fn another_example() { // Этот атрибут создаёт два ожидания линта. Линт `unused_mut` будет // подавлен и таким образом выполнит первое ожидание. Линт `unused_variables` // не сработает, так как переменная используется. Это ожидание поэтому // останется неудовлетворённым, и будет выдано предупреждение. #[expect(unused_mut, unused_variables)] let mut link = "https://www.rust-lang.org/"; println!("Welcome to our community: {link}"); } }
Note
Поведение
#[expect(unfulfilled_lint_expectations)]в настоящее время определено так, что оно всегда генерирует линтunfulfilled_lint_expectations.
Группы линтов
Линты могут быть организованы в именованные группы, чтобы уровень связанных линтов можно было настраивать вместе. Использование именованной группы эквивалентно перечислению линтов внутри этой группы.
#![allow(unused)] fn main() { // Это разрешает все линты в группе "unused". #[allow(unused)] // Это переопределяет линт "unused_must_use" из группы "unused" на запрет. #[deny(unused_must_use)] fn example() { // Это не генерирует предупреждение, потому что линт "unused_variables" // находится в группе "unused". let x = 1; // Это генерирует ошибку, потому что результат не используется и // "unused_must_use" помечен как "deny". std::fs::remove_file("some_file"); // ERROR: неиспользованный `Result`, который должен быть использован } }
Существует специальная группа с именем “warnings”, которая включает все линты на уровне “warn”. Группа “warnings” игнорирует порядок атрибутов и применяется ко всем линтам, которые иначе выдали бы предупреждение внутри сущности.
#![allow(unused)] fn main() { unsafe fn an_unsafe_fn() {} // Порядок этих двух атрибутов не имеет значения. #[deny(warnings)] // Линт unsafe_code обычно разрешён ("allow") по умолчанию. #[warn(unsafe_code)] fn example_err() { // Это ошибка, потому что предупреждение `unsafe_code` было // повышено до "deny". unsafe { an_unsafe_fn() } // ERROR: использование `unsafe` блока } }
Атрибуты линтов инструментов
Линты инструментов позволяют использовать области видимости для линтов, чтобы allow, warn, deny или forbid линты определённых инструментов.
Линты инструментов проверяются только тогда, когда связанный инструмент активен. Если атрибут линта, такой как allow, ссылается на несуществующий линт инструмента, компилятор не будет предупреждать о несуществующем линте, пока вы не используете инструмент.
В остальном они работают так же, как обычные атрибуты линтов:
// установить всю группу линтов `pedantic` clippy на уровень warn #![warn(clippy::pedantic)] // отключить предупреждения от линта `filter_map` clippy #![allow(clippy::filter_map)] fn main() { // ... } // отключить линт `cmp_nan` clippy только для этой функции #[allow(clippy::cmp_nan)] fn foo() { // ... }
Атрибут deprecated
Атрибут deprecated помечает элемент как устаревший. rustc будет выдавать предупреждения при использовании элементов с #[deprecated]. rustdoc будет показывать устаревание элемента, включая версию since и note, если они доступны.
Атрибут deprecated имеет несколько форм:
deprecated— Выдаёт общее сообщение.deprecated = "message"— Включает данную строку в сообщение об устаревании.- Синтаксис MetaListNameValueStr с двумя необязательными полями:
since— Указывает номер версии, когда элемент был устаревшим.rustcв настоящее время не интерпретирует строку, но внешние инструменты, такие как Clippy, могут проверять корректность значения.note— Указывает строку, которая должна быть включена в сообщение об устаревании. Обычно используется для предоставления объяснения об устаревании и предпочтительных альтернатив.
Атрибут deprecated может применяться к любому элементу, элементу трейта, варианту перечисления, полю структуры, элементу внешнего блока или определению макроса. Он не может быть применён к элементам реализации трейта. При применении к элементу, содержащему другие элементы, такие как модуль или реализация, все дочерние элементы наследуют атрибут устаревания.
Вот пример:
#![allow(unused)] fn main() { #[deprecated(since = "5.2.0", note = "foo редко использовался. Пользователям следует вместо этого использовать bar")] pub fn foo() {} pub fn bar() {} }
RFC содержит обоснования и более подробную информацию.
Атрибут must_use
Атрибут must_use используется для выдачи диагностического предупреждения, когда значение не “используется”.
Атрибут must_use может применяться к пользовательским составным типам
(struct, enum и union), функциям и трейтам.
Атрибут must_use может включать сообщение, используя синтаксис MetaNameValueStr, такой как #[must_use = "example message"]. Сообщение будет выдано вместе с предупреждением.
При использовании на пользовательских составных типах, если выражение выражения-оператора имеет этот тип, то нарушается линт unused_must_use.
#![allow(unused)] fn main() { #[must_use] struct MustUse { // некоторые поля } impl MustUse { fn new() -> MustUse { MustUse {} } } // Нарушает линт `unused_must_use`. MustUse::new(); }
При использовании на функции, если выражение выражения-оператора является вызовом выражения этой функции, то нарушается линт unused_must_use.
#![allow(unused)] fn main() { #[must_use] fn five() -> i32 { 5i32 } // Нарушает линт unused_must_use. five(); }
При использовании на объявлении трейта, вызов выражения выражения-оператора функции, которая возвращает impl trait или dyn trait этого трейта, нарушает линт unused_must_use.
#![allow(unused)] fn main() { #[must_use] trait Critical {} impl Critical for i32 {} fn get_critical() -> impl Critical { 4i32 } // Нарушает линт `unused_must_use`. get_critical(); }
При использовании на функции в объявлении трейта, это поведение также применяется, когда вызов выражения является функцией из реализации трейта.
#![allow(unused)] fn main() { trait Trait { #[must_use] fn use_me(&self) -> i32; } impl Trait for i32 { fn use_me(&self) -> i32 { 0i32 } } // Нарушает линт `unused_must_use`. 5i32.use_me(); }
При использовании на функции в реализации трейта, атрибут ничего не делает.
Note
Тривиальные неточные выражения, содержащие значение, не нарушают линт. Примеры включают обёртывание значения в тип, который не реализует
Drop, а затем неиспользование этого типа и быть конечным выражением блочного выражения, которое не используется.#![allow(unused)] fn main() { #[must_use] fn five() -> i32 { 5i32 } // Ни одно из этих действий не нарушает линт unused_must_use. (five(),); Some(five()); { five() }; if true { five() } else { 0i32 }; match true { _ => five() }; }
Note
Идиоматично использовать let оператор с шаблоном
_, когда намеренно отбрасывается must-use значение.#![allow(unused)] fn main() { #[must_use] fn five() -> i32 { 5i32 } // Не нарушает линт unused_must_use. let _ = five(); }
Пространство имён атрибутов инструмента diagnostic
Пространство имён атрибута #[diagnostic] — это место для атрибутов, влияющих на сообщения об ошибках во время компиляции.
Подсказки, предоставляемые этими атрибутами, не гарантированно используются.
Неизвестные атрибуты в этом пространстве имён принимаются, хотя они могут выдавать предупреждения о неиспользуемых атрибутах. Кроме того, неверные входные данные для известных атрибутов обычно приводят к предупреждению (см. определения атрибутов для подробностей). Это предназначено для允许 добавления или удаления атрибутов и изменения входных данных в будущем, чтобы允许 изменения без необходимости сохранять незначащие атрибуты или опции работающими.
Атрибут diagnostic::on_unimplemented
Атрибут #[diagnostic::on_unimplemented] — это подсказка компилятору дополнить сообщение об ошибке, которое обычно генерируется в сценариях, где требуется трейт, но он не реализован для типа.
Атрибут должен размещаться на объявлении трейта, хотя его размещение в других позициях не является ошибкой.
Атрибут использует синтаксис MetaListNameValueStr для указания своих входных данных, хотя любые некорректные входные данные для атрибута не считаются ошибкой для обеспечения прямой и обратной совместимости.
Следующие ключи имеют указанное значение:
message— Текст для основного сообщения об ошибке.label— Текст для метки, показываемой встроенно в сломанном коде в сообщении об ошибке.note— Предоставляет дополнительные примечания.
Опция note может появляться несколько раз, что приводит к выдаче нескольких сообщений-примечаний.
Если любая другая опция появляется несколько раз, первое вхождение соответствующей опции определяет фактически используемое значение. Последующие вхождения генерируют предупреждение.
Предупреждение генерируется для любых неизвестных ключей.
Все три опции принимают строку в качестве аргумента, интерпретируемую с использованием того же форматирования, что и строка std::fmt.
Параметры форматирования с данным именованным параметром будут заменены следующим текстом:
{Self}— Имя типа, реализующего трейт.{ИмяПараметраДженерика}— Имя типа аргумента дженерика для данного параметра дженерика.
Любой другой параметр форматирования сгенерирует предупреждение, но в остальном будет включён в строку как есть.
Некорректные строки форматирования могут генерировать предупреждение, но в остальном разрешены, хотя могут отображаться не так, как задумано. Спецификаторы форматирования могут генерировать предупреждение, но в остальном игнорируются.
В этом примере:
#[diagnostic::on_unimplemented( message = "Моё сообщение для `ImportantTrait<{A}>`, реализованного для `{Self}`", label = "Моя метка", note = "Примечание 1", note = "Примечание 2" )] trait ImportantTrait<A> {} fn use_my_trait(_: impl ImportantTrait<i32>) {} fn main() { use_my_trait(String::new()); }
компилятор может сгенерировать сообщение об ошибке, которое выглядит так:
error[E0277]: Моё сообщение для `ImportantTrait<i32>`, реализованного для `String`
--> src/main.rs:14:18
|
14 | use_my_trait(String::new());
| ------------ ^^^^^^^^^^^^^ Моя метка
| |
| required by a bound introduced by this call
|
= help: трейт `ImportantTrait<i32>` не реализован для `String`
= note: Примечание 1
= note: Примечание 2
Атрибут diagnostic::do_not_recommend
Атрибут #[diagnostic::do_not_recommend] — это подсказка компилятору не показывать аннотированную реализацию трейта как часть диагностического сообщения.
Note
Подавление рекомендации может быть полезным, если вы знаете, что рекомендация обычно не будет полезна программисту. Это часто происходит с широкими, общими реализациями (blanket impls). Рекомендация может направить программиста по ложному пути, или реализация трейта может быть внутренней деталью, которую вы не хотите раскрывать, или ограничения (bounds) могут быть невыполнимыми для программиста.
Например, в сообщении об ошибке о том, что тип не реализует требуемый трейт, компилятор может найти реализацию трейта, которая удовлетворила бы требованиям, если бы не определённые ограничения в реализации трейта. Компилятор может сказать пользователю, что есть impl, но проблема в ограничениях реализации трейта. Атрибут
#[diagnostic::do_not_recommend]можно использовать, чтобы сказать компилятору не сообщать пользователю о реализации трейта, а вместо этого просто сказать, что тип не реализует требуемый трейт.
Атрибут должен размещаться на элементе реализации трейта, хотя его размещение в других позициях не является ошибкой.
Атрибут не принимает никаких аргументов, хотя неожиданные аргументы не считаются ошибкой.
В следующем примере есть трейт AsExpression, который используется для приведения произвольных типов к типу Expression, используемому в SQL-библиотеке. Есть метод check, который принимает AsExpression.
pub trait Expression { type SqlType; } pub trait AsExpression<ST> { type Expression: Expression<SqlType = ST>; } pub struct Text; pub struct Integer; pub struct Bound<T>(T); pub struct SelectInt; impl Expression for SelectInt { type SqlType = Integer; } impl<T> Expression for Bound<T> { type SqlType = T; } impl AsExpression<Integer> for i32 { type Expression = Bound<Integer>; } impl AsExpression<Text> for &'_ str { type Expression = Bound<Text>; } impl<T> Foo for T where T: Expression {} // Раскомментируйте эту строку, чтобы изменить рекомендацию. // #[diagnostic::do_not_recommend] impl<T, ST> AsExpression<ST> for T where T: Expression<SqlType = ST>, { type Expression = T; } trait Foo: Expression + Sized { fn check<T>(&self, _: T) -> <T as AsExpression<<Self as Expression>::SqlType>>::Expression where T: AsExpression<Self::SqlType>, { todo!() } } fn main() { SelectInt.check("bar"); }
Метод check типа SelectInt ожидает тип Integer. Вызов его с типом i32 работает, так как он преобразуется в Integer трейтом AsExpression. Однако вызов его со строкой не работает и генерирует ошибку, которая может выглядеть так:
error[E0277]: трейт-ограничение `&str: Expression` не выполнено
--> src/main.rs:53:15
|
53 | SelectInt.check("bar");
| ^^^^^ трейт `Expression` не реализован для `&str`
|
= help: следующие другие типы реализуют трейт `Expression`:
Bound<T>
SelectInt
note: требуется для `&str`, чтобы реализовать `AsExpression<Integer>`
--> src/main.rs:45:13
|
45 | impl<T, ST> AsExpression<ST> for T
| ^^^^^^^^^^^^^^^^ ^
46 | where
47 | T: Expression<SqlType = ST>,
| ------------------------ невыполненное трейт-ограничение, введённое здесь
Добавив атрибут #[diagnostic::do_not_recommend] к обобщённой impl для AsExpression, сообщение изменяется на:
error[E0277]: трейт-ограничение `&str: AsExpression<Integer>` не выполнено
--> src/main.rs:53:15
|
53 | SelectInt.check("bar");
| ^^^^^ трейт `AsExpression<Integer>` не реализован для `&str`
|
= help: трейт `AsExpression<Integer>` не реализован для `&str`
но трейт `AsExpression<Text>` реализован для него
= help: для этой реализации трейта ожидался `Text`, но найден `Integer`
Первое сообщение об ошибке включает несколько сбивающее с толку сообщение об ошибке об отношении &str и Expression, а также невыполненное трейт-ограничение в обобщённой impl. После добавления #[diagnostic::do_not_recommend] оно больше не рассматривает обобщённую impl для рекомендации. Сообщение должно быть немного яснее, с указанием, что строку нельзя преобразовать в Integer.
Атрибуты генерации кода
Следующие атрибуты используются для управления генерацией кода.
Атрибут inline
Атрибут inline предлагает, следует ли разместить копию кода функции в месте вызова вместо генерации вызова функции.
Example
#![allow(unused)] fn main() { #[inline] pub fn example1() {} #[inline(always)] pub fn example2() {} #[inline(never)] pub fn example3() {} }
Note
rustcавтоматически встраивает функции, когда это кажется целесообразным. Используйте этот атрибут осторожно, так как неправильные решения о том, что встраивать, могут замедлить программы.
Синтаксис атрибута inline:
Syntax
InlineAttribute →
inline ( always )
| inline ( never )
| inline
Атрибут inline может применяться только к функциям с телами — замыканиям, async блокам, свободным функциям, ассоциированным функциям в неявной реализации или реализации трейта, и ассоциированным функциям в определении трейта, когда эти функции имеют определение по умолчанию.
Note
rustcигнорирует использование в других позициях, но выдает предупреждение. В будущем это может стать ошибкой.
Note
Хотя атрибут может применяться к замыканиям и async блокам, полезность этого ограничена, так как мы ещё не поддерживаем атрибуты на выражениях.
#![allow(unused)] fn main() { // Мы разрешаем атрибуты на операторах. #[inline] || (); // OK #[inline] async {}; // OK }#![allow(unused)] fn main() { // Мы ещё не разрешаем атрибуты на выражениях. let f = #[inline] || (); // ERROR }
Только первое использование inline на функции имеет эффект.
Note
rustcвыдает предупреждение на любое использование после первого. В будущем это может стать ошибкой.
Атрибут inline поддерживает следующие режимы:
#[inline]предлагает выполнить встраивание (inline expansion).#[inline(always)]предлагает, что встраивание должно выполняться всегда.#[inline(never)]предлагает, что встраивание никогда не должно выполняться.
Note
В каждой форме атрибут является подсказкой. Компилятор может проигнорировать её.
Когда inline применяется к функции в трейте, он применяется только к коду определения по умолчанию.
Когда inline применяется к async функции или async замыканию, он применяется только к коду сгенерированной функции poll.
Note
Для более подробной информации см. Rust issue #129347.
Атрибут inline игнорируется, если функция экспортируется внешне с no_mangle или export_name.
Атрибут cold
Атрибут cold предполагает, что функция вряд ли будет вызвана, что может помочь компилятору генерировать лучший код.
Example
#![allow(unused)] fn main() { #[cold] pub fn example() {} }
Атрибут cold использует синтаксис MetaWord.
Атрибут cold может применяться только к функциям с телами — замыканиям, async блокам, свободным функциям, ассоциированным функциям в неявной реализации или реализации трейта, и ассоциированным функциям в определении трейта, когда эти функции имеют определение по умолчанию.
Note
rustcигнорирует использование в других позициях, но выдает предупреждение. В будущем это может стать ошибкой.
Note
Хотя атрибут может применяться к замыканиям и async блокам, полезность этого ограничена, так как мы ещё не поддерживаем атрибуты на выражениях.
Только первое использование cold на функции имеет эффект.
Note
rustcвыдает предупреждение на любое использование после первого. В будущем это может стать ошибкой.
Когда cold применяется к функции в трейте, он применяется только к коду определения по умолчанию.
Атрибут naked
Атрибут naked предотвращает генерацию компилятором пролога и эпилога функции.
Тело функции должно состоять ровно из одного вызова макроса naked_asm!.
Для функции с атрибутом не генерируется пролог или эпилог. Ассемблерный код в блоке naked_asm! составляет полное тело naked-функции.
Атрибут naked является небезопасным атрибутом. Аннотирование функции с #[unsafe(naked)] накладывает обязательство безопасности, что тело должно учитывать соглашение о вызовах функции, соблюдать её сигнатуру и либо возвращаться, либо расходиться (т.е. не проходить через конец ассемблерного кода).
Ассемблерный код может предполагать, что стек вызовов и состояние регистров действительны при входе в соответствии с сигнатурой и соглашением о вызовах функции.
Ассемблерный код не может быть дублирован компилятором, кроме случаев мономорфизации полиморфных функций.
Note
Гарантирование того, когда ассемблерный код может или не может быть дублирован, важно для naked-функций, которые определяют символы.
Линт unused_variables подавляется внутри naked-функций.
Атрибут inline не может быть применён к naked-функции.
Атрибут track_caller не может быть применён к naked-функции.
Атрибуты тестирования не могут быть применены к naked-функции.
Атрибут no_builtins
Атрибут no_builtins отключает оптимизацию определённых шаблонов кода, связанных с вызовами библиотечных функций, которые предполагаются существующими.
Example
#![allow(unused)] #![no_builtins] fn main() { }
Атрибут no_builtins использует синтаксис MetaWord.
Атрибут no_builtins может применяться только к корню крейта.
Только первое использование атрибута no_builtins имеет эффект.
Note
rustcвыдает предупреждение на любое использование после первого.
Атрибут target_feature
Атрибут target_feature может применяться к функции для включения генерации кода этой функции для определённых функций архитектуры платформы. Он использует синтаксис MetaListNameValueStr с единственным ключом enable, значение которого — строка с разделёнными запятыми именами функций для включения.
#![allow(unused)] fn main() { #[cfg(target_feature = "avx2")] #[target_feature(enable = "avx2")] fn foo_avx2() {} }
Каждая целевая архитектура имеет набор функций, которые могут быть включены. Ошибкой указывать функцию для целевой архитектуры, для которой крейт не компилируется.
Замыкания, определённые внутри функции с аннотацией target_feature, наследуют атрибут от включающей функции.
Неопределённое поведение — вызывать функцию, которая скомпилирована с функцией, не поддерживаемой на текущей платформе, на которой выполняется код, за исключением случаев, когда платформа явно документирует, что это безопасно.
Следующие ограничения применяются, если иное не указано в правилах платформы ниже:
- Безопасные функции
#[target_feature](и замыкания, которые наследуют атрибут) могут быть безопасно вызваны только внутри вызывающего, который включает всеtarget_feature, которые включает вызываемый. Это ограничение не применяется в небезопасном контексте. - Безопасные функции
#[target_feature](и замыкания, которые наследуют атрибут) могут быть приведены к безопасным указателям на функции только в контекстах, которые включают всеtarget_feature, которые включает приводимый. Это ограничение не применяется к небезопасным указателям на функции.
Неявно включённые функции включаются в это правило. Например, функция sse2 может вызывать функции, помеченные sse.
#![allow(unused)] fn main() { #[cfg(target_feature = "sse2")] { #[target_feature(enable = "sse")] fn foo_sse() {} fn bar() { // Вызов `foo_sse` здесь небезопасен, так как мы должны сначала убедиться, что SSE // доступен, даже если `sse` включен по умолчанию на целевой // платформе или вручную включен как флаги компилятора. unsafe { foo_sse(); } } #[target_feature(enable = "sse")] fn bar_sse() { // Вызов `foo_sse` здесь безопасен. foo_sse(); || foo_sse(); } #[target_feature(enable = "sse2")] fn bar_sse2() { // Вызов `foo_sse` здесь безопасен, потому что `sse2` подразумевает `sse`. foo_sse(); } } }
Функция с атрибутом #[target_feature] никогда не реализует семейство трейтов Fn, хотя замыкания, наследущие функции от включающей функции, делают это.
Атрибут #[target_feature] не разрешён в следующих местах:
- функция
main - функция
panic_handler - безопасные методы трейтов
- безопасные функции по умолчанию в трейтах
Функции, помеченные target_feature, не встраиваются в контекст, который не поддерживает данные функции. Атрибут #[inline(always)] не может использоваться с атрибутом target_feature.
Доступные функции
Ниже приведён список доступных имён функций.
x86 или x86_64
Выполнение кода с неподдерживаемыми функциями является неопределённым поведением на этой платформе.
Следовательно, на этой платформе использование функций #[target_feature] следует
вышеуказанным ограничениям.
| Функция | Неявно включает | Описание |
|---|---|---|
adx | ADX — Многоточные расширения инструкций сложения с переносом | |
aes | sse2 | AES — Стандарт шифрования Advanced Encryption Standard |
avx | sse4.2 | AVX — Расширения Advanced Vector Extensions |
avx2 | avx | AVX2 — Расширения Advanced Vector Extensions 2 |
avx512bf16 | avx512bw | AVX512-BF16 — Расширения Advanced Vector Extensions 512-bit - Bfloat16 Extensions |
avx512bitalg | avx512bw | AVX512-BITALG — Расширения Advanced Vector Extensions 512-bit - Bit Algorithms |
avx512bw | avx512f | AVX512-BW — Расширения Advanced Vector Extensions 512-bit - Byte and Word Instructions |
avx512cd | avx512f | AVX512-CD — Расширения Advanced Vector Extensions 512-bit - Conflict Detection Instructions |
avx512dq | avx512f | AVX512-DQ — Расширения Advanced Vector Extensions 512-bit - Doubleword and Quadword Instructions |
avx512f | avx2, fma, f16c | AVX512-F — Расширения Advanced Vector Extensions 512-bit - Foundation |
avx512fp16 | avx512bw | AVX512-FP16 — Расширения Advanced Vector Extensions 512-bit - Float16 Extensions |
avx512ifma | avx512f | AVX512-IFMA — Расширения Advanced Vector Extensions 512-bit - Integer Fused Multiply Add |
avx512vbmi | avx512bw | AVX512-VBMI — Расширения Advanced Vector Extensions 512-bit - Vector Byte Manipulation Instructions |
avx512vbmi2 | avx512bw | AVX512-VBMI2 — Расширения Advanced Vector Extensions 512-bit - Vector Byte Manipulation Instructions 2 |
avx512vl | avx512f | AVX512-VL — Расширения Advanced Vector Extensions 512-bit - Vector Length Extensions |
avx512vnni | avx512f | AVX512-VNNI — Расширения Advanced Vector Extensions 512-bit - Vector Neural Network Instructions |
avx512vp2intersect | avx512f | AVX512-VP2INTERSECT — Расширения Advanced Vector Extensions 512-bit - Vector Pair Intersection to a Pair of Mask Registers |
avx512vpopcntdq | avx512f | AVX512-VPOPCNTDQ — Расширения Advanced Vector Extensions 512-bit - Vector Population Count Instruction |
avxifma | avx2 | AVX-IFMA — Расширения Advanced Vector Extensions - Integer Fused Multiply Add |
avxneconvert | avx2 | AVX-NE-CONVERT — Расширения Advanced Vector Extensions - No-Exception Floating-Point conversion Instructions |
avxvnni | avx2 | AVX-VNNI — Расширения Advanced Vector Extensions - Vector Neural Network Instructions |
avxvnniint16 | avx2 | AVX-VNNI-INT16 — Расширения Advanced Vector Extensions - Vector Neural Network Instructions with 16-bit Integers |
avxvnniint8 | avx2 | AVX-VNNI-INT8 — Расширения Advanced Vector Extensions - Vector Neural Network Instructions with 8-bit Integers |
bmi1 | BMI1 — Наборы инструкций манипуляции битами | |
bmi2 | BMI2 — Наборы инструкций манипуляции битами 2 | |
cmpxchg16b | cmpxchg16b — Сравнивает и обменивает 16 байт (128 бит) данных атомарно | |
f16c | avx | F16C — Инструкции преобразования 16-битных чисел с плавающей запятой |
fma | avx | FMA3 — Трёхоперандное fused multiply-add |
fxsr | fxsave и fxrstor — Сохранить и восстановить состояние x87 FPU, MMX Technology и SSE | |
gfni | sse2 | GFNI — Новые инструкции поля Галуа |
kl | sse2 | KEYLOCKER — Инструкции Intel Key Locker |
lzcnt | lzcnt — Подсчёт ведущих нулей | |
movbe | movbe — Перемещение данных после обмена байтов | |
pclmulqdq | sse2 | pclmulqdq — Упакованное умножение без переноса quadword |
popcnt | popcnt — Подсчёт битов, установленных в 1 | |
rdrand | rdrand — Чтение случайного числа | |
rdseed | rdseed — Чтение случайного сида | |
sha | sse2 | SHA — Алгоритм безопасного хеширования |
sha512 | avx2 | SHA512 — Алгоритм безопасного хеширования с дайджестом 512 бит |
sm3 | avx | SM3 — Алгоритм хеширования ShangMi 3 |
sm4 | avx2 | SM4 — Алгоритм шифрования ShangMi 4 |
sse | SSE — Расширения Streaming SIMD | |
sse2 | sse | SSE2 — Расширения Streaming SIMD 2 |
sse3 | sse2 | SSE3 — Расширения Streaming SIMD 3 |
sse4.1 | ssse3 | SSE4.1 — Расширения Streaming SIMD 4.1 |
sse4.2 | sse4.1 | SSE4.2 — Расширения Streaming SIMD 4.2 |
sse4a | sse3 | SSE4a — Расширения Streaming SIMD 4a |
ssse3 | sse3 | SSSE3 — Дополнительные расширения Streaming SIMD 3 |
tbm | TBM — Манипуляция trailing битами | |
vaes | avx2, aes | VAES — Векторные инструкции AES |
vpclmulqdq | avx, pclmulqdq | VPCLMULQDQ — Векторное умножение без переноса Quadwords |
widekl | kl | KEYLOCKER_WIDE — Инструкции Intel Wide Keylocker |
xsave | xsave — Сохранение расширенных состояний процессора | |
xsavec | xsavec — Сохранение расширенных состояний процессора с уплотнением | |
xsaveopt | xsaveopt — Оптимизированное сохранение расширенных состояний процессора | |
xsaves | xsaves — Сохранение расширенных состояний процессора supervisor |
aarch64
На этой платформе использование функций #[target_feature] следует
вышеуказанным ограничениям.
Дополнительная документация по этим функциям может быть найдена в ARM Architecture Reference Manual или elsewhere на developer.arm.com.
Note
Следующие пары функций должны быть помечены как включённые или отключённые вместе, если используются:
pacaиpacg, которые LLVM в настоящее время реализует как одну функцию.
| Функция | Неявно включает | Имя функции |
|---|---|---|
aes | neon | FEAT_AES & FEAT_PMULL — Расширения Advanced SIMD AES & PMULL instructions |
bf16 | FEAT_BF16 — Инструкции BFloat16 | |
bti | FEAT_BTI — Идентификация цели ветвления | |
crc | FEAT_CRC — Инструкции контрольной суммы CRC32 | |
dit | FEAT_DIT — Инструкции с независимым от данных временем | |
dotprod | neon | FEAT_DotProd — Расширения Advanced SIMD Int8 dot product instructions |
dpb | FEAT_DPB — Очистка кэша данных до точки сохранения | |
dpb2 | dpb | FEAT_DPB2 — Очистка кэша данных до точки глубокого сохранения |
f32mm | sve | FEAT_F32MM — Инструкция умножения матриц с плавающей запятой одинарной точности SVE |
f64mm | sve | FEAT_F64MM — Инструкция умножения матриц с плавающей запятой двойной точности SVE |
fcma | neon | FEAT_FCMA — Поддержка комплексных чисел с плавающей запятой |
fhm | fp16 | FEAT_FHM — Инструкции Half-precision FP FMLAL |
flagm | FEAT_FLAGM — Манипуляция условными флагами | |
fp16 | neon | FEAT_FP16 — Обработка данных с плавающей запятой половинной точности |
frintts | FEAT_FRINTTS — Вспомогательные инструкции преобразования плавающей запятой в int | |
i8mm | FEAT_I8MM — Умножение матриц Int8 | |
jsconv | neon | FEAT_JSCVT — Инструкция преобразования JavaScript |
lor | FEAT_LOR — Расширение ограниченных регионов упорядочения | |
lse | FEAT_LSE — Расширения больших систем | |
mte | FEAT_MTE & FEAT_MTE2 — Расширение тегирования памяти | |
neon | FEAT_AdvSimd & FEAT_FP — Расширения Floating Point и Advanced SIMD | |
paca | FEAT_PAUTH — Аутентификация указателей (аутентификация адреса) | |
pacg | FEAT_PAUTH — Аутентификация указателей (общая аутентификация) | |
pan | FEAT_PAN — Расширение Privileged Access-Never | |
pmuv3 | FEAT_PMUv3 — Расширения мониторов производительности (v3) | |
rand | FEAT_RNG — Генератор случайных чисел | |
ras | FEAT_RAS & FEAT_RASv1p1 — Расширение надёжности, доступности и обслуживаемости | |
rcpc | FEAT_LRCPC — Release consistent Processor Consistent | |
rcpc2 | rcpc | FEAT_LRCPC2 — RcPc с непосредственными смещениями |
rdm | neon | FEAT_RDM — Округление двойного умножения с накоплением |
sb | FEAT_SB — Барьер спекуляции | |
sha2 | neon | FEAT_SHA1 & FEAT_SHA256 — Расширения Advanced SIMD SHA instructions |
sha3 | sha2 | FEAT_SHA512 & FEAT_SHA3 — Расширения Advanced SIMD SHA instructions |
sm4 | neon | FEAT_SM3 & FEAT_SM4 — Расширения Advanced SIMD SM3/4 instructions |
spe | FEAT_SPE — Расширение статистического профилирования | |
ssbs | FEAT_SSBS & FEAT_SSBS2 — Speculative Store Bypass Safe | |
sve | neon | FEAT_SVE — Масштабируемое векторное расширение |
sve2 | sve | FEAT_SVE2 — Масштабируемое векторное расширение 2 |
sve2-aes | sve2, aes | FEAT_SVE_AES & FEAT_SVE_PMULL128 — Инструкции SVE AES |
sve2-bitperm | sve2 | FEAT_SVE2_BitPerm — SVE Bit Permute |
sve2-sha3 | sve2, sha3 | FEAT_SVE2_SHA3 — Инструкции SVE SHA3 |
sve2-sm4 | sve2, sm4 | FEAT_SVE2_SM4 — Инструкции SVE SM4 |
tme | FEAT_TME — Расширение транзакционной памяти | |
vh | FEAT_VHE — Расширения виртуализации хоста |
loongarch
На этой платформе использование функций #[target_feature] следует
вышеуказанным ограничениям.
| Функция | Неявно включает | Описание |
|---|---|---|
f | F — Инструкции чисел с плавающей запятой одинарной точности | |
d | f | D — Инструкции чисел с плавающей запятой двойной точности |
frecipe | FRECIPE — Инструкции аппроксимации обратной величины | |
lasx | lsx | LASX — 256-битные векторные инструкции |
lbt | LBT — Инструкции бинарной трансляции | |
lsx | d | LSX — 128-битные векторные инструкции |
lvz | LVZ — Инструкции виртуализации |
riscv32 или riscv64
На этой платформе использование функций #[target_feature] следует
вышеуказанным ограничениям.
Дополнительная документация по этим функциям может быть найдена в их соответствующих спецификациях. Многие спецификации описаны в RISC-V ISA Manual, version 20250508, или в другом руководстве, размещённом на RISC-V GitHub Account.
| Функция | Неявно включает | Описание |
|---|---|---|
a | A — Атомарные инструкции | |
c | C — Сжатые инструкции | |
m | M — Инструкции целочисленного умножения и деления | |
zba | Zba — Инструкции генерации адресов | |
zbb | Zbb — Базовая манипуляция битами | |
zbc | zbkc | Zbc — Умножение без переноса |
zbkb | Zbkb — Инструкции манипуляции битами для криптографии | |
zbkc | Zbkc — Умножение без переноса для криптографии | |
zbkx | Zbkx — Перестановки crossbar | |
zbs | Zbs — Инструкции работы с одним битом | |
zk | zkn, zkr, zks, zkt, zbkb, zbkc, zkbx | Zk — Скалярная криптография |
zkn | zknd, zkne, zknh, zbkb, zbkc, zkbx | Zkn — Расширение набора алгоритмов NIST |
zknd | Zknd — Набор NIST: Дешифрование AES | |
zkne | Zkne — Набор NIST: Шифрование AES | |
zknh | Zknh — Набор NIST: Инструкции хеш-функций | |
zkr | Zkr — Расширение источника энтропии | |
zks | zksed, zksh, zbkb, zbkc, zkbx | Zks — Набор алгоритмов ShangMi |
zksed | Zksed — Набор ShangMi: Инструкции блочного шифра SM4 | |
zksh | Zksh — Набор ShangMi: Инструкции хеш-функции SM3 | |
zkt | Zkt — Подмножество с независимой от данных задержкой выполнения |
wasm32 или wasm64
Безопасные функции #[target_feature] могут всегда использоваться в безопасных контекстах на платформах Wasm.
Невозможно вызвать неопределённое поведение через атрибут
#[target_feature], потому что попытка использовать инструкции,
не поддерживаемые движком Wasm, завершится ошибкой во время загрузки без риска быть
интерпретированной способом, отличным от ожидаемого компилятором.
| Функция | Неявно включает | Описание |
|---|---|---|
bulk-memory | Предложение операций с массовой памятью WebAssembly | |
extended-const | Предложение расширенных константных выражений WebAssembly | |
mutable-globals | Предложение изменяемых глобальных переменных WebAssembly | |
nontrapping-fptoint | Предложение нетrapping-преобразования float-to-int WebAssembly | |
relaxed-simd | simd128 | Предложение relaxed simd WebAssembly |
sign-ext | Предложение операторов расширения знака WebAssembly | |
simd128 | Предложение simd WebAssembly | |
multivalue | Предложение multivalue WebAssembly | |
reference-types | Предложение reference-types WebAssembly | |
tail-call | Предложение tail-call WebAssembly |
Дополнительная информация
См. опцию условной компиляции [target_feature] для выборочного
включения или отключения компиляции кода на основе настроек времени компиляции. Обратите внимание,
что эта опция не зависит от атрибута target_feature и
управляется только функциями, включёнными для всего крейта.
См. макросы is_x86_feature_detected или is_aarch64_feature_detected
в стандартной библиотеке для обнаружения функций во время выполнения на этих платформах.
Note
rustcимеет набор функций, включённых по умолчанию для каждой цели и CPU. CPU может быть выбран с помощью флага-C target-cpu. Отдельные функции могут быть включены или отключены для всего крейта с помощью флага-C target-feature.
Атрибут track_caller
Атрибут track_caller может применяться к любой функции с ABI "Rust"
за исключением точки входа fn main.
При применении к функциям и методам в объявлениях трейтов атрибут применяется ко всем реализациям. Если трейт предоставляет реализацию по умолчанию с атрибутом, то атрибут также применяется к переопределяющим реализациям.
При применении к функции в блоке extern атрибут также должен быть применён к любым связанным
реализациям, иначе результат — неопределённое поведение. При применении к функции, которая становится
доступной для блока extern, объявление в блоке extern также должно иметь атрибут,
иначе результат — неопределённое поведение.
Поведение
Применение атрибута к функции f позволяет коду внутри f получить подсказку о Location
“самого верхнего” отслеживаемого вызова, который привёл к вызову f. В момент наблюдения реализация
ведёт себя так, как если бы она поднималась по стеку от фрейма f, чтобы найти ближайший фрейм
без атрибута функции outer, и возвращает Location отслеживаемого вызова в outer.
#![allow(unused)] fn main() { #[track_caller] fn f() { println!("{}", std::panic::Location::caller()); } }
Note
coreпредоставляетcore::panic::Location::callerдля наблюдения за местоположениями вызывающих. Он оборачивает внутреннюю функциюcore::intrinsics::caller_location, реализованнуюrustc.
Note
Поскольку результирующий
Locationявляется подсказкой, реализация может остановить подъём по стеку раньше. См. Ограничения для важных предостережений.
Примеры
Когда f вызывается напрямую calls_f, код в f наблюдает место вызова внутри calls_f:
#![allow(unused)] fn main() { #[track_caller] fn f() { println!("{}", std::panic::Location::caller()); } fn calls_f() { f(); // <-- f() печатает это местоположение } }
Когда f вызывается другой атрибутированной функцией g, которая в свою очередь вызывается calls_g, код в
обоих f и g наблюдает место вызова g внутри calls_g:
#![allow(unused)] fn main() { #[track_caller] fn f() { println!("{}", std::panic::Location::caller()); } #[track_caller] fn g() { println!("{}", std::panic::Location::caller()); f(); } fn calls_g() { g(); // <-- g() печатает это местоположение дважды, один раз сама и один раз из f() } }
Когда g вызывается другой атрибутированной функцией h, которая в свою очередь вызывается calls_h, весь код
в f, g и h наблюдает место вызова h внутри calls_h:
#![allow(unused)] fn main() { #[track_caller] fn f() { println!("{}", std::panic::Location::caller()); } #[track_caller] fn g() { println!("{}", std::panic::Location::caller()); f(); } #[track_caller] fn h() { println!("{}", std::panic::Location::caller()); g(); } fn calls_h() { h(); // <-- печатает это местоположение трижды, один раз сама, один раз из g(), один раз из f() } }
И так далее.
Ограничения
Эта информация является подсказкой, и реализации не обязаны её сохранять.
В частности, приведение функции с #[track_caller] к указателю на функцию создаёт обёртку (shim), которая
для наблюдателей появляется так, как если бы она была вызвана в месте определения атрибутированной функции, теряя фактическую
информацию о вызывающем при виртуальных вызовах. Распространённым примером этого приведения является создание
объекта трейта, методы которого атрибутированы.
Note
Упомянутая выше обёртка для указателей на функции необходима, потому что
rustcреализуетtrack_callerв контексте кодогенерации, добавляя неявный параметр к ABI функции, но это было бы небезопасно для косвенного вызова, потому что параметр не является частью типа функции, и данный тип указателя на функцию может ссылаться или не ссылаться на функцию с атрибутом. Создание обёртки скрывает неявный параметр от вызывающих указатель на функцию, сохраняя безопасность.
Атрибут instruction_set
Атрибут instruction_set указывает набор инструкций, который функция будет использовать во время генерации кода. Это позволяет смешивать более одного набора инструкций в одной программе.
Example
#[instruction_set(arm::a32)] fn arm_code() {} #[instruction_set(arm::t32)] fn thumb_code() {}
Атрибут instruction_set использует синтаксис MetaListPaths для указания единственного пути, состоящего из имени семейства архитектуры и имени набора инструкций.
Атрибут instruction_set может применяться только к функциям с телами — замыканиям, async блокам, свободным функциям, ассоциированным функциям в неявной реализации или реализации трейта, и ассоциированным функциям в определении трейта, когда эти функции имеют определение по умолчанию.
Note
rustcигнорирует использование в других позициях, но выдает предупреждение. В будущем это может стать ошибкой.
Note
Хотя атрибут может применяться к замыканиям и async блокам, полезность этого ограничена, так как мы ещё не поддерживаем атрибуты на выражениях.
Атрибут instruction_set может использоваться только один раз на функции.
Атрибут instruction_set может использоваться только с целью, которая поддерживает данное значение.
Когда используется атрибут instruction_set, любая встроенная ассемблерная вставка в функции должна использовать указанный набор инструкций вместо целевого по умолчанию.
instruction_set на ARM
При нацеливании на архитектуры ARMv4T и ARMv5te поддерживаемые значения для instruction_set:
arm::a32— Генерировать функцию как код A32 “ARM”.arm::t32— Генерировать функцию как код T32 “Thumb”.
Если адрес функции берётся как указатель на функцию, младший бит адреса будет зависеть от выбранного набора инструкций:
- Для
arm::a32(“ARM”) он будет 0. - Для
arm::t32(“Thumb”) он будет 1.
Ограничения
Следующие атрибуты влияют на ограничения времени компиляции.
Атрибут recursion_limit
Атрибут recursion_limit может применяться на уровне крейта для установки
максимальной глубины для потенциально бесконечно рекурсивных операций времени компиляции,
таких как раскрытие макросов или авто-разыменование.
Он использует синтаксис MetaNameValueStr для указания глубины рекурсии.
Note
Значение по умолчанию в
rustcравно 128.
#![allow(unused)] #![recursion_limit = "4"] fn main() { macro_rules! a { () => { a!(1); }; (1) => { a!(2); }; (2) => { a!(3); }; (3) => { a!(4); }; (4) => { }; } // Это не удаётся раскрыть, потому что требуется глубина рекурсии больше 4. a!{} }
#![allow(unused)] #![recursion_limit = "1"] fn main() { // Это не удаётся, потому что требуется два рекурсивных шага для авто-разыменования. (|_: &u8| {})(&&&1); }
Атрибут type_length_limit
Атрибут type_length_limit устанавливает максимальное количество подстановок типов, разрешённых при построении конкретного типа во время мономорфизации.
Note
rustcприменяет это ограничение только тогда, когда активен ночной флаг-Zenforce-type-length-limit.Для получения дополнительной информации см. Rust PR #127670.
Example
#![type_length_limit = "4"] fn f<T>(x: T) {} // Это не компилируется, потому что мономорфизация до // `f::<((((i32,), i32), i32), i32)>` требует более // 4 элементов типа. f(((((1,), 2), 3), 4));
Note
Значение по умолчанию в
rustcравно1048576.
Атрибут type_length_limit использует синтаксис MetaNameValueStr. Значение в строке должно быть неотрицательным числом.
Атрибут type_length_limit может применяться только к корню крейта.
Note
rustcигнорирует использование в других позициях, но выдает предупреждение. В будущем это может стать ошибкой.
Только первое использование type_length_limit на элементе имеет эффект.
Note
rustcвыдает предупреждение на любое использование после первого. В будущем это может стать ошибкой.
Атрибуты системы типов
Следующие атрибуты используются для изменения того, как тип может использоваться.
Атрибут non_exhaustive
Атрибут non_exhaustive указывает, что тип или вариант перечисления может иметь
дополнительные поля или варианты, добавленные в будущем.
Он может применяться к структурам, перечислениям и вариантам перечислений.
Атрибут non_exhaustive использует синтаксис MetaWord и, следовательно, не
принимает никаких входных данных.
Внутри определяющего крейта non_exhaustive не имеет эффекта.
#![allow(unused)] fn main() { #[non_exhaustive] pub struct Config { pub window_width: u16, pub window_height: u16, } #[non_exhaustive] pub struct Token; #[non_exhaustive] pub struct Id(pub u64); #[non_exhaustive] pub enum Error { Message(String), Other, } pub enum Message { #[non_exhaustive] Send { from: u32, to: u32, contents: String }, #[non_exhaustive] Reaction(u32), #[non_exhaustive] Quit, } // Неисчерпывающие структуры могут быть созданы как обычно внутри определяющего крейта. let config = Config { window_width: 640, window_height: 480 }; let token = Token; let id = Id(4); // Неисчерпывающие структуры могут быть сопоставлены исчерпывающе внутри определяющего крейта. let Config { window_width, window_height } = config; let Token = token; let Id(id_number) = id; let error = Error::Other; let message = Message::Reaction(3); // Неисчерпывающие перечисления могут быть сопоставлены исчерпывающе внутри определяющего крейта. match error { Error::Message(ref s) => { }, Error::Other => { }, } match message { // Неисчерпывающие варианты могут быть сопоставлены исчерпывающе внутри определяющего крейта. Message::Send { from, to, contents } => { }, Message::Reaction(id) => { }, Message::Quit => { }, } }
За пределами определяющего крейта типы, аннотированные non_exhaustive, имеют ограничения, которые
сохраняют обратную совместимость при добавлении новых полей или вариантов.
Неисчерпывающие типы не могут быть созданы за пределами определяющего крейта:
- Неисчерпывающие варианты (
структурыиливарианты перечислений) не могут быть созданы с помощью StructExpression (включая синтаксис функционального обновления). - Неявно определённая константа с тем же именем для unit-подобной структуры
или функция-конструктор с тем же именем для кортежной структуры
имеет видимость не больше
pub(crate). То есть, если видимость структурыpub, то видимость константы или конструктора равнаpub(crate), в противном случае видимость двух элементов одинакова (как и в случае без#[non_exhaustive]). - Экземпляры
перечислениймогут быть созданы.
Следующие примеры создания не компилируются за пределами определяющего крейта:
// Это типы, определённые в вышестоящем крейте, которые были аннотированы как
// `#[non_exhaustive]`.
use upstream::{Config, Token, Id, Error, Message};
// Нельзя создать экземпляр `Config`; если бы новые поля были добавлены в
// новой версии `upstream`, то это не скомпилировалось бы, поэтому
// это запрещено.
let config = Config { window_width: 640, window_height: 480 };
// Нельзя создать экземпляр `Token`; если бы новые поля были добавлены, то
// это больше не было бы unit-подобной структурой, поэтому константа с тем же именем,
// созданная из-за того, что это unit-подобная структура, не является публичной вне крейта;
// этот код не компилируется.
let token = Token;
// Нельзя создать экземпляр `Id`; если бы новые поля были добавлены, то
// сигнатура его функции-конструктора изменилась бы, поэтому его функция-конструктор
// не является публичной вне крейта; этот код не компилируется.
let id = Id(5);
// Можно создать экземпляр `Error`; введение новых вариантов не
// приведёт к тому, что это не скомпилируется.
let error = Error::Message("foo".to_string());
// Нельзя создать экземпляр `Message::Send` или `Message::Reaction`;
// если бы новые поля были добавлены в новой версии `upstream`, то это
// не скомпилировалось бы, поэтому это запрещено.
let message = Message::Send { from: 0, to: 1, contents: "foo".to_string(), };
let message = Message::Reaction(0);
// Нельзя создать экземпляр `Message::Quit`; если бы это было преобразовано в
// кортежный вариант перечисления в `upstream`, это не скомпилировалось бы.
let message = Message::Quit;
Существуют ограничения при сопоставлении с неисчерпывающими типами за пределами определяющего крейта:
- При сопоставлении с образцом неисчерпывающего варианта (
структурыиливарианта перечисления) должен использоваться StructPattern, который должен включать... Видимость конструктора кортежного варианта перечисления уменьшается до не более чемpub(crate). - При сопоставлении с образцом неисчерпывающего
перечисления, сопоставление с вариантом не учитывается при исчерпываемости ветвей. Следующие примеры сопоставления не компилируются за пределами определяющего крейта:
// Это типы, определённые в вышестоящем крейте, которые были аннотированы как
// `#[non_exhaustive]`.
use upstream::{Config, Token, Id, Error, Message};
// Нельзя сопоставить неисчерпывающее перечисление без включения подстановочной ветки.
match error {
Error::Message(ref s) => { },
Error::Other => { },
// скомпилировалось бы с: `_ => {},`
}
// Нельзя сопоставить неисчерпывающую структуру без подстановочного знака.
if let Ok(Config { window_width, window_height }) = config {
// скомпилировалось бы с: `..`
}
// Нельзя сопоставить неисчерпывающую unit-подобную или кортежную структуру, кроме как с использованием
// синтаксиса структуры с фигурными скобками с подстановочным знаком.
// Это скомпилировалось бы как `let Token { .. } = token;`
let Token = token;
// Это скомпилировалось бы как `let Id { 0: id_number, .. } = id;`
let Id(id_number) = id;
match message {
// Нельзя сопоставить неисчерпывающий вариант перечисления-структуры без включения подстановочного знака.
Message::Send { from, to, contents } => { },
// Нельзя сопоставить неисчерпывающий кортежный или unit вариант перечисления.
Message::Reaction(type) => { },
Message::Quit => { },
}
Также не разрешается использовать числовые приведения (as) для перечислений, которые содержат любые неисчерпывающие варианты.
Например, следующее перечисление может быть приведено, потому что оно не содержит неисчерпывающих вариантов:
#![allow(unused)] fn main() { #[non_exhaustive] pub enum Example { First, Second } }
Однако, если перечисление содержит хотя бы один неисчерпывающий вариант, приведение приведёт к ошибке. Рассмотрим эту изменённую версию того же перечисления:
#![allow(unused)] fn main() { #[non_exhaustive] pub enum EnumWithNonExhaustiveVariants { First, #[non_exhaustive] Second } }
use othercrate::EnumWithNonExhaustiveVariants;
// Ошибка: нельзя привести перечисление с неисчерпывающим вариантом, когда оно определено в другом крейте
let _ = EnumWithNonExhaustiveVariants::First as u8;
Неисчерпывающие типы всегда считаются населёнными (inhabited) в нижестоящих крейтах.
Атрибуты отладчика
Следующие атрибуты используются для улучшения опыта отладки при использовании сторонних отладчиков, таких как GDB или WinDbg.
Атрибут debugger_visualizer
Атрибут debugger_visualizer может использоваться для встраивания файла визуализатора отладчика в отладочную информацию. Это улучшает опыт отладки при отображении значений.
Example
#![debugger_visualizer(natvis_file = "Example.natvis")] #![debugger_visualizer(gdb_script_file = "example.py")]
Атрибут debugger_visualizer использует синтаксис MetaListNameValueStr для указания своих входных данных. Должен быть указан один из следующих ключей:
Атрибут debugger_visualizer может применяться только к модулю или к корню крейта.
Атрибут debugger_visualizer может использоваться любое количество раз на форме. Все указанные файлы визуализаторов будут загружены.
Использование debugger_visualizer с Natvis
Natvis — это основанный на XML фреймворк для отладчиков Microsoft (таких как Visual Studio и WinDbg), который использует декларативные правила для настройки отображения типов. Для подробной информации о формате Natvis обратитесь к документации Natvis от Microsoft.
Этот атрибут поддерживает встраивание файлов Natvis только для целей -windows-msvc.
Путь к файлу Natvis указывается с помощью ключа natvis_file, который является путём относительно исходного файла.
Example
#![debugger_visualizer(natvis_file = "Rectangle.natvis")] struct FancyRect { x: f32, y: f32, dx: f32, dy: f32, } fn main() { let fancy_rect = FancyRect { x: 10.0, y: 10.0, dx: 5.0, dy: 5.0 }; println!("set breakpoint here"); }
Rectangle.natvisсодержит:<?xml version="1.0" encoding="utf-8"?> <AutoVisualizer xmlns="http://schemas.microsoft.com/vstudio/debugger/natvis/2010"> <Type Name="foo::FancyRect"> <DisplayString>({x},{y}) + ({dx}, {dy})</DisplayString> <Expand> <Synthetic Name="LowerLeft"> <DisplayString>({x}, {y})</DisplayString> </Synthetic> <Synthetic Name="UpperLeft"> <DisplayString>({x}, {y + dy})</DisplayString> </Synthetic> <Synthetic Name="UpperRight"> <DisplayString>({x + dx}, {y + dy})</DisplayString> </Synthetic> <Synthetic Name="LowerRight"> <DisplayString>({x + dx}, {y})</DisplayString> </Synthetic> </Expand> </Type> </AutoVisualizer>При просмотре в WinDbg переменная
fancy_rectбудет показана следующим образом:> Variables: > fancy_rect: (10.0, 10.0) + (5.0, 5.0) > LowerLeft: (10.0, 10.0) > UpperLeft: (10.0, 15.0) > UpperRight: (15.0, 15.0) > LowerRight: (15.0, 10.0)
Использование debugger_visualizer с GDB
GDB поддерживает использование структурированного скрипта Python, называемого pretty printer (красивым принтером), который описывает, как тип должен визуализироваться в представлении отладчика. Для подробной информации о pretty printers обратитесь к документации по pretty printing от GDB.
Note
Встроенные pretty printers не загружаются автоматически при отладке бинарного файла под GDB.
Есть два способа включить автозагрузку встроенных pretty printers:
- Запустить GDB с дополнительными аргументами для явного добавления директории или бинарного файла в безопасный путь автозагрузки:
gdb -iex "add-auto-load-safe-path safe-path path/to/binary" path/to/binaryДля получения дополнительной информации см. документацию по автозагрузке GDB.- Создать файл с именем
gdbinitв$HOME/.config/gdb(возможно, вам потребуется создать директорию, если она ещё не существует). Добавьте следующую строку в этот файл:add-auto-load-safe-path path/to/binary.
Эти скрипты встраиваются с помощью ключа gdb_script_file, который является путём относительно исходного файла.
Example
#![debugger_visualizer(gdb_script_file = "printer.py")] struct Person { name: String, age: i32, } fn main() { let bob = Person { name: String::from("Bob"), age: 10 }; println!("set breakpoint here"); }
printer.pyсодержит:import gdb class PersonPrinter: "Print a Person" def __init__(self, val): self.val = val self.name = val["name"] self.age = int(val["age"]) def to_string(self): return "{} is {} years old.".format(self.name, self.age) def lookup(val): lookup_tag = val.type.tag if lookup_tag is None: return None if "foo::Person" == lookup_tag: return PersonPrinter(val) return None gdb.current_objfile().pretty_printers.append(lookup)Когда отладочный исполняемый файл крейта передаётся в GDB1,
print bobотобразит:"Bob" is 10 years old.
Атрибут collapse_debuginfo
Атрибут collapse_debuginfo управляет тем, сворачиваются ли местоположения кода из определения макроса в единственное местоположение, связанное с местом вызова макроса, при генерации отладочной информации для кода, вызывающего этот макрос.
Example
#![allow(unused)] fn main() { #[collapse_debuginfo(yes)] macro_rules! example { () => { println!("hello!"); }; } }При использовании отладчика вызов макроса
exampleможет выглядеть так, как будто он вызывает функцию. То есть, когда вы переходите к месту вызова, оно может показывать вызов макроса, а не раскрытый код.
Синтаксис атрибута collapse_debuginfo:
Syntax
CollapseDebuginfoAttribute → collapse_debuginfo ( CollapseDebuginfoOption )
CollapseDebuginfoOption →
yes
| no
| external
Атрибут collapse_debuginfo может применяться только к определению macro_rules.
Атрибут collapse_debuginfo может использоваться только один раз на макросе.
Атрибут collapse_debuginfo принимает следующие опции:
#[collapse_debuginfo(yes)]— Местоположения кода в отладочной информации сворачиваются.#[collapse_debuginfo(no)]— Местоположения кода в отладочной информации не сворачиваются.#[collapse_debuginfo(external)]— Местоположения кода в отладочной информации сворачиваются только если макрос происходит из другого крейта.
Поведение external является значением по умолчанию для макросов, которые не имеют этого атрибута, за исключением встроенных макросов. Для встроенных макросов значением по умолчанию является yes.
Note
rustcимеет опцию CLI-C collapse-macro-debuginfoдля переопределения как поведения по умолчанию, так и значений любых атрибутов#[collapse_debuginfo].
-
Примечание: Это предполагает, что вы используете скрипт
rust-gdb, который настраивает pretty-printers для типов стандартной библиотеки, таких какString. ↩
Операторы и выражения
Rust — это в первую очередь язык выражений. Это означает, что большинство форм оценивания, порождающих значения или вызывающих эффекты, управляются единой синтаксической категорией выражений. Каждый вид выражений обычно может вкладываться в любой другой вид выражений, а правила вычисления выражений включают спецификацию как значения, порождаемого выражением, так и порядка, в котором его подвыражения сами вычисляются.
В противоположность этому, операторы служат главным образом для содержания и явного упорядочивания вычисления выражений.
Операторы
Syntax
Statement →
;
| Item
| LetStatement
| ExpressionStatement
| OuterAttribute* MacroInvocationSemi
Оператор — это компонент блока, который в свою очередь является компонентом внешнего выражения или функции.
Rust имеет два вида операторов: операторы объявления и операторы выражений.
Операторы объявления
Оператор объявления — это оператор, который вводит одно или несколько имён в охватывающий блок операторов. Объявленные имена могут обозначать новые переменные или новые элементы.
Два вида операторов объявления — это объявления элементов и операторы let.
Объявления элементов
Оператор объявления элемента имеет синтаксическую форму, идентичную объявлению элемента внутри модуля.
Объявление элемента внутри блока операторов ограничивает его область видимости блоком, содержащим оператор. Элементу не даётся канонический путь, как и любым подэлементам, которые он может объявлять.
Исключением является то, что ассоциированные элементы, определённые реализациями, всё ещё доступны во внешних областях видимости, пока элемент и, если применимо, трейт доступны. В остальном это идентично по смыслу объявлению элемента внутри модуля.
Не происходит неявного захвата обобщённых параметров, параметров и локальных переменных содержащей функции.
Например, inner не может получить доступ к outer_var.
#![allow(unused)] fn main() { fn outer() { let outer_var = true; fn inner() { /* outer_var не в области видимости здесь */ } inner(); } }
Операторы let
Syntax
LetStatement →
OuterAttribute* let PatternNoTopAlt ( : Type )?
(
= Expression
| = Expressionexcept LazyBooleanExpression or end with a } else BlockExpression
)? ;
Оператор let вводит новый набор переменных, заданный паттерном.
Паттерн следует, опционально, за аннотацией типа, а затем либо заканчивается, либо следует за выражением инициализации плюс опциональный блок else.
Когда аннотация типа не дана, компилятор выведет тип или сообщит об ошибке, если недостаточно информации о типе для определённого вывода.
Любые переменные, введённые объявлением переменной, видны с точки объявления до конца охватывающей области видимости блока, за исключением случаев, когда они затенены другим объявлением переменной.
Если блок else отсутствует, паттерн должен быть неопровержимым.
Если блок else присутствует, паттерн может быть опровержимым.
Если паттерн не совпадает (это требует, чтобы он был опровержимым), выполняется блок else.
Блок else всегда должен расходиться (вычисляться в тип never).
#![allow(unused)] fn main() { let (mut v, w) = (vec![1, 2, 3], 42); // Привязки могут быть mut или const let Some(t) = v.pop() else { // Опровержимые паттерны требуют блока else panic!(); // Блок else должен расходиться }; let [u, v] = [v[0], v[1]] else { // Этот паттерн неопровержим, поэтому компилятор // предупредит, что блок else избыточен. panic!(); }; }
Операторы выражений
Syntax
ExpressionStatement →
ExpressionWithoutBlock ;
| ExpressionWithBlock ;?
Оператор выражения — это оператор, который вычисляет выражение и игнорирует его результат. Как правило, цель оператора выражения — вызвать эффекты вычисления его выражения.
Выражение, состоящее только из блочного выражения или выражения управления потоком, если используется в контексте, где разрешён оператор, может опускать завершающую точку с запятой. Это может вызвать неоднозначность между его разбором как самостоятельного оператора и как части другого выражения; в этом случае он разбирается как оператор.
Тип выражений ExpressionWithBlock при использовании в качестве операторов должен быть типом unit.
#![allow(unused)] fn main() { let mut v = vec![1, 2, 3]; v.pop(); // Игнорировать элемент, возвращённый из pop if v.is_empty() { v.push(5); } else { v.remove(0); } // Точка с запятой может быть опущена. [1]; // Отдельный оператор выражения, не индексное выражение. }
Когда завершающая точка с запятой опущена, результат должен быть типа ().
#![allow(unused)] fn main() { // плохо: тип блока i32, не () // Ошибка: ожидался `()` из-за типа возврата по умолчанию // if true { // 1 // } // хорошо: тип блока i32 if true { 1 } else { 2 }; }
Атрибуты на операторах
Операторы принимают внешние атрибуты.
Атрибуты, которые имеют смысл на операторе, — это cfg и атрибуты проверки линтов.
Выражения
Syntax
Expression →
ExpressionWithoutBlock
| ExpressionWithBlock
ExpressionWithoutBlock →
OuterAttribute*
(
LiteralExpression
| PathExpression
| OperatorExpression
| GroupedExpression
| ArrayExpression
| AwaitExpression
| IndexExpression
| TupleExpression
| TupleIndexingExpression
| StructExpression
| CallExpression
| MethodCallExpression
| FieldExpression
| ClosureExpression
| AsyncBlockExpression
| ContinueExpression
| BreakExpression
| RangeExpression
| ReturnExpression
| UnderscoreExpression
| MacroInvocation
)
ExpressionWithBlock →
OuterAttribute*
(
BlockExpression
| ConstBlockExpression
| UnsafeBlockExpression
| LoopExpression
| IfExpression
| MatchExpression
)
Выражение может иметь две роли: оно всегда производит значение и может иметь эффекты (также известные как “побочные эффекты”).
Выражение вычисляется в значение и имеет эффекты во время вычисления.
Многие выражения содержат подвыражения, называемые операндами выражения.
Значение каждого вида выражения диктует несколько вещей:
- Вычислять ли операнды при вычислении выражения
- Порядок вычисления операндов
- Как комбинировать значения операндов для получения значения выражения
Таким образом, структура выражений диктует структуру выполнения. Блоки — это просто другой вид выражений, поэтому блоки, операторы, выражения и снова блоки могут рекурсивно вкладываться друг в друга на произвольную глубину.
Note
Мы даём имена операндам выражений, чтобы мы могли обсуждать их, но эти имена не стабильны и могут быть изменены.
Приоритет выражений
Приоритет операторов и выражений Rust упорядочен следующим образом, от сильного к слабому. Бинарные операторы на одном уровне приоритета группируются в порядке, заданном их ассоциативностью.
| Оператор/Выражение | Ассоциативность |
|---|---|
| Пути | |
| Вызовы методов | |
| Выражения полей | слева направо |
| Вызовы функций, индексация массивов | |
? | |
Унарные - ! * заимствование | |
as | слева направо |
* / % | слева направо |
+ - | слева направо |
<< >> | слева направо |
& | слева направо |
^ | слева направо |
| | слева направо |
== != < > <= >= | Требуют скобок |
&& | слева направо |
|| | слева направо |
.. ..= | Требуют скобок |
= += -= *= /= %= &= |= ^= <<= >>= | справа налево |
return break замыкания |
Порядок вычисления операндов
Следующий список выражений вычисляет свои операнды одинаковым образом, как описано после списка. Другие выражения либо не принимают операнды, либо вычисляют их условно, как описано на соответствующих страницах.
- Выражение разыменования
- Выражение распространения ошибки
- Выражение отрицания
- Арифметические и логические бинарные операторы
- Операторы сравнения
- Выражение приведения типа
- Группированное выражение
- Выражение массива
- Выражение await
- Выражение индекса
- Выражение кортежа
- Выражение индекса кортежа
- Выражение структуры
- Выражение вызова
- Выражение вызова метода
- Выражение поля
- Выражение break
- Выражение диапазона
- Выражение return
Операнды этих выражений вычисляются до применения эффектов выражения. Выражения, принимающие несколько операндов, вычисляются слева направо, как написано в исходном коде.
Note
Какие подвыражения являются операндами выражения, определяется приоритетом выражений согласно предыдущему разделу.
Например, два вызова метода next всегда будут вызываться в одном и том же порядке:
#![allow(unused)] fn main() { // Использование vec вместо массива, чтобы избежать ссылок // поскольку на момент написания этого примера не было // стабильного итератора по владеемому массиву. let mut one_two = vec![1, 2].into_iter(); assert_eq!( (1, 2), (one_two.next().unwrap(), one_two.next().unwrap()) ); }
Note
Поскольку это применяется рекурсивно, эти выражения также вычисляются от самого внутреннего к самому внешнему, игнорируя соседние элементы, пока не останется внутренних подвыражений.
Выражения мест и выражения значений
Выражения делятся на две основные категории: выражения мест и выражения значений; есть также третья, второстепенная категория выражений, называемая выражениями присваивания. Внутри каждого выражения операнды могут аналогично встречаться либо в контексте места, либо в контексте значения. Вычисление выражения зависит как от его собственной категории, так и от контекста, в котором оно встречается.
Выражение места — это выражение, которое представляет область памяти.
Эти выражения — это пути, которые ссылаются на локальные переменные, статические переменные, разыменования (*expr), выражения индексации массивов (expr[expr]), ссылки на поля (expr.f) и выражения мест в скобках.
Все остальные выражения являются выражениями значений.
Выражение значения — это выражение, которое представляет фактическое значение.
Следующие контексты являются контекстами выражений мест:
- Левый операнд выражения составного присваивания.
- Операнд унарного оператора заимствования, сырого заимствования или разыменования.
- Операнд выражения поля.
- Индексируемый операнд выражения индексации массива.
- Операнд любого неявного заимствования.
- Инициализатор оператора let.
- Образец выражения
if let,matchилиwhile let. - Основа функционального обновления структуры.
Note
Исторически выражения мест назывались lvalues, а выражения значений назывались rvalues.
Выражение присваивания — это выражение, которое появляется в левом операнде выражения присваивания. Явно, выражения присваивания это:
- Выражения мест.
- Подчёркивания.
- Кортежи из выражений присваивания.
- Срезы из выражений присваивания.
- Кортежные структуры из выражений присваивания.
- Структуры из выражений присваивания (с опционально именованными полями).
- Unit структуры
Произвольная расстановка скобок разрешена внутри выражений присваивания.
Перемещаемые и копируемые типы
Когда выражение места вычисляется в контексте выражения значения или привязывается по значению в паттерне, оно обозначает значение, хранящееся в этой области памяти.
Если тип этого значения реализует Copy, то значение будет скопировано.
В оставшихся ситуациях, если этот тип Sized, то может быть возможно переместить значение.
Только следующие выражения мест могут быть перемещены:
- Переменные, которые в настоящее время не заимствованы.
- Временные значения.
- Поля выражения места, которые можно переместить и которые не реализуют
Drop. - Результат разыменования выражения с типом
Box<T>, которое также можно переместить.
После перемещения из выражения места, которое вычисляется в локальную переменную, местоположение деинициализируется и не может быть прочитано снова, пока не будет реинициализировано.
Во всех других случаях попытка использовать выражение места в контексте выражения значения является ошибкой.
Изменяемость
Чтобы выражение места могло быть присвоено, изменяемо заимствовано, неявно изменяемо заимствовано или привязано к паттерну, содержащему ref mut, оно должно быть изменяемым.
Мы называем их изменяемыми выражениями мест.
В противоположность этому, другие выражения мест называются неизменяемыми выражениями мест.
Следующие выражения могут быть контекстами изменяемых выражений мест:
- Изменяемые переменные, которые в настоящее время не заимствованы.
- Изменяемые
staticэлементы. - Временные значения.
- Поля: это вычисляет подвыражение в контексте изменяемого выражения места.
- Разыменования указателя
*mut T. - Разыменование переменной или поля переменной с типом
&mut T. Примечание: Это исключение из требования следующего правила. - Разыменования типа, который реализует
DerefMut: это затем требует, чтобы значение, которое разыменовывается, вычислялось в контексте изменяемого выражения места. - Индексация массива типа, который реализует
IndexMut: это затем вычисляет значение, которое индексируется, но не индекс, в контексте изменяемого выражения места.
Временные значения
При использовании выражения значения в большинстве контекстов выражений мест создаётся временное безымянное местоположение памяти и инициализируется этим значением.
Выражение вычисляется в это местоположение вместо этого, за исключением случаев, когда оно повышено до static.
Область удаления временного значения обычно является концом охватывающего оператора.
Супер макросы
Некоторые встроенные макросы могут создавать временные значения, чьи области видимости могут быть расширены. Эти временные значения являются супер временными, а эти макросы — супер макросами. Вызовы этих макросов являются выражениями вызова супер макросов. Аргументы этих макросов могут быть супер операндами.
Note
Когда выражение вызова супер макроса является расширяющим выражением, его супер операнды являются расширяющими выражениями и области видимости супер временных значений расширены. См. destructors.scope.lifetime-extension.exprs.
format_args!
За исключением аргумента строки формата, все аргументы, переданные в format_args!, являются супер операндами.
#![allow(unused)] fn main() { fn temp() -> String { String::from("") } // Поскольку вызов является расширяющим выражением и аргумент // является супер операндом, внутренний блок является расширяющим выражением, // поэтому область видимости временного значения, созданного в его завершающем выражении, // расширена. let _ = format_args!("{}", { &temp() }); // OK }
Супер операнды format_args! неявно заимствуются и поэтому являются контекстами выражений мест. Когда выражение значения передаётся как аргумент, оно создаёт супер временное значение.
#![allow(unused)] fn main() { fn temp() -> String { String::from("") } let x = format_args!("{}", temp()); x; // <-- Временное значение расширено, позволяя использование здесь. }
Развёртывание вызова format_args! иногда создаёт другие внутренние супер временные значения.
#![allow(unused)] fn main() { let x = { // Этот вызов создаёт внутреннее временное значение. let x = format_args!("{:?}", 0); x // <-- Временное значение расширено, позволяя его использование здесь. }; // <-- Временное значение удаляется здесь. x; // ERROR }
#![allow(unused)] fn main() { // Этот вызов не создаёт внутреннее временное значение. let x = { let x = format_args!("{}", 0); x }; x; // OK }
Note
Подробности того, когда
format_args!создаёт или не создаёт внутренние временные значения, в настоящее время не специфицированы.
pin!
Аргумент pin! является супер операндом.
#![allow(unused)] fn main() { use core::pin::pin; fn temp() {} // Как выше для `format_args!`. let _ = pin!({ &temp() }); // OK }
Аргумент pin! является контекстом выражения значения и создаёт супер временное значение.
#![allow(unused)] fn main() { use core::pin::pin; fn temp() {} // Аргумент вычисляется в супер временное значение. let x = pin!(temp()); // Временное значение расширено, позволяя его использование здесь. x; // OK }
Неявные заимствования
Некоторые выражения будут обрабатывать выражение как выражение места, неявно заимствуя его.
Например, можно напрямую сравнивать два несized среза на равенство, потому что оператор == неявно заимствует свои операнды:
#![allow(unused)] fn main() { let c = [1, 2, 3]; let d = vec![1, 2, 3]; let a: &[i32]; let b: &[i32]; a = &c; b = &d; // ... *a == *b; // Эквивалентная форма: ::std::cmp::PartialEq::eq(&*a, &*b); }
Неявные заимствования могут быть взяты в следующих выражениях:
- Левый операнд в выражениях вызова метода.
- Левый операнд в выражениях поля.
- Левый операнд в выражениях вызова.
- Левый операнд в выражениях индексации массива.
- Операнд оператора разыменования (
*). - Операнды сравнения.
- Левые операнды составного присваивания.
- Аргументы
format_args!кроме строки формата.
Перегружаемые трейты
Многие из следующих операторов и выражений также могут быть перегружены для других типов с использованием трейтов в std::ops или std::cmp.
Эти трейты также существуют в core::ops и core::cmp с теми же именами.
Атрибуты выражений
Внешние атрибуты перед выражением разрешены только в нескольких конкретных случаях:
- Перед выражением, используемым как оператор.
- Элементы выражений массивов, кортежей, вызовов и кортежных структур.
- Завершающее выражение блочных выражений.
Они никогда не разрешены перед:
- Выражениями диапазона.
- Выражениями бинарных операторов (ArithmeticOrLogicalExpression, ComparisonExpression, LazyBooleanExpression, TypeCastExpression, AssignmentExpression, CompoundAssignmentExpression).
Выражения-литералы
Syntax
LiteralExpression →
CHAR_LITERAL
| STRING_LITERAL
| RAW_STRING_LITERAL
| BYTE_LITERAL
| BYTE_STRING_LITERAL
| RAW_BYTE_STRING_LITERAL
| C_STRING_LITERAL
| RAW_C_STRING_LITERAL
| INTEGER_LITERAL
| FLOAT_LITERAL
| true
| false
Выражение-литерал — это выражение, состоящее из одного токена, а не из последовательности токенов, которое непосредственно и прямо обозначает значение, к которому оно вычисляется, а не ссылается на него по имени или по какому-либо другому правилу вычисления.
Литерал является формой константного выражения, поэтому вычисляется (в основном) во время компиляции.
Каждая из лексических форм литеральных токенов, описанных ранее, может составлять выражение-литерал, как и ключевые слова true и false.
#![allow(unused)] fn main() { "hello"; // строковый тип '5'; // символьный тип 5; // целочисленный тип }
В приведенных ниже описаниях строковое представление токена — это последовательность символов из входных данных, которая соответствует продукции токена в грамматике Лексера.
Note
Это строковое представление никогда не включает символ
U+000D(CR), за которым сразу следуетU+000A(LF): эта пара была бы ранее преобразована в одинU+000A(LF).
Экранирования
В описаниях текстовых выражений-литералов ниже используются несколько форм экранирования.
Каждая форма экранирования характеризуется:
- последовательностью экранирования: последовательностью символов, которая всегда начинается с
U+005C(\) - экранированным значением: либо одним символом, либо пустой последовательностью символов
В определениях экранирований ниже:
- Восьмеричная цифра — это любой из символов в диапазоне [
0-7]. - Шестнадцатеричная цифра — это любой из символов в диапазонах [
0-9], [a-f] или [A-F].
Простые экранирования
Каждая последовательность символов, встречающаяся в первом столбце следующей таблицы, является последовательностью экранирования.
В каждом случае экранированное значение — это символ, указанный в соответствующей записи во втором столбце.
| Последовательность | Экранированное значение |
|---|---|
\0 | U+0000 (NUL) |
\t | U+0009 (HT) |
\n | U+000A (LF) |
\r | U+000D (CR) |
\" | U+0022 (КАВЫЧКА) |
\' | U+0027 (АПОСТРОФ) |
\\ | U+005C (ОБРАТНАЯ КОСАЯ ЧЕРТА) |
8-битные экранирования
Последовательность экранирования состоит из \x, за которым следуют две шестнадцатеричные цифры.
Экранированное значение — это символ, скалярное значение Юникода которого является результатом интерпретации последних двух символов в последовательности экранирования как шестнадцатеричного целого числа, как если бы с помощью u8::from_str_radix с основанием 16.
Note
Таким образом, экранированное значение имеет скалярное значение Юникода в диапазоне
u8.
7-битные экранирования
Последовательность экранирования состоит из \x, за которым следуют восьмеричная цифра, а затем шестнадцатеричная цифра.
Экранированное значение — это символ, скалярное значение Юникода которого является результатом интерпретации последних двух символов в последовательности экранирования как шестнадцатеричного целого числа, как если бы с помощью u8::from_str_radix с основанием 16.
Юникодные экранирования
Последовательность экранирования состоит из \u{, за которым следует последовательность символов, каждый из которых является шестнадцатеричной цифрой или _, а затем }.
Экранированное значение — это символ, скалярное значение Юникода которого является результатом интерпретации шестнадцатеричных цифр, содержащихся в последовательности экранирования, как шестнадцатеричного целого числа, как если бы с помощью u32::from_str_radix с основанием 16.
Note
Разрешенные формы токенов CHAR_LITERAL или STRING_LITERAL гарантируют, что такой символ существует.
Экранирования продолжения строки
Последовательность экранирования состоит из \, за которым сразу следует U+000A (LF), и всех последующих пробельных символов до следующего непробельного символа.
Для этой цели пробельными символами являются U+0009 (HT), U+000A (LF), U+000D (CR) и U+0020 (SPACE).
Экранированное значение представляет собой пустую последовательность символов.
Note
Эффект этой формы экранирования заключается в том, что продолжение строки пропускает последующие пробелы, включая дополнительные переводы строк. Таким образом,
a,bиcравны:#![allow(unused)] fn main() { let a = "foobar"; let b = "foo\ bar"; let c = "foo\ bar"; assert_eq!(a, b); assert_eq!(b, c); }Пропуск дополнительных переводов строк (как в примере c) потенциально сбивает с толку и является неожиданным. Это поведение может быть изменено в будущем. Пока решение не принято, рекомендуется избегать зависимости от пропуска нескольких переводов строк с помощью продолжений строк. Дополнительную информацию смотрите в этом issue.
Символьные выражения-литералы
Символьное выражение-литерал состоит из одного токена CHAR_LITERAL.
Тип выражения — примитивный тип char.
Токен не должен иметь суффикса.
Содержимое литерала токена — это последовательность символов, следующая за первым U+0027 (') и предшествующая последнему U+0027 (') в строковом представлении токена.
Представленный символ выражения-литерала получается из содержимого литерала следующим образом:
- Если содержимое литерала является одной из следующих форм последовательности экранирования, то представленный символ — это экранированное значение последовательности экранирования:
- В противном случае представленный символ — это единственный символ, составляющий содержимое литерала.
Значением выражения является char, соответствующий скалярному значению Юникода представленного символа.
Note
Разрешенные формы токена CHAR_LITERAL гарантируют, что эти правила всегда производят один символ.
Примеры символьных выражений-литералов:
#![allow(unused)] fn main() { 'R'; // R '\''; // ' '\x52'; // R '\u{00E6}'; // LATIN SMALL LETTER AE (U+00E6) }
Строковые выражения-литералы
Строковое выражение-литерал состоит из одного токена STRING_LITERAL или RAW_STRING_LITERAL.
Тип выражения — разделяемая ссылка (со временем жизни static) на примитивный тип str.
То есть тип — &'static str.
Токен не должен иметь суффикса.
Содержимое литерала токена — это последовательность символов, следующая за первым U+0022 (") и предшествующая последнему U+0022 (") в строковом представлении токена.
Представленная строка выражения-литерала — это последовательность символов, полученная из содержимого литерала следующим образом:
-
Если токен является STRING_LITERAL, каждая последовательность экранирования любой из следующих форм, встречающаяся в содержимом литерала, заменяется экранированным значением последовательности экранирования.
- Простые экранирования
- 7-битные экранирования
- Юникодные экранирования
- Экранирования продолжения строки
Эти замены происходят в порядке слева направо. Например, токен
"\\x41"преобразуется в символы\x41.
- Если токен является RAW_STRING_LITERAL, представленная строка идентична содержимому литерала.
Значением выражения является ссылка на статически выделенный str, содержащий UTF-8 кодировку представленной строки.
Примеры строковых выражений-литералов:
#![allow(unused)] fn main() { "foo"; r"foo"; // foo "\"foo\""; r#""foo""#; // "foo" "foo #\"# bar"; r##"foo #"# bar"##; // foo #"# bar "\x52"; "R"; r"R"; // R "\\x52"; r"\x52"; // \x52 }
Байтовые выражения-литералы
Байтовое выражение-литерал состоит из одного токена BYTE_LITERAL.
Тип выражения — примитивный тип u8.
Токен не должен иметь суффикса.
Содержимое литерала токена — это последовательность символов, следующая за первым U+0027 (') и предшествующая последнему U+0027 (') в строковом представлении токена.
Представленный символ выражения-литерала получается из содержимого литерала следующим образом:
- Если содержимое литерала является одной из следующих форм последовательности экранирования, то представленный символ — это экранированное значение последовательности экранирования:
- В противном случае представленный символ — это единственный символ, составляющий содержимое литерала.
Значением выражения является скалярное значение Юникода представленного символа.
Note
Разрешенные формы токена BYTE_LITERAL гарантируют, что эти правила всегда производят один символ, чье скалярное значение Юникода находится в диапазоне
u8.
Примеры байтовых выражений-литералов:
#![allow(unused)] fn main() { b'R'; // 82 b'\''; // 39 b'\x52'; // 82 b'\xA0'; // 160 }
Байтовые строковые выражения-литералы
Байтовое строковое выражение-литерал состоит из одного токена BYTE_STRING_LITERAL или RAW_BYTE_STRING_LITERAL.
Тип выражения — разделяемая ссылка (со временем жизни static) на массив, тип элемента которого — u8.
То есть тип — &'static [u8; N], где N — количество байт в представленной строке, описанной ниже.
Токен не должен иметь суффикса.
Содержимое литерала токена — это последовательность символов, следующая за первым U+0022 (") и предшествующая последнему U+0022 (") в строковом представлении токена.
Представленная строка выражения-литерала — это последовательность символов, полученная из содержимого литерала следующим образом:
-
Если токен является BYTE_STRING_LITERAL, каждая последовательность экранирования любой из следующих форм, встречающаяся в содержимом литерала, заменяется экранированным значением последовательности экранирования.
Эти замены происходят в порядке слева направо. Например, токен
b"\\x41"преобразуется в символы\x41.
- Если токен является RAW_BYTE_STRING_LITERAL, представленная строка идентична содержимому литерала.
Значением выражения является ссылка на статически выделенный массив, содержащий скалярные значения Юникода символов в представленной строке, в том же порядке.
Note
Разрешенные формы токенов BYTE_STRING_LITERAL и RAW_BYTE_STRING_LITERAL гарантируют, что эти правила всегда производят значения элементов массива в диапазоне
u8.
Примеры байтовых строковых выражений-литералов:
#![allow(unused)] fn main() { b"foo"; br"foo"; // foo b"\"foo\""; br#""foo""#; // "foo" b"foo #\"# bar"; br##"foo #"# bar"##; // foo #"# bar b"\x52"; b"R"; br"R"; // R b"\\x52"; br"\x52"; // \x52 }
Строковые выражения-литералы в стиле C
Строковое выражение-литерал в стиле C состоит из одного токена C_STRING_LITERAL или RAW_C_STRING_LITERAL.
Тип выражения — разделяемая ссылка (со временем жизни static) на тип CStr из стандартной библиотеки.
То есть тип — &'static core::ffi::CStr.
Токен не должен иметь суффикса.
Содержимое литерала токена — это последовательность символов, следующая за первым " и предшествующая последнему " в строковом представлении токена.
Представленные байты выражения-литерала — это последовательность байтов, полученная из содержимого литерала следующим образом:
- Если токен является C_STRING_LITERAL, содержимое литерала обрабатывается как последовательность элементов, каждый из которых представляет собой либо один символ Юникода, отличный от
\, либо экранирование. Последовательность элементов преобразуется в последовательность байтов следующим образом:- Каждый отдельный символ Юникода вносит свой UTF-8 representation.
- Каждое простое экранирование вносит скалярное значение Юникода своего экранированного значения.
- Каждое 8-битное экранирование вносит один байт, содержащий скалярное значение Юникода своего экранированного значения.
- Каждое юникодное экранирование вносит UTF-8 representation своего экранированного значения.
- Каждое экранирование продолжения строки не вносит байтов.
- Если токен является RAW_C_STRING_LITERAL, представленные байты — это UTF-8 кодировка содержимого литерала.
Note
Разрешенные формы токенов C_STRING_LITERAL и RAW_C_STRING_LITERAL гарантируют, что представленные байты никогда не включают нулевой байт.
Значением выражения является ссылка на статически выделенный CStr, чей массив байтов содержит представленные байты, за которыми следует нулевой байт.
Примеры строковых выражений-литералов в стиле C:
#![allow(unused)] fn main() { c"foo"; cr"foo"; // foo c"\"foo\""; cr#""foo""#; // "foo" c"foo #\"# bar"; cr##"foo #"# bar"##; // foo #"# bar c"\x52"; c"R"; cr"R"; // R c"\\x52"; cr"\x52"; // \x52 c"æ"; // LATIN SMALL LETTER AE (U+00E6) c"\u{00E6}"; // LATIN SMALL LETTER AE (U+00E6) c"\xC3\xA6"; // LATIN SMALL LETTER AE (U+00E6) c"\xE6".to_bytes(); // [230] c"\u{00E6}".to_bytes(); // [195, 166] }
Целочисленные выражения-литералы
Целочисленное выражение-литерал состоит из одного токена INTEGER_LITERAL.
Если токен имеет суффикс, этот суффикс должен быть именем одного из примитивных целочисленных типов: u8, i8, u16, i16, u32, i32, u64, i64, u128, i128, usize или isize, и выражение имеет этот тип.
Если токен не имеет суффикса, тип выражения определяется выводом типа:
- Если целочисленный тип может быть однозначно определен из окружающего контекста программы, выражение имеет этот тип.
- Если контекст программы недостаточно ограничивает тип, по умолчанию используется знаковый 32-битный целочисленный тип
i32.
- Если контекст программы чрезмерно ограничивает тип, это считается статической ошибкой типа.
Примеры целочисленных выражений-литералов:
#![allow(unused)] fn main() { 123; // тип i32 123i32; // тип i32 123u32; // тип u32 123_u32; // тип u32 let a: u64 = 123; // тип u64 0xff; // тип i32 0xff_u8; // тип u8 0o70; // тип i32 0o70_i16; // тип i16 0b1111_1111_1001_0000; // тип i32 0b1111_1111_1001_0000i64; // тип i64 0usize; // тип usize }
Значение выражения определяется из строкового представления токена следующим образом:
-
Выбирается целочисленное основание системы счисления путем проверки первых двух символов строки, следующим образом:
0bуказывает основание 20oуказывает основание 80xуказывает основание 16- в противном случае основание равно 10.
- Если основание не 10, первые два символа удаляются из строки.
- Любой суффикс удаляется из строки.
- Любые подчеркивания удаляются из строки.
- Строка преобразуется в значение
u128как если бы с помощьюu128::from_str_radixс выбранным основанием. Если значение не помещается вu128, это ошибка компилятора.
- Значение
u128преобразуется в тип выражения через числовое приведение.
Note
Финальное приведение будет усекать значение литерала, если оно не помещается в тип выражения.
rustcвключает проверку переполнения литералов с именемoverflowing_literals, по умолчаниюdeny, которая отклоняет выражения, где это происходит.
Note
-1i8, например, это применение оператора отрицания к выражению-литералу1i8, а не одиночное целочисленное выражение-литерал. См. Переполнение для примечаний о представлении наименьшего отрицательного значения для знакового типа.
Выражения-литералы с плавающей точкой
Выражение-литерал с плавающей точкой имеет одну из двух форм:
- одиночный токен FLOAT_LITERAL
- одиночный токен INTEGER_LITERAL, который имеет суффикс и не имеет индикатора системы счисления
Если токен имеет суффикс, этот суффикс должен быть именем одного из примитивных типов с плавающей точкой: f32 или f64, и выражение имеет этот тип.
Если токен не имеет суффикса, тип выражения определяется выводом типа:
- Если тип с плавающей точкой может быть однозначно определен из окружающего контекста программы, выражение имеет этот тип.
- Если контекст программы недостаточно ограничивает тип, по умолчанию используется
f64.
- Если контекст программы чрезмерно ограничивает тип, это считается статической ошибкой типа.
Примеры выражений-литералов с плавающей точкой:
#![allow(unused)] fn main() { 123.0f64; // тип f64 0.1f64; // тип f64 0.1f32; // тип f32 12E+99_f64; // тип f64 5f32; // тип f32 let x: f64 = 2.; // тип f64 }
Значение выражения определяется из строкового представления токена следующим образом:
- Любой суффикс удаляется из строки.
- Любые подчеркивания удаляются из строки.
- Строка преобразуется в тип выражения как если бы с помощью
f32::from_strилиf64::from_str.
Note
-1.0, например, это применение оператора отрицания к выражению-литералу1.0, а не одиночное выражение-литерал с плавающей точкой.
Note
infиNaNне являются литеральными токенами. Вместо выражений-литералов можно использовать константыf32::INFINITY,f64::INFINITY,f32::NANиf64::NAN. Вrustcлитерал, достаточно большой, чтобы быть оцененным как бесконечность, вызовет проверку переполнения литераловoverflowing_literals.
Логические выражения-литералы
Логическое выражение-литерал состоит из одного из ключевых слов true или false.
Тип выражения — примитивный логический тип, и его значение:
- true, если ключевое слово
true - false, если ключевое слово
false
Выражения-пути
Путь, используемый в контексте выражения, обозначает либо локальную переменную, либо элемент.
Выражения-пути, которые разрешаются в локальные или статические переменные, являются выражениями-местами, другие пути являются выражениями-значениями.
Использование переменной static mut требует небезопасного блока.
#![allow(unused)] fn main() { mod globals { pub static STATIC_VAR: i32 = 5; pub static mut STATIC_MUT_VAR: i32 = 7; } let local_var = 3; local_var; globals::STATIC_VAR; unsafe { globals::STATIC_MUT_VAR }; let some_constructor = Some::<i32>; let push_integer = Vec::<i32>::push; let slice_reverse = <[i32]>::reverse; }
Вычисление ассоциированных констант обрабатывается так же, как и const блоки.
Выражения-блоки
Syntax
BlockExpression →
{
InnerAttribute*
Statements?
}
Statements →
Statement+
| Statement+ ExpressionWithoutBlock
| ExpressionWithoutBlock
Выражение-блок, или блок, — это выражение управления потоком выполнения и анонимная пространство имен для элементов и объявлений переменных.
Как выражение управления потоком, блок последовательно выполняет свои составные операторы не-элементы, а затем свое конечное необязательное выражение.
Как анонимная пространство имен, объявления элементов видны только внутри самого блока, а переменные, объявленные операторами let, видны со следующего оператора до конца блока.
Смотрите главу области видимости для более подробной информации.
Синтаксис блока: {, затем любые внутренние атрибуты, затем любое количество операторов, затем необязательное выражение, называемое конечным операндом, и, наконец, }.
Операторы обычно должны следовать за точкой с запятой, за двумя исключениями:
- Операторы объявления элементов не требуют точки с запятой после себя.
- Операторы-выражения обычно требуют последующей точки с запятой, за исключением случая, когда их внешнее выражение является выражением управления потоком.
Кроме того, дополнительные точки с запятой между операторами разрешены, но они не влияют на семантику.
При вычислении выражения-блока каждый оператор, кроме операторов объявления элементов, выполняется последовательно.
Затем выполняется конечный операнд, если он указан.
Тип блока — это тип конечного операнда или (), если конечный операнд опущен.
#![allow(unused)] fn main() { fn fn_call() {} let _: () = { fn_call(); }; let five: i32 = { fn_call(); 5 }; assert_eq!(5, five); }
Note
Как выражение управления потоком, если выражение-блок является внешним выражением оператора-выражения, ожидаемый тип —
(), если за ним не следует сразу точка с запятой.
Блоки всегда являются выражениями-значениями и вычисляют последний операнд в контексте выражения-значения.
Note
Это можно использовать для принудительного перемещения значения, если это действительно необходимо. Например, следующий пример завершается ошибкой при вызове
consume_self, потому что структура была перемещена изsв выражении-блоке.#![allow(unused)] fn main() { struct Struct; impl Struct { fn consume_self(self) {} fn borrow_self(&self) {} } fn move_by_block_expression() { let s = Struct; // Перемещаем значение из `s` в выражении-блоке. (&{ s }).borrow_self(); // Не выполняется, потому что `s` была перемещена. s.consume_self(); } }
async блоки
Syntax
AsyncBlockExpression → async move? BlockExpression
Async блок — это вариант выражения-блока, которое вычисляется в будущее (future).
Конечное выражение блока, если оно присутствует, определяет результирующее значение будущего.
Выполнение async блока похоже на выполнение выражения замыкания: его непосредственный эффект — создание и возврат анонимного типа.
Однако, в то время как замыкания возвращают тип, который реализует один или более трейтов std::ops::Fn, тип, возвращаемый для async блока, реализует трейт std::future::Future.
Фактуальный формат данных для этого типа не специфицирован.
Note
Тип будущего, генерируемый rustc, грубо эквивалентен перечислению с одним вариантом на каждую точку
await, где каждый вариант хранит данные, необходимые для возобновления из соответствующей точки.
2018 Edition differences
Async блоки доступны только начиная с Rust 2018.
Режимы захвата
Async блоки захватывают переменные из своего окружения, используя те же режимы захвата, что и замыкания.
Как и замыкания, когда написан async { .. }, режим захвата для каждой переменной будет выведен из содержимого блока.
Однако блоки async move { .. } будут перемещать все referenced переменные в результирующее будущее.
Async контекст
Поскольку async блоки конструируют будущее, они определяют async контекст, который, в свою очередь, может содержать await выражения.
Async контексты устанавливаются async блоками, а также телами async функций, чья семантика определена в терминах async блоков.
Операторы управления потоком
Async блоки действуют как граница функции, подобно замыканиям.
Поэтому оператор ? и выражения return влияют на вывод будущего, а не на объемлющую функцию или другой контекст.
То есть return <expr> изнутри async блока вернет результат <expr> как вывод будущего.
Аналогично, если <expr>? распространяет ошибку, эта ошибка распространяется как результат будущего.
Наконец, ключевые слова break и continue не могут быть использованы для выхода из async блока.
Следовательно, следующее незаконно:
#![allow(unused)] fn main() { loop { async move { break; // ошибка[E0267]: `break` внутри `async` блока } } }
const блоки
Syntax
ConstBlockExpression → const BlockExpression
Const блок — это вариант выражения-блока, тело которого вычисляется во время компиляции, а не во время выполнения.
Const блоки позволяют определить константное значение без необходимости определять новые константные элементы, и поэтому их иногда называют встроенными константами (inline consts). Они также поддерживают вывод типов, поэтому не нужно указывать тип, в отличие от константных элементов.
Const блоки имеют возможность ссылаться на обобщенные параметры в области видимости, в отличие от свободных константных элементов. Они десугарируются в константные элементы с обобщенными параметрами в области видимости (аналогично ассоциированным константам, но без трейта или типа, с которым они ассоциированы). Например, этот код:
#![allow(unused)] fn main() { fn foo<T>() -> usize { const { std::mem::size_of::<T>() + 1 } } }
эквивалентен:
#![allow(unused)] fn main() { fn foo<T>() -> usize { { struct Const<T>(T); impl<T> Const<T> { const CONST: usize = std::mem::size_of::<T>() + 1; } Const::<T>::CONST } } }
Если выражение const блока выполняется во время выполнения, то гарантируется, что константа будет вычислена, даже если ее возвращаемое значение игнорируется:
#![allow(unused)] fn main() { fn foo<T>() -> usize { // Если этот код когда-либо выполнится, то утверждение точно // было вычислено во время компиляции. const { assert!(std::mem::size_of::<T>() > 0); } // Здесь у нас может быть небезопасный код, полагающийся на то, что тип имеет ненулевой размер. /* ... */ 42 } }
Если выражение const блока не выполняется во время выполнения, оно может быть вычислено или нет:
#![allow(unused)] fn main() { if false { // Паника может произойти или не произойти при сборке программы. const { panic!(); } } }
unsafe блоки
Syntax
UnsafeBlockExpression → unsafe BlockExpression
Смотрите unsafe блоки для получения дополнительной информации о том, когда использовать unsafe.
Блок кода может быть предварен ключевым словом unsafe, чтобы разрешить небезопасные операции.
Примеры:
#![allow(unused)] fn main() { unsafe { let b = [13u8, 17u8]; let a = &b[0] as *const u8; assert_eq!(*a, 13); assert_eq!(*a.offset(1), 17); } unsafe fn an_unsafe_fn() -> i32 { 10 } let a = unsafe { an_unsafe_fn() }; }
Блоки с метками
Блоки с метками документированы в разделе Циклы и другие прерываемые выражения.
Атрибуты на выражениях-блоках
Внутренние атрибуты разрешены непосредственно после открывающей фигурной скобки выражения-блока в следующих ситуациях:
- Тела функций и методов.
- Тела циклов (
loop,while, иfor). - Выражения-блоки, используемые как оператор.
- Выражения-блоки как элементы выражений-массивов, выражений-кортежей, выражений-вызовов и структурных выражений в стиле кортежей.
- Выражение-блок как конечное выражение другого выражения-блока.
Атрибуты, которые имеют смысл на выражении-блоке, это cfg и атрибуты проверки линтеров.
Например, эта функция возвращает true на unix платформах и false на других платформах.
#![allow(unused)] fn main() { fn is_unix_platform() -> bool { #[cfg(unix)] { true } #[cfg(not(unix))] { false } } }
Операторные выражения
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 -
только замыкания, которые не захватывают (не закрывают) никакие локальные переменные, могут быть приведены к указателям на функцию. ↩
Группирующие выражения
Syntax
GroupedExpression → ( Expression )
Выражение в скобках оборачивает одиночное выражение, вычисляясь в это выражение.
Синтаксис выражения в скобках: (, затем выражение, называемое включенным операндом, и затем ).
Выражения в скобках вычисляются в значение включенного операнда.
В отличие от других выражений, выражения в скобках являются одновременно выражениями-местами и выражениями-значениями. Когда включенный операнд является выражением-местом, оно является выражением-местом, и когда включенный операнд является выражением-значением, оно является выражением-значением.
Скобки могут быть использованы для явного изменения порядка приоритета подвыражений внутри выражения.
Пример выражения в скобках:
#![allow(unused)] fn main() { let x: i32 = 2 + 3 * 4; // без скобок let y: i32 = (2 + 3) * 4; // в скобках assert_eq!(x, 14); assert_eq!(y, 20); }
Пример необходимого использования скобок - вызов указателя на функцию, который является членом структуры:
#![allow(unused)] fn main() { struct A { f: fn() -> &'static str } impl A { fn f(&self) -> &'static str { "Метод f" } } let a = A{f: || "Поле f"}; assert_eq!( a.f (), "Метод f"); assert_eq!((a.f)(), "Поле f"); }
Выражения массивов и индексации массивов
Выражения массивов
Syntax
ArrayExpression → [ ArrayElements? ]
ArrayElements →
Expression ( , Expression )* ,?
| Expression ; Expression
Выражения массивов конструируют массивы. Выражения массивов бывают двух форм.
Первая форма перечисляет каждое значение в массиве.
Синтаксис для этой формы - это разделенный запятыми список выражений единообразного типа, заключенный в квадратные скобки.
Это производит массив, содержащий каждое из этих значений в том порядке, в котором они записаны.
Синтаксис для второй формы - два выражения, разделенные точкой с запятой (;), заключенные в квадратные скобки.
Выражение перед ; называется операндом повторения.
Выражение после ; называется операндом длины.
Операнд длины должен быть либо выведенной константой, либо константным выражением типа usize (например, литералом или константным элементом).
#![allow(unused)] fn main() { const C: usize = 1; let _: [u8; C] = [0; 1]; // Литерал. let _: [u8; C] = [0; C]; // Константный элемент. let _: [u8; C] = [0; _]; // Выведенная константа. let _: [u8; C] = [0; (((_)))]; // Выведенная константа. }
Note
В выражении массива выведенная константа парсится как выражение, но затем семантически трактуется как отдельный вид аргумента обобщенной константы.
Выражение массива этой формы создает массив с длиной, равной значению операнда длины, где каждый элемент является копией операнда повторения.
То есть, [a; b] создает массив, содержащий b копий значения a.
Если операнд длины имеет значение больше 1, то это требует, чтобы операнд повторения имел тип, который реализует Copy, был выражением const блока или был путем к константному элементу.
Когда операнд повторения является const блоком или путем к константному элементу, он вычисляется количество раз, указанное в операнде длины.
Если это значение равно 0, то const блок или константный элемент не вычисляется вообще.
Для выражений, которые не являются ни const блоком, ни путем к константному элементу, оно вычисляется ровно один раз, а затем результат копируется количество раз, равное значению операнда длины.
#![allow(unused)] fn main() { [1, 2, 3, 4]; ["a", "b", "c", "d"]; [0; 128]; // массив со 128 нулями [0u8, 0u8, 0u8, 0u8,]; [[1, 0, 0], [0, 1, 0], [0, 0, 1]]; // 2D массив const EMPTY: Vec<i32> = Vec::new(); [EMPTY; 2]; }
Выражения индексации массивов и срезов
Syntax
IndexExpression → Expression [ Expression ]
Значения типов массив и срез могут быть проиндексированы путем написания выражения типа usize (индекс) в квадратных скобках после них.
Когда массив изменяемый, результирующая область памяти может быть присвоена.
Для других типов выражение индексации a[b] эквивалентно *std::ops::Index::index(&a, b), или *std::ops::IndexMut::index_mut(&mut a, b) в контексте изменяемого выражения-места.
Так же, как и с методами, Rust также будет вставлять операции разыменования для a повторно, чтобы найти реализацию.
Индексация начинается с нуля для массивов и срезов.
Доступ к массиву является константным выражением, поэтому границы могут быть проверены во время компиляции с постоянным значением индекса. В противном случае проверка будет выполнена во время выполнения, которая переведет поток в состояние паники, если она не удастся.
#![allow(unused)] fn main() { // линтер по умолчанию запрещает. #![warn(unconditional_panic)] ([1, 2, 3, 4])[2]; // Вычисляется в 3 let b = [[1, 0, 0], [0, 1, 0], [0, 0, 1]]; b[1][2]; // многомерная индексация массива let x = (["a", "b"])[10]; // предупреждение: индекс вне границ let n = 10; let y = (["a", "b"])[n]; // паника let arr = ["a", "b"]; arr[10]; // предупреждение: индекс вне границ }
Выражение индексации массива может быть реализовано для типов, отличных от массивов и срезов, путем реализации трейтов Index и IndexMut.
Выражения кортежей и индексации кортежей
Выражения кортежей
Syntax
TupleExpression → ( TupleElements? )
TupleElements → ( Expression , )+ Expression?
Выражение кортежа конструирует значения кортежей.
Синтаксис выражений кортежей - это заключенный в круглые скобки, разделенный запятыми список выражений, называемых операндами инициализатора кортежа.
Одноэлементные выражения кортежей требуют запятую после их операнда инициализатора кортежа, чтобы быть отличимыми от выражения в скобках.
Выражения кортежей являются выражениями-значениями, которые вычисляются в новосозданное значение типа кортежа.
Количество операндов инициализатора кортежа является арностью конструируемого кортежа.
Выражения кортежей без каких-либо операндов инициализатора кортежа производят кортеж-единицу.
Для других выражений кортежей первый записанный операнд инициализатора кортежа инициализирует поле 0, а последующие операнды инициализируют следующее по возрастанию поле.
Например, в выражении кортежа ('a', 'b', 'c'), 'a' инициализирует значение поля 0, 'b' - поле 1, и 'c' - поле 2.
Примеры выражений кортежей и их типов:
| Выражение | Тип |
|---|---|
() | () (единица) |
(0.0, 4.5) | (f64, f64) |
("x".to_string(), ) | (String, ) |
("a", 4usize, true) | (&'static str, usize, bool) |
Выражения индексации кортежей
Syntax
TupleIndexingExpression → Expression . TUPLE_INDEX
Выражение индексации кортежа обращается к полям кортежей и кортежных структур.
Синтаксис выражения индексации кортежа: выражение, называемое операндом кортежа, затем ., и, наконец, индекс кортежа.
Синтаксис для индекса кортежа - это десятичный литерал без ведущих нулей, подчеркиваний или суффикса.
Например, 0 и 2 являются допустимыми индексами кортежей, но не 01, 0_, ни 0i32.
Тип операнда кортежа должен быть типом кортежа или кортежной структурой.
Индекс кортежа должен быть именем поля типа операнда кортежа.
Вычисление выражений индексации кортежей не имеет побочных эффектов, кроме вычисления его операнда кортежа. Как выражение-место, оно вычисляется в местоположение поля операнда кортежа с тем же именем, что и индекс кортежа.
Примеры выражений индексации кортежей:
#![allow(unused)] fn main() { // Индексация кортежа let pair = ("a string", 2); assert_eq!(pair.1, 2); // Индексация кортежной структуры struct Point(f32, f32); let point = Point(1.0, 0.0); assert_eq!(point.0, 1.0); assert_eq!(point.1, 0.0); }
Note
В отличие от выражений доступа к полям, выражения индексации кортежей могут быть функциональным операндом выражения вызова, поскольку они не могут быть спутаны с вызовом метода, так как имена методов не могут быть числами.
Note
Хотя массивы и срезы также имеют элементы, вы должны использовать выражение индексации массива или среза или образец среза для доступа к их элементам.
Выражения структур
Syntax
StructExpression →
PathInExpression { ( StructExprFields | StructBase )? }
StructExprFields →
StructExprField ( , StructExprField )* ( , StructBase | ,? )
StructExprField →
OuterAttribute*
(
IDENTIFIER
| ( IDENTIFIER | TUPLE_INDEX ) : Expression
)
StructBase → .. Expression
Выражение структуры создает значение структуры, варианта перечисления или объединения. Оно состоит из пути к элементу структуры, варианта перечисления или объединения, за которым следуют значения полей этого элемента.
Следующие примеры являются выражениями структур:
#![allow(unused)] fn main() { struct Point { x: f64, y: f64 } struct NothingInMe { } mod game { pub struct User<'a> { pub name: &'a str, pub age: u32, pub score: usize } } enum Enum { Variant {} } Point {x: 10.0, y: 20.0}; NothingInMe {}; let u = game::User {name: "Joe", age: 35, score: 100_000}; Enum::Variant {}; }
Note
Кортежные структуры и кортежные варианты перечислений обычно создаются с использованием выражения вызова, ссылающегося на конструктор в пространстве имен значений. Они отличаются от выражения структуры с фигурными скобками, ссылающегося на конструктор в пространстве имен типов.
#![allow(unused)] fn main() { struct Position(i32, i32, i32); Position(0, 0, 0); // Обычный способ создания кортежной структуры. let c = Position; // `c` - это функция, принимающая 3 аргумента. let pos = c(8, 6, 7); // Создает значение `Position`. enum Version { Triple(i32, i32, i32) }; Version::Triple(0, 0, 0); let f = Version::Triple; let ver = f(8, 6, 7); }Последний сегмент пути вызова не может ссылаться на псевдоним типа:
#![allow(unused)] fn main() { trait Tr { type T; } impl<T> Tr for T { type T = T; } struct Tuple(); enum Enum { Tuple() } // <Unit as Tr>::T(); // вызывает ошибку -- `::T` это тип, а не значение <Enum as Tr>::T::Tuple(); // OK }
Структуры-единицы и варианты перечислений-единицы обычно создаются с использованием выражения пути, ссылающегося на константу в пространстве имен значений.
#![allow(unused)] fn main() { struct Gamma; // Значение-единица Gamma, ссылающееся на константу в пространстве имен значений. let a = Gamma; // Точно такое же значение, как `a`, но созданное с использованием выражения структуры // ссылающегося на пространство имен типов. let b = Gamma {}; enum ColorSpace { Oklch } let c = ColorSpace::Oklch; let d = ColorSpace::Oklch {}; }
Выражение структуры с полями
Выражение структуры с полями, заключенными в фигурные скобки, позволяет указать значение для каждого отдельного поля в любом порядке. Имя поля отделяется от его значения двоеточием.
Значение типа объединения может быть создано только с использованием этого синтаксиса, и оно должно указывать ровно одно поле.
Синтаксис функционального обновления
Выражение структуры, которое конструирует значение типа структуры, может заканчиваться синтаксисом .., за которым следует выражение, обозначающее функциональное обновление.
Выражение после .. (база) должно иметь тот же тип структуры, что и новая структура, которая формируется.
Все выражение использует заданные значения для указанных полей и перемещает или копирует оставшиеся поля из базового выражения.
Как и во всех выражениях структур, все поля структуры должны быть видимыми, даже те, которые не указаны явно.
#![allow(unused)] fn main() { struct Point3d { x: i32, y: i32, z: i32 } let mut base = Point3d {x: 1, y: 2, z: 3}; let y_ref = &mut base.y; Point3d {y: 0, z: 10, .. base}; // OK, доступен только base.x drop(y_ref); }
Выражения структур не могут использоваться непосредственно в заголовке выражения цикла или if, или в рассматриваемом выражении выражения if let или match. Однако выражения структур могут использоваться в этих ситуациях, если они находятся внутри другого выражения, например, внутри скобок.
Имена полей могут быть десятичными целыми числами для указания индексов при конструировании кортежных структур. Это может быть использовано с базовыми структурами для заполнения оставшихся не указанных индексов:
#![allow(unused)] fn main() { struct Color(u8, u8, u8); let c1 = Color(0, 0, 0); // Обычный способ создания кортежной структуры. let c2 = Color{0: 255, 1: 127, 2: 0}; // Указание полей по индексу. let c3 = Color{1: 0, ..c2}; // Заполнить все остальные поля с использованием базовой структуры. }
Сокращенная инициализация полей структуры
При инициализации структуры данных (структуры, перечисления, объединения) с именованными (но не нумерованными) полями разрешается писать fieldname как сокращение для fieldname: fieldname.
Это позволяет компактный синтаксис с меньшим дублированием.
Например:
#![allow(unused)] fn main() { struct Point3d { x: i32, y: i32, z: i32 } let x = 0; let y_value = 0; let z = 0; Point3d { x: x, y: y_value, z: z }; Point3d { x, y: y_value, z }; }
Выражения вызовов
Syntax
CallExpression → Expression ( CallParams? )
CallParams → Expression ( , Expression )* ,?
Выражение вызова вызывает функцию. Синтаксис выражения вызова: выражение, называемое функциональным операндом, за которым следует заключенный в круглые скобки разделенный запятыми список выражений, называемых операндами аргументов.
Если функция в конечном итоге возвращает значение, то выражение завершается.
Для не-функциональных типов выражение f(...) использует метод одного из следующих трейтов в зависимости от функционального операнда:
FnилиAsyncFn— разделяемая ссылка.FnMutилиAsyncFnMut— изменяемая ссылка.FnOnceилиAsyncFnOnce— значение.
Автоматическое заимствование будет выполнено при необходимости. Функциональный операнд также будет автоматически разыменован по мере необходимости.
Некоторые примеры выражений вызовов:
#![allow(unused)] fn main() { fn add(x: i32, y: i32) -> i32 { 0 } let three: i32 = add(1i32, 2i32); let name: &'static str = (|| "Rust")(); }
Разрешение неоднозначности вызовов функций
Все вызовы функций являются синтаксическим сахаром для более явного полностью квалифицированного синтаксиса.
Вызовы функций могут нуждаться в полной квалификации в зависимости от неоднозначности вызова в свете элементов в области видимости.
Note
В прошлом термины “Unambiguous Function Call Syntax”, “Universal Function Call Syntax” или “UFCS” использовались в документации, issues, RFC и других материалах сообщества. Однако этим терминам не хватает описательной силы, и они потенциально запутывают суть вопроса. Мы упоминаем их здесь для удобства поиска.
Несколько ситуаций часто возникают, которые приводят к неоднозначностям относительно получателя или референта вызовов методов или ассоциированных функций. Эти ситуации могут включать:
- Несколько трейтов в области видимости определяют методы с одинаковыми именами для одних и тех же типов
- Авто-
derefнежелателен; например, различение методов на самом умном указателе и референте указателя - Методы, которые не принимают аргументов, такие как
default(), и возвращают свойства типа, такие какsize_of()
Чтобы разрешить неоднозначность, программист может ссылаться на желаемый метод или функцию, используя более конкретные пути, типы или трейты.
Например,
trait Pretty { fn print(&self); } trait Ugly { fn print(&self); } struct Foo; impl Pretty for Foo { fn print(&self) {} } struct Bar; impl Pretty for Bar { fn print(&self) {} } impl Ugly for Bar { fn print(&self) {} } fn main() { let f = Foo; let b = Bar; // мы можем сделать это, потому что у нас только один элемент с именем `print` для `Foo` f.print(); // более явно и, в случае `Foo`, не необходимо Foo::print(&f); // если вы не любите краткость <Foo as Pretty>::print(&f); // b.print(); // Ошибка: найдено несколько 'print' // Bar::print(&b); // Все еще ошибка: найдено несколько `print` // необходимо из-за элементов в области видимости, определяющих `print` <Bar as Pretty>::print(&b); }
Обратитесь к RFC 132 для получения дополнительных деталей и мотивации.
Выражения вызовов методов
Syntax
MethodCallExpression → Expression . PathExprSegment ( CallParams? )
Вызов метода состоит из выражения (называемого получателем), за которым следует точка, сегмент пути выражения и заключенный в круглые скобки список выражений.
Вызовы методов разрешаются в ассоциированные методы определенных трейтов, либо статически диспетчеризуясь к методу, если точный self-тип левой части известен, либо динамически диспетчеризуясь, если выражение левой части является косвенным трейт-объектом.
#![allow(unused)] fn main() { let pi: Result<f32, _> = "3.14".parse(); let log_pi = pi.unwrap_or(1.0).log(2.72); assert!(1.14 < log_pi && log_pi < 1.15) }
При поиске вызова метода получатель может быть автоматически разыменован или заимствован для вызова метода. Это требует более сложного процесса поиска, чем для других функций, поскольку может существовать несколько возможных методов для вызова. Используется следующая процедура:
Первый шаг - построить список кандидатов типов получателей. Получите их, повторно разыменовывая тип выражения-получателя, добавляя каждый встреченный тип в список, затем, наконец, попытавшись выполнить неразмерное приведение в конце и добавив результирующий тип, если это удалось.
Затем для каждого кандидата T добавьте &T и &mut T в список сразу после T.
Например, если получатель имеет тип Box<[i32;2]>, то кандидатами будут типы Box<[i32;2]>, &Box<[i32;2]>, &mut Box<[i32;2]>, [i32; 2] (путем разыменования), &[i32; 2], &mut [i32; 2], [i32] (путем неразмерного приведения), &[i32] и, наконец, &mut [i32].
Затем для каждого кандидата типа T выполните поиск видимого метода с получателем этого типа в следующих местах:
- Собственные методы
T(методы, реализованные непосредственно наT). - Любые методы, предоставляемые видимым трейтом, реализованным для
T. ЕслиTявляется параметром типа, сначала ищутся методы, предоставляемые ограничениями трейтов наT. Затем ищутся все оставшиеся методы в области видимости.
Note
Поиск выполняется для каждого типа по порядку, что иногда может приводить к неожиданным результатам. Следующий код напечатает “In trait impl!”, потому что методы
&selfищутся первыми, и метод трейта находится до того, как будет найден метод&mut selfструктуры.struct Foo {} trait Bar { fn bar(&self); } impl Foo { fn bar(&mut self) { println!("In struct impl!") } } impl Bar for Foo { fn bar(&self) { println!("In trait impl!") } } fn main() { let mut f = Foo{}; f.bar(); }
Если это приводит к нескольким возможным кандидатам, то это ошибка, и получатель должен быть преобразован в соответствующий тип получателя для выполнения вызова метода.
Этот процесс не учитывает изменяемость или время жизни получателя, а также то, является ли метод unsafe.
После того как метод найден, если он не может быть вызван по одной (или нескольким) из этих причин, результатом является ошибка компилятора.
Если достигается шаг, на котором существует более одного возможного метода, например, когда обобщенные методы или трейты считаются одинаковыми, то это ошибка компилятора. Эти случаи требуют синтаксиса разрешения неоднозначности вызовов функций для вызовов методов и функций.
2021 Edition differences
До редакции 2021 года, во время поиска видимых методов, если кандидат-тип получателя является типом массива, методы, предоставляемые стандартной библиотекой трейтом
IntoIterator, игнорировались.Редакция, используемая для этой цели, определяется токеном, представляющим имя метода.
Этот особый случай может быть удален в будущем.
Warning
Для трейт-объектов, если существует собственный метод с тем же именем, что и метод трейта, это вызовет ошибку компилятора при попытке вызвать метод в выражении вызова метода. Вместо этого вы можете вызвать метод, используя синтаксис разрешения неоднозначности вызовов функций, в этом случае будет вызван метод трейта, а не собственный метод. Нет способа вызвать собственный метод. Просто не определяйте собственные методы на трейт-объектах с тем же именем, что и метод трейта, и все будет хорошо.
Выражения доступа к полям
Syntax
FieldExpression → Expression . IDENTIFIER
Выражение доступа к полю — это выражение-место, которое вычисляется в местоположение поля структуры или объединения.
Когда операнд изменяем, выражение доступа к полю также является изменяемым.
Синтаксис выражения доступа к полю: выражение, называемое операндом-контейнером, затем ., и, наконец, идентификатор.
Выражения доступа к полям не могут следовать за заключенным в круглые скобки списком выражений, разделенных запятыми, так как это вместо этого парсится как выражение вызова метода. То есть они не могут быть функциональным операндом выражения вызова.
Note
Оберните выражение доступа к полю в выражение в скобках, чтобы использовать его в выражении вызова.
#![allow(unused)] fn main() { struct HoldsCallable<F: Fn()> { callable: F } let holds_callable = HoldsCallable { callable: || () }; // Неверно: Парсится как вызов метода "callable" // holds_callable.callable(); // Верно (holds_callable.callable)(); }
Примеры:
mystruct.myfield;
foo().x;
(Struct {a: 10, b: 20}).a;
(mystruct.function_field)() // Выражение вызова, содержащее выражение доступа к полю
Автоматическое разыменование
Если тип операнда-контейнера реализует Deref или DerefMut в зависимости от того, является ли операнд изменяемым, он автоматически разыменовывается столько раз, сколько необходимо, чтобы сделать доступ к полю возможным.
Этот процесс также сокращенно называется autoderef.
Заимствование
Поля структуры или ссылки на структуру рассматриваются как отдельные сущности при заимствовании.
Если структура не реализует Drop и хранится в локальной переменной, это также применяется к перемещению из каждого из ее полей.
Это также не применяется, если автоматическое разыменование выполняется через пользовательские типы, отличные от Box.
#![allow(unused)] fn main() { struct A { f1: String, f2: String, f3: String } let mut x: A; x = A { f1: "f1".to_string(), f2: "f2".to_string(), f3: "f3".to_string() }; let a: &mut String = &mut x.f1; // x.f1 заимствовано изменяемо let b: &String = &x.f2; // x.f2 заимствовано неизменяемо let c: &String = &x.f2; // Можно заимствовать снова let d: String = x.f3; // Перемещение из x.f3 }
Выражения замыканий
Syntax
ClosureExpression →
async?1
move?
( || | | ClosureParameters? | )
( Expression | -> TypeNoBounds BlockExpression )
ClosureParameters → ClosureParam ( , ClosureParam )* ,?
ClosureParam → OuterAttribute* PatternNoTopAlt ( : Type )?
Выражение замыкания, также известное как лямбда-выражение или лямбда, определяет тип замыкания и вычисляется в значение этого типа.
Синтаксис выражения замыкания: необязательное ключевое слово async, необязательное ключевое слово move, затем разделенный символами трубы (|) список образцов, называемый параметрами замыкания, каждый из которых необязательно сопровождается : и типом, затем необязательный -> и тип, называемый типом возврата, и затем выражение, называемое операндом тела замыкания.
Необязательный тип после каждого образца является аннотацией типа для образца.
Если указан тип возврата, тело замыкания должно быть блоком.
Выражение замыкания обозначает функцию, которая отображает список параметров на выражение, следующее за параметрами.
Так же, как привязка let, параметры замыкания являются неопровержимыми образцами, чья аннотация типа необязательна и будет выведена из контекста, если не указана.
Каждое выражение замыкания имеет уникальный, анонимный тип.
Существенно, что выражения замыкания захватывают свое окружение, чего обычные определения функций не делают.
Без ключевого слова move выражение замыкания выводит, как оно захватывает каждую переменную из своего окружения, предпочитая захват по разделяемой ссылке, эффективно заимствуя все внешние переменные, упомянутые внутри тела замыкания.
При необходимости компилятор выведет, что вместо этого должны быть взяты изменяемые ссылки, или что значения должны быть перемещены или скопированы (в зависимости от их типа) из окружения.
Замыкание можно принудительно заставить захватывать свое окружение путем копирования или перемещения значений, добавив перед ним ключевое слово move.
Это часто используется для обеспечения того, чтобы время жизни замыкания было 'static.
Реализации трейтов замыкания
Какие трейты реализует тип замыкания, зависит от того, как захватываются переменные, типов захваченных переменных и наличия async.
Смотрите главу трейты вызова и приведения для информации о том, как и когда замыкание реализует Fn, FnMut и FnOnce.
Тип замыкания реализует Send и Sync, если тип каждой захваченной переменной также реализует этот трейт.
Асинхронные замыкания
Замыкания, помеченные ключевым словом async, указывают, что они являются асинхронными, аналогично асинхронной функции.
Вызов асинхронного замыкания не выполняет никакой работы, но вместо этого вычисляется в значение, которое реализует Future и соответствует вычислению тела замыкания.
#![allow(unused)] fn main() { async fn takes_async_callback(f: impl AsyncFn(u64)) { f(0).await; f(1).await; } async fn example() { takes_async_callback(async |i| { core::future::ready(i).await; println!("done with {i}."); }).await; } }
2018 Edition differences
Асинхронные замыкания доступны только начиная с Rust 2018.
Пример
В этом примере мы определяем функцию ten_times, которая принимает аргумент функции высшего порядка, а затем вызываем ее с выражением замыкания в качестве аргумента, за которым следует выражение замыкания, которое перемещает значения из своего окружения.
#![allow(unused)] fn main() { fn ten_times<F>(f: F) where F: Fn(i32) { for index in 0..10 { f(index); } } ten_times(|j| println!("hello, {}", j)); // С аннотациями типов ten_times(|j: i32| -> () { println!("hello, {}", j) }); let word = "konnichiwa".to_owned(); ten_times(move |j| println!("{}, {}", word, j)); }
Атрибуты на параметрах замыкания
Атрибуты на параметрах замыкания следуют тем же правилам и ограничениям, что и обычные параметры функции.
-
Квалификатор
asyncне разрешен в редакции 2015. ↩
Циклы и другие прерываемые выражения
Syntax
LoopExpression →
LoopLabel? (
InfiniteLoopExpression
| PredicateLoopExpression
| IteratorLoopExpression
| LabelBlockExpression
)
Rust поддерживает четыре типа выражений циклов:
- Выражение
loopобозначает бесконечный цикл. - Выражение
whileвыполняется до тех пор, пока предикат не станет ложным. - Выражение
forизвлекает значения из итератора, выполняясь до опустошения итератора. - Выражение блока с меткой выполняет цикл ровно один раз, но позволяет досрочно выйти из цикла с помощью
break.
Все четыре типа циклов поддерживают выражения break и метки.
Все, кроме выражений блоков с метками, поддерживают выражения continue.
Только выражения loop и блоки с метками поддерживают вычисление в нетривиальные значения.
Бесконечные циклы
Syntax
InfiniteLoopExpression → loop BlockExpression
Выражение loop повторяет выполнение своего тела непрерывно:
loop { println!("I live."); }.
Выражение loop без связанного выражения break является расходящимся и имеет тип !.
Выражение loop, содержащее связанные выражения break, может завершаться и должно иметь тип, совместимый со значением выражений break.
Циклы с условием
Syntax
PredicateLoopExpression → while Conditions BlockExpression
Выражение цикла while позволяет повторять вычисление блока, пока набор условий остается истинным.
Синтаксис выражения while - это последовательность одного или более операндов условий, разделенных &&,
за которыми следует BlockExpression.
Операнды условий должны быть либо Выражением с булевым типом, либо условным сопоставлением let.
Если все операнды условий вычисляются в true и все образцы let успешно сопоставляются со своими рассматриваемыми выражениями,
то выполняется блок тела цикла.
После успешного выполнения тела цикла операнды условий переоцениваются, чтобы определить, должно ли тело выполняться снова.
Если какой-либо операнд условия вычисляется в false или какой-либо образец let не сопоставляется со своим рассматриваемым выражением,
тело не выполняется, и выполнение продолжается после выражения while.
Выражение while вычисляется в ().
Пример:
#![allow(unused)] fn main() { let mut i = 0; while i < 10 { println!("hello"); i = i + 1; } }
Образцы while let
Образцы let в условии while позволяют привязывать новые переменные в область видимости, когда образец успешно сопоставляется.
Следующие примеры иллюстрируют привязки с использованием образцов let:
#![allow(unused)] fn main() { let mut x = vec![1, 2, 3]; while let Some(y) = x.pop() { println!("y = {}", y); } while let _ = 5 { println!("Неопровержимые образцы всегда истинны"); break; } }
Цикл while let эквивалентен выражению loop, содержащему выражение match, следующим образом.
'label: while let PATS = EXPR {
/* тело цикла */
}
эквивалентно
'label: loop {
match EXPR {
PATS => { /* тело цикла */ },
_ => break,
}
}
Несколько образцов могут быть указаны с оператором |.
Это имеет ту же семантику, что и | в выражениях match:
#![allow(unused)] fn main() { let mut vals = vec![2, 3, 1, 2, 2]; while let Some(v @ 1) | Some(v @ 2) = vals.pop() { // Печатает 2, 2, затем 1 println!("{}", v); } }
Цепочки условий while
Несколько операндов условий могут быть разделены с помощью &&.
Они имеют ту же семантику и ограничения, что и цепочки условий if.
Следующий пример демонстрирует цепочку нескольких выражений, смешивая привязки let и булевы выражения, причем выражения могут ссылаться на привязки образцов из предыдущих выражений:
fn main() { let outer_opt = Some(Some(1i32)); while let Some(inner_opt) = outer_opt && let Some(number) = inner_opt && number == 1 { println!("Peek a boo"); break; } }
Циклы по итераторам
Syntax
IteratorLoopExpression →
for Pattern in Expressionexcept StructExpression BlockExpression
Выражение for - это синтаксическая конструкция для перебора элементов, предоставляемых реализацией std::iter::IntoIterator.
Если итератор выдает значение, это значение сопоставляется с неопровержимым образцом, выполняется тело цикла, а затем управление возвращается в начало цикла for.
Если итератор пуст, выражение for завершается.
Пример цикла for по содержимому массива:
#![allow(unused)] fn main() { let v = &["apples", "cake", "coffee"]; for text in v { println!("I like {}.", text); } }
Пример цикла for по серии целых чисел:
#![allow(unused)] fn main() { let mut sum = 0; for n in 1..11 { sum += n; } assert_eq!(sum, 55); }
Цикл for эквивалентен выражению loop, содержащему выражение match, следующим образом:
'label: for PATTERN in iter_expr {
/* тело цикла */
}
эквивалентно
{
let result = match IntoIterator::into_iter(iter_expr) {
mut iter => 'label: loop {
let mut next;
match Iterator::next(&mut iter) {
Option::Some(val) => next = val,
Option::None => break,
};
let PATTERN = next;
let () = { /* тело цикла */ };
},
};
result
}
IntoIterator, Iterator и Option здесь всегда являются элементами стандартной библиотеки, а не тем, на что эти имена разрешаются в текущей области видимости.
Имена переменных next, iter и val приведены для пояснения, они на самом деле не имеют имен, которые пользователь может набрать.
Note
Внешний
matchиспользуется для обеспечения того, чтобы любые временные значения вiter_exprне были сброшены до завершения цикла.nextобъявляется до присваивания, потому что это чаще приводит к правильному выводу типов.
Метки циклов
Syntax
LoopLabel → LIFETIME_OR_LABEL :
Выражение цикла может опционально иметь метку. Метка записывается как время жизни, предшествующее выражению цикла, как в 'foo: loop { break 'foo; }, 'bar: while false {}, 'humbug: for _ in 0..0 {}.
Если метка присутствует, то вложенные выражения break и continue с метками внутри этого цикла могут выйти из этого цикла или вернуть управление в его начало.
См. выражения break и выражения continue.
Метки следуют правилам гигиены и затенения локальных переменных. Например, этот код напечатает “outer loop”:
#![allow(unused)] fn main() { 'a: loop { 'a: loop { break 'a; } print!("outer loop"); break 'a; } }
'_ не является допустимой меткой цикла.
Выражения break
Syntax
BreakExpression → break LIFETIME_OR_LABEL? Expression?
Когда встречается break, выполнение связанного тела цикла немедленно прекращается, например:
#![allow(unused)] fn main() { let mut last = 0; for x in 1..100 { if x > 12 { break; } last = x; } assert_eq!(last, 12); }
Выражение break обычно связано с самым внутренним циклом loop, for или while, охватывающим выражение break,
но метка может быть использована для указания, на какой охватывающий цикл влияет.
Пример:
#![allow(unused)] fn main() { 'outer: loop { while true { break 'outer; } } }
Выражение break разрешено только в теле цикла и имеет одну из форм break, break 'label или (см. ниже) break EXPR или break 'label EXPR.
Выражения блоков с метками
Syntax
LabelBlockExpression → BlockExpression
Выражения блоков с метками точно такие же, как обычные выражения блоков, за исключением того, что они позволяют использовать выражения break внутри блока.
В отличие от циклов, выражения break внутри выражения блока с меткой должны иметь метку (т.е. метка не является опциональной).
Аналогично, выражения блоков с метками должны начинаться с метки.
#![allow(unused)] fn main() { fn do_thing() {} fn condition_not_met() -> bool { true } fn do_next_thing() {} fn do_last_thing() {} let result = 'block: { do_thing(); if condition_not_met() { break 'block 1; } do_next_thing(); if condition_not_met() { break 'block 2; } do_last_thing(); 3 }; }
Выражения continue
Syntax
ContinueExpression → continue LIFETIME_OR_LABEL?
Когда встречается continue, текущая итерация связанного тела цикла немедленно прекращается, возвращая управление в начало цикла.
В случае цикла while началом являются операнды условий, управляющие циклом.
В случае цикла for началом является выражение-вызов, управляющее циклом.
Как и break, continue обычно связан с самым внутренним охватывающим циклом, но continue 'label может быть использован для указания затронутого цикла.
Выражение continue разрешено только в теле цикла.
break и значения циклов
При связи с циклом loop выражение break может быть использовано для возврата значения из этого цикла с помощью одной из форм break EXPR или break 'label EXPR, где EXPR - это выражение, результат которого возвращается из loop.
Например:
#![allow(unused)] fn main() { let (mut a, mut b) = (1, 1); let result = loop { if b > 10 { break b; } let c = a + b; a = b; b = c; }; // первое число в последовательности Фибоначчи больше 10: assert_eq!(result, 13); }
В случае, если у loop есть связанный break, он не считается расходящимся, и loop должен иметь тип, совместимый с каждым выражением break.
break без выражения считается идентичным break с выражением ().
Выражения диапазонов
Syntax
RangeExpression →
RangeExpr
| RangeFromExpr
| RangeToExpr
| RangeFullExpr
| RangeInclusiveExpr
| RangeToInclusiveExpr
RangeExpr → Expression .. Expression
RangeFromExpr → Expression ..
RangeToExpr → .. Expression
RangeFullExpr → ..
Операторы .. и ..= будут конструировать объект одного из вариантов std::ops::Range (или core::ops::Range) в соответствии со следующей таблицей:
| Конструкция | Синтаксис | Тип | Диапазон |
|---|---|---|---|
| RangeExpr | start..end | std::ops::Range | start ≤ x < end |
| RangeFromExpr | start.. | std::ops::RangeFrom | start ≤ x |
| RangeToExpr | ..end | std::ops::RangeTo | x < end |
| RangeFullExpr | .. | std::ops::RangeFull | - |
| RangeInclusiveExpr | start..=end | std::ops::RangeInclusive | start ≤ x ≤ end |
| RangeToInclusiveExpr | ..=end | std::ops::RangeToInclusive | x ≤ end |
Примеры:
#![allow(unused)] fn main() { 1..2; // std::ops::Range 3..; // std::ops::RangeFrom ..4; // std::ops::RangeTo ..; // std::ops::RangeFull 5..=6; // std::ops::RangeInclusive ..=7; // std::ops::RangeToInclusive }
Следующие выражения эквивалентны.
#![allow(unused)] fn main() { let x = std::ops::Range {start: 0, end: 10}; let y = 0..10; assert_eq!(x, y); }
Диапазоны могут использоваться в циклах for:
#![allow(unused)] fn main() { for i in 1..11 { println!("{}", i); } }
Выражения if
Syntax
IfExpression →
if Conditions BlockExpression
( else ( BlockExpression | IfExpression ) )?
Conditions →
Expressionexcept StructExpression
| LetChain
LetChain → LetChainCondition ( && LetChainCondition )*
LetChainCondition →
Expressionexcept ExcludedConditions
| OuterAttribute* let Pattern = Scrutineeexcept ExcludedConditions
ExcludedConditions →
StructExpression
| LazyBooleanExpression
| RangeExpr
| RangeFromExpr
| RangeInclusiveExpr
| AssignmentExpression
| CompoundAssignmentExpression
Синтаксис выражения if - это последовательность одного или более операндов условий, разделенных &&,
за которой следует последующий блок, любое количество условий else if и блоков, и необязательный завершающий блок else.
Операнды условий должны быть либо Выражением с булевым типом, либо условным сопоставлением let.
Если все операнды условий вычисляются в true и все образцы let успешно сопоставляются со своими рассматриваемыми выражениями,
последующий блок выполняется, и любые последующие блоки else if или else пропускаются.
Если какой-либо операнд условия вычисляется в false или какой-либо образец let не сопоставляется со своим рассматриваемым выражением,
последующий блок пропускается, и оценивается любое последующее условие else if.
Если все условия if и else if вычисляются в false, то выполняется любой блок else.
Выражение if вычисляется в то же значение, что и выполненный блок, или в (), если ни один блок не был выполнен.
Выражение if должно иметь одинаковый тип во всех ситуациях.
#![allow(unused)] fn main() { let x = 3; if x == 4 { println!("x is four"); } else if x == 3 { println!("x is three"); } else { println!("x is something else"); } // `if` может использоваться как выражение. let y = if 12 * 15 > 150 { "Bigger" } else { "Smaller" }; assert_eq!(y, "Bigger"); }
Образцы if let
Образцы let в условии if позволяют привязывать новые переменные в область видимости, когда образец успешно сопоставляется.
Следующие примеры иллюстрируют привязки с использованием образцов let:
#![allow(unused)] fn main() { let dish = ("Ham", "Eggs"); // Это тело будет пропущено, потому что образец опровергнут. if let ("Bacon", b) = dish { println!("Bacon is served with {}", b); } else { // Вместо этого выполняется этот блок. println!("No bacon will be served"); } // Это тело выполнится. if let ("Ham", b) = dish { println!("Ham is served with {}", b); } if let _ = 5 { println!("Irrefutable patterns are always true"); } }
Несколько образцов могут быть указаны с оператором |.
Это имеет ту же семантику, что и | в выражениях match:
#![allow(unused)] fn main() { enum E { X(u8), Y(u8), Z(u8), } let v = E::Y(12); if let E::X(n) | E::Y(n) = v { assert_eq!(n, 12); } }
Цепочки условий
Несколько операндов условий могут быть разделены с помощью &&.
Подобно Ленивому логическому выражению &&, каждый операнд вычисляется слева направо до тех пор, пока операнд не вычислится как false или сопоставление let не завершится неудачей,
в этом случае последующие операнды не вычисляются.
Привязки каждого образца помещаются в область видимости, чтобы быть доступными для следующего операнда условия и последующего блока.
Следующий пример демонстрирует цепочку нескольких выражений, смешивая привязки let и булевы выражения, причем выражения могут ссылаться на привязки образцов из предыдущих выражений:
#![allow(unused)] fn main() { fn single() { let outer_opt = Some(Some(1i32)); if let Some(inner_opt) = outer_opt && let Some(number) = inner_opt && number == 1 { println!("Peek a boo"); } } }
Вышеприведенное эквивалентно следующему без использования цепочек условий:
#![allow(unused)] fn main() { fn nested() { let outer_opt = Some(Some(1i32)); if let Some(inner_opt) = outer_opt { if let Some(number) = inner_opt { if number == 1 { println!("Peek a boo"); } } } } }
Если какой-либо операнд условия является образцом let, то ни один из операндов условий не может быть ленивым логическим оператором || из-за неоднозначности и приоритета с рассматриваемым выражением let.
Если выражение || необходимо, то можно использовать скобки. Например:
#![allow(unused)] fn main() { let foo = Some(123); let condition1 = true; let condition2 = false; // Здесь требуются скобки. if let Some(x) = foo && (condition1 || condition2) { /*...*/ } }
2024 Edition differences
До редакции 2024 года цепочки
letне поддерживаются. То есть грамматика LetChain не разрешена в выраженииif.
Выражения match
Syntax
MatchExpression →
match Scrutinee {
InnerAttribute*
MatchArms?
}
Scrutinee → Expressionexcept StructExpression
MatchArms →
( MatchArm => ( ExpressionWithoutBlock , | ExpressionWithBlock ,? ) )*
MatchArm => Expression ,?
MatchArm → OuterAttribute* Pattern MatchArmGuard?
MatchArmGuard → if Expression
Выражение match ветвится на основе образца.
Точная форма сопоставления зависит от образца.
Выражение match имеет рассматриваемое выражение, которое является значением для сравнения с образцами.
Рассматриваемое выражение и образцы должны иметь одинаковый тип.
match ведет себя по-разному в зависимости от того, является ли рассматриваемое выражение выражением-местом или выражением-значением.
Если рассматриваемое выражение является выражением-значением, оно сначала вычисляется во временное местоположение, и полученное значение последовательно сравнивается с образцами в ветвях до тех пор, пока не будет найден совпадающий образец.
Первая ветвь с совпадающим образцом выбирается в качестве цели ветвления match, любые переменные, привязанные образцом, присваиваются локальным переменным в блоке ветви, и управление переходит в блок.
Когда рассматриваемое выражение является выражением-местом, сопоставление не выделяет временное местоположение; однако привязка по значению может скопировать или переместить из области памяти. Когда это возможно, предпочтительнее сопоставлять по выражениям-местам, так как время жизни этих сопоставлений наследует время жизни выражения-места, а не ограничивается внутренней частью сопоставления.
Пример выражения match:
#![allow(unused)] fn main() { let x = 1; match x { 1 => println!("one"), 2 => println!("two"), 3 => println!("three"), 4 => println!("four"), 5 => println!("five"), _ => println!("something else"), } }
Переменные, привязанные внутри образца, ограничены областью видимости охраны сопоставления и выражения ветви.
Режим привязки (перемещение, копирование или ссылка) зависит от образца.
Несколько образцов сопоставления могут быть объединены с помощью оператора |.
Каждый образец будет проверяться в последовательности слева направо до тех пор, пока не будет найден успешный совпадающий образец.
#![allow(unused)] fn main() { let x = 9; let message = match x { 0 | 1 => "not many", 2 ..= 9 => "a few", _ => "lots" }; assert_eq!(message, "a few"); // Демонстрация порядка сопоставления образцов. struct S(i32, i32); match S(1, 2) { S(z @ 1, _) | S(_, z @ 2) => assert_eq!(z, 1), _ => panic!(), } }
Note
2..=9является Образцом диапазона, а не Выражением диапазона. Таким образом, в ветвях сопоставления могут использоваться только те типы диапазонов, которые поддерживаются образцами диапазонов.
Каждая привязка в каждом образце, разделенном |, должна присутствовать во всех образцах в ветви.
Каждая привязка с тем же именем должна иметь тот же тип и тот же режим привязки.
Охраны сопоставления
Ветви сопоставления могут принимать охраны сопоставления для дальнейшего уточнения критериев сопоставления случая.
Охраны образцов появляются после образца и состоят из выражения типа bool, следующего за ключевым словом if.
Когда образец успешно сопоставляется, выполняется выражение охраны. Если выражение вычисляется в true, образец успешно сопоставляется.
В противном случае проверяется следующий образец, включая другие сопоставления с оператором | в той же ветви.
#![allow(unused)] fn main() { let maybe_digit = Some(0); fn process_digit(i: i32) { } fn process_other(i: i32) { } let message = match maybe_digit { Some(x) if x < 10 => process_digit(x), Some(x) => process_other(x), None => panic!(), }; }
Note
Множественные сопоставления с использованием оператора
|могут вызвать многократное выполнение охраны образца и его побочных эффектов. Например:#![allow(unused)] fn main() { use std::cell::Cell; let i : Cell<i32> = Cell::new(0); match 1 { 1 | _ if { i.set(i.get() + 1); false } => {} _ => {} } assert_eq!(i.get(), 2); }
Охрана образца может ссылаться на переменные, привязанные в образце, за которым она следует.
Перед вычислением охраны берется разделяемая ссылка на часть рассматриваемого выражения, с которой сопоставляется переменная. Во время вычисления охраны эта разделяемая ссылка затем используется при доступе к переменной.
Только когда охрана вычисляется в true, значение перемещается или копируется из рассматриваемого выражения в переменную. Это позволяет использовать разделяемые заимствования внутри охраны без перемещения из рассматриваемого выражения в случае, если охрана не срабатывает.
Более того, удерживая разделяемую ссылку во время вычисления охраны, также предотвращается мутация внутри охраны.
Атрибуты на ветвях сопоставления
Внешние атрибуты разрешены на ветвях сопоставления.
Единственные атрибуты, которые имеют смысл на ветвях сопоставления, это cfg и атрибуты проверки линтеров.
Внутренние атрибуты разрешены непосредственно после открывающей фигурной скобки выражения сопоставления в тех же контекстах выражений, что и атрибуты на выражениях-блоках.
Выражения return
Syntax
ReturnExpression → return Expression?
Выражения возврата обозначаются ключевым словом return.
Вычисление выражения return перемещает его аргумент в предназначенное выходное местоположение для текущего вызова функции, уничтожает текущий кадр активации функции и передает управление вызывающему кадру.
Пример выражения return:
#![allow(unused)] fn main() { fn max(a: i32, b: i32) -> i32 { if a > b { return a; } return b; } }
Выражения await
Syntax
AwaitExpression → Expression . await
Выражение await - это синтаксическая конструкция для приостановки вычисления,
предоставляемого реализацией std::future::IntoFuture, до тех пор, пока данное
будущее (future) не будет готово произвести значение.
Синтаксис выражения await: выражение с типом, который реализует трейт IntoFuture, называемое операндом будущего, затем токен ., и затем ключевое слово await.
Выражения await разрешены только внутри асинхронного контекста, такого как async fn, async замыкание или async блок.
Более конкретно, выражение await имеет следующий эффект.
- Создает будущее, вызывая
IntoFuture::into_futureна операнде будущего. - Вычисляет будущее до будущего
tmp; - Фиксирует
tmpс помощьюPin::new_unchecked; - Это зафиксированное будущее затем опрашивается путем вызова метода
Future::pollи передачи ему текущего контекста задачи; - Если вызов
pollвозвращаетPoll::Pending, то будущее возвращаетPoll::Pending, приостанавливая свое состояние так, что когда окружающий асинхронный контекст переопрашивается, выполнение возвращается к шагу 3; - В противном случае вызов
pollдолжен был вернутьPoll::Ready, в этом случае значение, содержащееся в вариантеPoll::Ready, используется как результат самого выраженияawait.
2018 Edition differences
Выражения await доступны только начиная с Rust 2018.
Контекст задачи
Контекст задачи относится к Context, который был предоставлен текущему асинхронному контексту, когда сам асинхронный контекст был опрошен.
Поскольку выражения await разрешены только в асинхронном контексте, должен быть доступен некоторый контекст задачи.
Приблизительная десугаризация
Фактически, выражение await примерно эквивалентно следующей ненормативной десугаризации:
match operand.into_future() {
mut pinned => loop {
let mut pin = unsafe { Pin::new_unchecked(&mut pinned) };
match Pin::future::poll(Pin::borrow(&mut pin), &mut current_context) {
Poll::Ready(r) => break r,
Poll::Pending => yield Poll::Pending,
}
}
}
где псевдокод yield возвращает Poll::Pending и, при повторном вызове, возобновляет выполнение с этой точки.
Переменная current_context ссылается на контекст, взятый из асинхронного окружения.
Выражения _
Syntax
UnderscoreExpression → _
Выражения подчеркивания, обозначаемые символом _, используются для обозначения
заполнителя в деструктурирующем присваивании.
Они могут появляться только в левой части присваивания.
Обратите внимание, что это отличается от подстановочного образца.
Примеры выражений _:
#![allow(unused)] fn main() { let p = (1, 2); let mut a = 0; (_, a) = p; struct Position { x: u32, y: u32, } Position { x: a, y: _ } = Position{ x: 2, y: 3 }; // неиспользуемый результат, присваивание `_` используется для объявления намерения и удаления предупреждения _ = 2 + 2; // вызывает предупреждение unused_must_use // 2 + 2; // эквивалентная техника с использованием подстановочного образца в привязке let let _ = 2 + 2; }
Паттерны (образцы)
Syntax
Pattern → |? PatternNoTopAlt ( | PatternNoTopAlt )*
PatternNoTopAlt →
PatternWithoutRange
| RangePattern
PatternWithoutRange →
LiteralPattern
| IdentifierPattern
| WildcardPattern
| RestPattern
| ReferencePattern
| StructPattern
| TupleStructPattern
| TuplePattern
| GroupedPattern
| SlicePattern
| PathPattern
| MacroInvocation
Паттерны используются для сопоставления значений со структурами и, опционально, привязки переменных к значениям внутри этих структур. Они также используются в объявлениях переменных и параметрах функций и замыканий.
Паттерн в следующем примере делает четыре вещи:
- Проверяет, заполнено ли поле
carвpersonкаким-либо значением. - Проверяет, находится ли поле
ageв диапазоне от 13 до 19, и привязывает его значение к переменнойperson_age. - Привязывает ссылку на поле
nameк переменнойperson_name. - Игнорирует остальные поля
person. Оставшиеся поля могут иметь любое значение и не привязываются к каким-либо переменным.
#![allow(unused)] fn main() { struct Car; struct Computer; struct Person { name: String, car: Option<Car>, computer: Option<Computer>, age: u8, } let person = Person { name: String::from("John"), car: Some(Car), computer: None, age: 15, }; if let Person { car: Some(_), age: person_age @ 13..=19, name: ref person_name, .. } = person { println!("{} has a car and is {} years old.", person_name, person_age); } }
Паттерны используются в:
- Выражениях
match
- Выражениях
if let
- Выражениях
while let
- Выражениях
for
Деструктуризация
Паттерны могут использоваться для деструктуризации структур, перечислений и кортежей. Деструктуризация разбивает значение на составляющие его части. Используемый синтаксис почти такой же, как при создании таких значений.
В паттерне, выражение-образец которого имеет тип struct, enum или tuple, подстановочный паттерн (_) заменяет одно поле данных, тогда как и так далее или остаточный паттерн (..) заменяет все оставшиеся поля определённого варианта.
При деструктуризации структуры данных с именованными (но не нумерованными) полями разрешается писать fieldname как сокращение для fieldname: fieldname.
#![allow(unused)] fn main() { enum Message { Quit, WriteString(String), Move { x: i32, y: i32 }, ChangeColor(u8, u8, u8), } let message = Message::Quit; match message { Message::Quit => println!("Quit"), Message::WriteString(write) => println!("{}", &write), Message::Move{ x, y: 0 } => println!("move {} horizontally", x), Message::Move{ .. } => println!("other move"), Message::ChangeColor { 0: red, 1: green, 2: _ } => { println!("color change, red: {}, green: {}", red, green); } }; }
Опровержимость
Паттерн называется опровержимым, когда есть возможность, что он не совпадёт со значением, с которым сопоставляется. Неопровержимые паттерны, напротив, всегда совпадают со значением, с которым сопоставляются. Примеры:
#![allow(unused)] fn main() { let (x, y) = (1, 2); // "(x, y)" - неопровержимый паттерн if let (a, 3) = (1, 2) { // "(a, 3)" опровержим и не совпадёт panic!("Shouldn't reach here"); } else if let (a, 4) = (3, 4) { // "(a, 4)" опровержим и совпадёт println!("Matched ({}, 4)", a); } }
Литеральные паттерны
Syntax
LiteralPattern → -? LiteralExpression
Литеральные паттерны сопоставляются точно с тем же значением, что создаётся литералом. Поскольку отрицательные числа не являются литералами, литералы в паттернах могут иметь необязательный знак минус, который действует как оператор отрицания.
Warning
C-строковые и сырые C-строковые литералы принимаются в литеральных паттернах, но
&CStrне реализует структурное равенство (#[derive(Eq, PartialEq)]) и поэтому любой такойmatchна&CStrбудет отклонён с ошибкой типа.
Литеральные паттерны всегда опровержимы.
Примеры:
#![allow(unused)] fn main() { for i in -2..5 { match i { -1 => println!("It's minus one"), 1 => println!("It's a one"), 2|4 => println!("It's either a two or a four"), _ => println!("Matched none of the arms"), } } }
Идентификаторные паттерны
Syntax
IdentifierPattern → ref? mut? IDENTIFIER ( @ PatternNoTopAlt )?
Идентификаторные паттерны привязывают значение, с которым они совпали, к переменной в пространстве имён значений.
Идентификатор должен быть уникальным в пределах паттерна.
Переменная будет затенять любые переменные с тем же именем в области видимости.
Область видимости новой привязки зависит от контекста, где используется паттерн (например, привязка let или ветка match).
Паттерны, состоящие только из идентификатора, возможно с mut, сопоставляются с любым значением и привязывают его к этому идентификатору.
Это наиболее часто используемый паттерн в объявлениях переменных и параметрах функций и замыканий.
#![allow(unused)] fn main() { let mut variable = 10; fn sum(x: i32, y: i32) -> i32 { x + y } }
Чтобы привязать сопоставленное значение паттерна к переменной, используйте синтаксис variable @ subpattern.
Например, следующее привязывает значение 2 к e (не весь диапазон: диапазон здесь является подпаттерном диапазона).
#![allow(unused)] fn main() { let x = 2; match x { e @ 1 ..= 5 => println!("got a range element {}", e), _ => println!("anything"), } }
По умолчанию идентификаторные паттерны привязывают переменную к копии или перемещению из сопоставленного значения в зависимости от того, реализует ли сопоставленное значение Copy.
Это можно изменить для привязки к ссылке с помощью ключевого слова ref или к изменяемой ссылке с помощью ref mut. Например:
#![allow(unused)] fn main() { let a = Some(10); match a { None => (), Some(value) => (), } match a { None => (), Some(ref value) => (), } }
В первом выражении match значение копируется (или перемещается).
Во втором match ссылка на ту же область памяти привязывается к переменной value.
Этот синтаксис необходим, потому что в деструктурирующих подпаттернах оператор & не может быть применён к полям значения.
Например, следующее недопустимо:
#![allow(unused)] fn main() { struct Person { name: String, age: u8, } let value = Person { name: String::from("John"), age: 23 }; if let Person { name: &person_name, age: 18..=150 } = value { } }
Чтобы сделать это допустимым, напишите следующее:
#![allow(unused)] fn main() { struct Person { name: String, age: u8, } let value = Person { name: String::from("John"), age: 23 }; if let Person { name: ref person_name, age: 18..=150 } = value { } }
Таким образом, ref не является чем-то, с чем сопоставляется.
Его цель исключительно в том, чтобы сделать привязку ссылкой, вместо потенциального копирования или перемещения того, что было сопоставлено.
Путевые паттерны имеют приоритет над идентификаторными паттернами.
Note
Когда паттерн является идентификатором с одним сегментом, грамматика неоднозначна в том, означает ли он IdentifierPattern или PathPattern. Эта неоднозначность может быть разрешена только после разрешения имён.
#![allow(unused)] fn main() { const EXPECTED_VALUE: u8 = 42; // ^^^^^^^^^^^^^^ То, что эта константа в области видимости, влияет на то, как // обрабатываются паттерны ниже. fn check_value(x: u8) -> Result<u8, u8> { match x { EXPECTED_VALUE => Ok(x), // ^^^^^^^^^^^^^^ Разбирается как `PathPattern`, который разрешается в // константу `42`. other_value => Err(x), // ^^^^^^^^^^^ Разбирается как `IdentifierPattern`. } } // Если бы `EXPECTED_VALUE` рассматривался как `IdentifierPattern` выше, // этот паттерн всегда бы совпадал, делая функцию всегда возвращающей // `Ok(_)` независимо от ввода. assert_eq!(check_value(42), Ok(42)); assert_eq!(check_value(43), Err(43)); }
Ошибкой является указание ref или ref mut, когда идентификатор затеняет константу.
Идентификаторные паттерны неопровержимы, если подпаттерн @ неопровержим или подпаттерн не указан.
Режимы привязки
Для лучшей эргономики паттерны работают в разных режимах привязки, чтобы упростить привязку ссылок к значениям.
Когда ссылочное значение сопоставляется не ссылочным паттерном, оно будет автоматически обрабатываться как привязка ref или ref mut.
Пример:
#![allow(unused)] fn main() { let x: &Option<i32> = &Some(3); if let Some(y) = x { // y был преобразован в `ref y` и его тип &i32 } }
Нессылочные паттерны включают все паттерны, кроме привязок, подстановочных паттернов (_), const паттернов ссылочных типов и ссылочных паттернов.
Если паттерн привязки явно не имеет ref, ref mut или mut, то он использует режим привязки по умолчанию для определения того, как переменная привязывается.
Режим привязки по умолчанию начинается в режиме “move”, который использует семантику перемещения.
При сопоставлении паттерна компилятор начинает снаружи паттерна и работает внутрь.
Каждый раз, когда ссылка сопоставляется с использованием нессылочного паттерна, она автоматически разыменовывает значение и обновляет режим привязки по умолчанию.
Ссылки устанавливают режим привязки по умолчанию в ref.
Изменяемые ссылки устанавливают режим в ref mut, если только режим уже не ref, в этом случае он остаётся ref.
Если автоматически разыменованное значение всё ещё является ссылкой, оно разыменовывается, и этот процесс повторяется.
Паттерн привязки может явно указывать режим привязки ref или ref mut, или указывать изменяемость с помощью mut, только когда режим привязки по умолчанию — “move”. Например, это не принимается:
#![allow(unused)] fn main() { let [mut x] = &[()]; //~ ERROR let [ref x] = &[()]; //~ ERROR let [ref mut x] = &mut [()]; //~ ERROR }
2024 Edition differences
До редакции 2024 года привязки могли явно указывать режим привязки
refилиref mut, даже когда режим привязки по умолчанию не был “move”, и они могли указывать изменяемость на таких привязках с помощьюmut. В этих редакциях указаниеmutна привязке устанавливало режим привязки в “move” независимо от текущего режима привязки по умолчанию.
Аналогично, ссылочный паттерн может появляться только когда режим привязки по умолчанию — “move”. Например, это не принимается:
#![allow(unused)] fn main() { let [&x] = &[&()]; //~ ERROR }
2024 Edition differences
До редакции 2024 года ссылочные паттерны могли появляться даже когда режим привязки по умолчанию не был “move”, и имели как эффект сопоставления с образцом, так и сброса режима привязки по умолчанию в “move”.
Привязки перемещения и ссылочные привязки могут смешиваться в одном паттерне. Это приведёт к частичному перемещению объекта, к которому привязаны, и объект нельзя будет использовать afterwards. Это применяется только если тип не может быть скопирован.
В примере ниже name перемещается из person.
Попытка использовать person как целое или person.name приведёт к ошибке из-за частичного перемещения.
Пример:
#![allow(unused)] fn main() { struct Person { name: String, age: u8, } let person = Person{ name: String::from("John"), age: 23 }; // `name` перемещается из person и `age` ссылается let Person { name, ref age } = person; }
Подстановочный паттерн
Syntax
WildcardPattern → _
Подстановочный паттерн (символ подчёркивания) сопоставляется с любым значением. Он используется для игнорирования значений, когда они не важны.
Внутри других паттернов он сопоставляется с одним полем данных (в отличие от .., который сопоставляется с оставшимися полями).
В отличие от идентификаторных паттернов, он не копирует, не перемещает и не заимствует значение, с которым сопоставляется.
Примеры:
#![allow(unused)] fn main() { let x = 20; let (a, _) = (10, x); // x всегда сопоставляется с _ assert_eq!(a, 10); // игнорировать параметр функции/замыкания let real_part = |a: f64, _: f64| { a }; // игнорировать поле из структуры struct RGBA { r: f32, g: f32, b: f32, a: f32, } let color = RGBA{r: 0.4, g: 0.1, b: 0.9, a: 0.5}; let RGBA{r: red, g: green, b: blue, a: _} = color; assert_eq!(color.r, red); assert_eq!(color.g, green); assert_eq!(color.b, blue); // принять любой Some, с любым значением let x = Some(10); if let Some(_) = x {} }
Подстановочный паттерн всегда неопровержим.
Остаточный паттерн
Syntax
RestPattern → ..
Остаточный паттерн (лексема ..) действует как паттерн переменной длины, который сопоставляется с нулём или более элементами, которые ещё не были сопоставлены до и после.
Он может использоваться только в кортежных, кортежно-структурных и срезовых паттернах и может появляться только один раз как один из элементов в этих паттернах. Также разрешён в идентификаторном паттерне только для срезовых паттернов.
Остаточный паттерн всегда неопровержим.
Примеры:
#![allow(unused)] fn main() { let words = vec!["a", "b", "c"]; let slice = &words[..]; match slice { [] => println!("slice is empty"), [one] => println!("single element {}", one), [head, tail @ ..] => println!("head={} tail={:?}", head, tail), } match slice { // Игнорировать всё, кроме последнего элемента, который должен быть "!". [.., "!"] => println!("!!!"), // `start` - это срез всего, кроме последнего элемента, который должен быть "z". [start @ .., "z"] => println!("starts with: {:?}", start), // `end` - это срез всего, кроме первого элемента, который должен быть "a". ["a", end @ ..] => println!("ends with: {:?}", end), // 'whole' - это весь срез, а `last` - конечный элемент whole @ [.., last] => println!("the last element of {:?} is {}", whole, last), rest => println!("{:?}", rest), } if let [.., penultimate, _] = slice { println!("next to last is {}", penultimate); } let tuple = (1, 2, 3, 4, 5); // Остаточный паттерн также может использоваться в кортежных и кортежно-структурных паттернах. match tuple { (1, .., y, z) => println!("y={} z={}", y, z), (.., 5) => println!("tail must be 5"), (..) => println!("matches everything else"), } }
Паттерны диапазонов
Syntax
RangePattern →
RangeExclusivePattern
| RangeInclusivePattern
| RangeFromPattern
| RangeToExclusivePattern
| RangeToInclusivePattern
| ObsoleteRangePattern1
RangeExclusivePattern →
RangePatternBound .. RangePatternBound
RangeInclusivePattern →
RangePatternBound ..= RangePatternBound
RangeFromPattern →
RangePatternBound ..
RangeToExclusivePattern →
.. RangePatternBound
RangeToInclusivePattern →
..= RangePatternBound
ObsoleteRangePattern →
RangePatternBound ... RangePatternBound
Паттерны диапазонов сопоставляются со скалярными значениями в пределах диапазона, определённого их границами.
Они состоят из сигилы (.. или ..=) и границы с одной или обеих сторон.
Граница слева от сигилы называется нижней границей. Граница справа называется верхней границей.
Исключающий паттерн диапазона сопоставляется со всеми значениями от нижней границы до, но не включая, верхней границы.
Он записывается как его нижняя граница, за которой следует .., за которой следует верхняя граница.
Например, паттерн 'm'..'p' будет сопоставляться только с 'm', 'n' и 'o', конкретно не включая 'p'.
Включающий паттерн диапазона сопоставляется со всеми значениями от нижней границы до и включая верхнюю границу.
Он записывается как его нижняя граница, за которой следует ..=, за которой следует верхняя граница.
Например, паттерн 'm'..='p' будет сопоставляться только со значениями 'm', 'n', 'o' и 'p'.
Паттерн диапазона “от” сопоставляется со всеми значениями больше или равными нижней границе.
Он записывается как его нижняя граница, за которой следует ...
Например, 1.. будет сопоставляться с любым целым числом больше или равным 1, таким как 1, 9, или 9001, или 9007199254740991 (если оно соответствующего размера), но не 0, и не отрицательными числами для знаковых целых чисел.
Исключающий паттерн диапазона “до” сопоставляется со всеми значениями меньше верхней границы.
Он записывается как .., за которым следует верхняя граница.
Например, ..10 будет сопоставляться с любым целым числом меньше 10, таким как 9, 1, 0, и для знаковых целых типов, все отрицательные значения.
Включающий паттерн диапазона “до” сопоставляется со всеми значениями меньше или равными верхней границе.
Он записывается как ..=, за которым следует верхняя граница.
Например, ..=10 будет сопоставляться с любым целым числом меньше или равным 10, таким как 10, 1, 0, и для знаковых целых типов, все отрицательные значения.
Нижняя граница не может быть больше верхней.
То есть, в a..=b, должно выполняться a ≤ b.
Например, ошибкой является иметь паттерн диапазона 10..=0.
Граница записывается как один из:
- Символьный, байтовый, целочисленный или плавающий литерал.
-, за которым следует целочисленный или плавающий литерал.- Путь.
Note
Мы синтаксически принимаем больше, чем это, для RangePatternBound. Мы позже отвергаем другие вещи семантически.
Если граница записана как путь, после разрешения макросов путь должен разрешаться в константный элемент типа char, целочисленного типа или плавающего типа.
Паттерн диапазона сопоставляется с типом его верхней и нижней границ, которые должны быть одного типа.
Если граница является путём, граница сопоставляется с типом и имеет значение константы, в которую разрешается путь.
Если граница является литералом, граница сопоставляется с типом и имеет значение соответствующего литерального выражения.
Если граница является литералом, предваряемым -, граница сопоставляется с тем же типом, что и соответствующее литеральное выражение, и имеет значение отрицания значения соответствующего литерального выражения.
Для плавающих паттернов диапазонов константа не может быть NaN.
Примеры:
#![allow(unused)] fn main() { let c = 'f'; let valid_variable = match c { 'a'..='z' => true, 'A'..='Z' => true, 'α'..='ω' => true, _ => false, }; let ph = 10; println!("{}", match ph { 0..7 => "acid", 7 => "neutral", 8..=14 => "base", _ => unreachable!(), }); let uint: u32 = 5; match uint { 0 => "zero!", 1.. => "positive number!", }; // использование путей к константам: const TROPOSPHERE_MIN : u8 = 6; const TROPOSPHERE_MAX : u8 = 20; const STRATOSPHERE_MIN : u8 = TROPOSPHERE_MAX + 1; const STRATOSPHERE_MAX : u8 = 50; const MESOSPHERE_MIN : u8 = STRATOSPHERE_MAX + 1; const MESOSPHERE_MAX : u8 = 85; let altitude = 70; println!("{}", match altitude { TROPOSPHERE_MIN..=TROPOSPHERE_MAX => "troposphere", STRATOSPHERE_MIN..=STRATOSPHERE_MAX => "stratosphere", MESOSPHERE_MIN..=MESOSPHERE_MAX => "mesosphere", _ => "outer space, maybe", }); pub mod binary { pub const MEGA : u64 = 1024*1024; pub const GIGA : u64 = 1024*1024*1024; } let n_items = 20_832_425; let bytes_per_item = 12; if let size @ binary::MEGA..=binary::GIGA = n_items * bytes_per_item { println!("It fits and occupies {} bytes", size); } trait MaxValue { const MAX: u64; } impl MaxValue for u8 { const MAX: u64 = (1 << 8) - 1; } impl MaxValue for u16 { const MAX: u64 = (1 << 16) - 1; } impl MaxValue for u32 { const MAX: u64 = (1 << 32) - 1; } // использование квалифицированных путей: println!("{}", match 0xfacade { 0 ..= <u8 as MaxValue>::MAX => "fits in a u8", 0 ..= <u16 as MaxValue>::MAX => "fits in a u16", 0 ..= <u32 as MaxValue>::MAX => "fits in a u32", _ => "too big", }); }
Паттерны диапазонов для целочисленных типов фиксированной ширины и типа char неопровержимы, когда они охватывают весь набор возможных значений типа.
Например, 0u8..=255u8 неопровержим.
Диапазон значений для целочисленного типа — это закрытый диапазон от его минимального до максимального значения.
Диапазон значений для типа char — это именно те диапазоны, содержащие все Unicode Scalar Values: '\u{0000}'..='\u{D7FF}' и '\u{E000}'..='\u{10FFFF}'.
RangeFromPattern не может использоваться как паттерн верхнего уровня для подпаттернов в срезовых паттернах.
Например, паттерн [1.., _] не является допустимым паттерном.
2021 Edition differences
До редакции 2021 года паттерны диапазонов с обеими границами также могли записываться с использованием
...вместо..=, с тем же значением.
Ссылочные паттерны
Syntax
ReferencePattern → ( & | && ) mut? PatternWithoutRange
Ссылочные паттерны разыменовывают указатели, с которыми сопоставляются, и, таким образом, заимствуют их.
Например, эти два сопоставления с x: &i32 эквивалентны:
#![allow(unused)] fn main() { let int_reference = &3; let a = match *int_reference { 0 => "zero", _ => "some" }; let b = match int_reference { &0 => "zero", _ => "some" }; assert_eq!(a, b); }
Грамматическая продукция для ссылочных паттернов должна сопоставлять лексему &&, чтобы сопоставить ссылку на ссылку, потому что это отдельная лексема, а не две лексемы &.
Добавление ключевого слова mut разыменовывает изменяемую ссылку. Изменяемость должна совпадать с изменяемостью ссылки.
Ссылочные паттерны всегда неопровержимы.
Структурные паттерны
Syntax
StructPattern →
PathInExpression {
StructPatternElements?
}
StructPatternElements →
StructPatternFields ( , | , StructPatternEtCetera )?
| StructPatternEtCetera
StructPatternFields →
StructPatternField ( , StructPatternField )*
StructPatternField →
OuterAttribute*
(
TUPLE_INDEX : Pattern
| IDENTIFIER : Pattern
| ref? mut? IDENTIFIER
)
Структурные паттерны сопоставляются со значениями структур, перечислений и объединений, которые соответствуют всем критериям, определённым их подпаттернами. Они также используются для деструктуризации значения структуры, перечисления или объединения.
В структурном паттерне поля ссылаются по имени, индексу (в случае кортежных структур) или игнорируются с помощью ..:
#![allow(unused)] fn main() { struct Point { x: u32, y: u32, } let s = Point {x: 1, y: 1}; match s { Point {x: 10, y: 20} => (), Point {y: 10, x: 20} => (), // порядок не имеет значения Point {x: 10, ..} => (), Point {..} => (), } struct PointTuple ( u32, u32, ); let t = PointTuple(1, 2); match t { PointTuple {0: 10, 1: 20} => (), PointTuple {1: 10, 0: 20} => (), // порядок не имеет значения PointTuple {0: 10, ..} => (), PointTuple {..} => (), } enum Message { Quit, Move { x: i32, y: i32 }, } let m = Message::Quit; match m { Message::Quit => (), Message::Move {x: 10, y: 20} => (), Message::Move {..} => (), } }
Если .. не используется, структурный паттерн, используемый для сопоставления со структурой, должен указывать все поля:
#![allow(unused)] fn main() { struct Struct { a: i32, b: char, c: bool, } let mut struct_value = Struct{a: 10, b: 'X', c: false}; match struct_value { Struct{a: 10, b: 'X', c: false} => (), Struct{a: 10, b: 'X', ref c} => (), Struct{a: 10, b: 'X', ref mut c} => (), Struct{a: 10, b: 'X', c: _} => (), Struct{a: _, b: _, c: _} => (), } }
Структурный паттерн, используемый для сопоставления с объединением, должен указывать ровно одно поле (см. Сопоставление с образцом для объединений).
Синтаксис IDENTIFIER сопоставляется с любым значением и привязывает его к переменной с тем же именем, что и данное поле. Это сокращение для fieldname: fieldname. Квалификаторы ref и mut могут быть включены с поведением, описанным в patterns.ident.ref.
#![allow(unused)] fn main() { struct Struct { a: i32, b: char, c: bool, } let struct_value = Struct{a: 10, b: 'X', c: false}; let Struct { a, b, c } = struct_value; }
Структурный паттерн опровержим, если PathInExpression разрешается в конструктор перечисления с более чем одним вариантом, или один из его подпаттернов опровержим.
Структурный паттерн сопоставляется со структурой, объединением или вариантом перечисления, конструктор которого разрешается из PathInExpression в пространстве имён типов. См. patterns.tuple-struct.namespace для более подробной информации.
Кортежно-структурные паттерны
Syntax
TupleStructPattern → PathInExpression ( TupleStructItems? )
TupleStructItems → Pattern ( , Pattern )* ,?
Кортежно-структурные паттерны сопоставляются со значениями кортежных структур и перечислений, которые соответствуют всем критериям, определённым их подпаттернами. Они также используются для деструктуризации значения кортежной структуры или перечисления.
Кортежно-структурный паттерн опровержим, если PathInExpression разрешается в конструктор перечисления с более чем одним вариантом, или один из его подпаттернов опровержим.
Кортежно-структурный паттерн сопоставляется с кортежной структурой или кортежным вариантом перечисления, конструктор которого разрешается из PathInExpression в пространстве имён значений.
Note
И наоборот, структурный паттерн для кортежной структуры или кортежного варианта перечисления, например
S { 0: _ }, сопоставляется с кортежной структурой или вариантом, конструктор которого разрешается в пространстве имён типов.enum E1 { V(u16) } enum E2 { V(u32) } // Импортировать `E1::V` только из пространства имён типов. mod _0 { const V: () = (); // Для маскирования пространства имён. pub(super) use super::E1::*; } use _0::*; // Импортировать `E2::V` только из пространства имён значений. mod _1 { struct V {} // Для маскирования пространства имён. pub(super) use super::E2::*; } use _1::*; fn f() { // Этот структурный паттерн сопоставляется с кортежным // вариантом перечисления, конструктор которого был найден в пространстве имён // типов. let V { 0: ..=u16::MAX } = (loop {}) else { loop {} }; // Этот кортежно-структурный паттерн сопоставляется с кортежным // вариантом перечисления, конструктор которого был найден в пространстве имён // значений. let V(..=u32::MAX) = (loop {}) else { loop {} }; } // Требуется из-за необычного поведения `super` внутри функций. fn main() {}Языковая команда приняла определённые решения, такие как в PR #138458, которые поднимают вопросы о желательности использования пространства имён значений таким образом для паттернов, как описано в PR #140593. Возможно, благоразумно не полагаться на этот нюанс в вашем коде.
Кортежные паттерны
Syntax
TuplePattern → ( TuplePatternItems? )
TuplePatternItems →
Pattern ,
| RestPattern
| Pattern ( , Pattern )+ ,?
Кортежные паттерны сопоставляются со значениями кортежей, которые соответствуют всем критериям, определённым их подпаттернами. Они также используются для деструктуризации кортежа.
Форма (..) с единственным RestPattern является специальной формой, которая не требует запятой и сопоставляется с кортежем любого размера.
Кортежный паттерн опровержим, когда один из его подпаттернов опровержим.
Пример использования кортежных паттернов:
#![allow(unused)] fn main() { let pair = (10, "ten"); let (a, b) = pair; assert_eq!(a, 10); assert_eq!(b, "ten"); }
Группированные паттерны
Syntax
GroupedPattern → ( Pattern )
Заключение паттерна в круглые скобки может использоваться для явного управления приоритетом составных паттернов.
Например, ссылочный паттерн рядом с паттерном диапазона, такой как &0..=5, неоднозначен и не разрешён, но может быть выражен с помощью круглых скобок.
#![allow(unused)] fn main() { let int_reference = &3; match int_reference { &(0..=5) => (), _ => (), } }
Срезовые паттерны
Syntax
SlicePattern → [ SlicePatternItems? ]
SlicePatternItems → Pattern ( , Pattern )* ,?
Срезовые паттерны могут сопоставляться как с массивами фиксированного размера, так и со срезами динамического размера.
#![allow(unused)] fn main() { // Фиксированный размер let arr = [1, 2, 3]; match arr { [1, _, _] => "starts with one", [a, b, c] => "starts with something else", }; }
#![allow(unused)] fn main() { // Динамический размер let v = vec![1, 2, 3]; match v[..] { [a, b] => { /* эта ветка не применится, потому что длина не совпадает */ } [a, b, c] => { /* эта ветка применится */ } _ => { /* этот подстановочный знак требуется, поскольку длина не известна статически */ } }; }
Срезовые паттерны неопровержимы при сопоставлении с массивом, пока каждый элемент неопровержим.
При сопоставлении со срезом они неопровержимы только в форме с единственным .. остаточным паттерном или идентификаторным паттерном с .. остаточным паттерном в качестве подпаттерна.
Внутри среза паттерн диапазона без обеих границ должен быть заключён в круглые скобки, как в (a..), чтобы прояснить, что он предназначен для сопоставления с одним элементом среза.
Паттерн диапазона с обеими границами, такой как a..=b, не требуется заключать в круглые скобки.
Путевые паттерны
Syntax
PathPattern → PathExpression
Путевые паттерны — это паттерны, которые ссылаются либо на константные значения, либо на структуры или варианты перечислений, которые не имеют полей.
Неквалифицированные путевые паттерны могут ссылаться на:
- варианты перечислений
- структуры
- константы
- ассоциированные константы
Квалифицированные путевые паттерны могут ссылаться только на ассоциированные константы.
Путевые паттерны неопровержимы, когда они ссылаются на структуры или вариант перечисления, когда перечисление имеет только один вариант, или на константу, тип которой неопровержим. Они опровержимы, когда ссылаются на опровержимые константы или варианты перечислений для перечислений с несколькими вариантами.
Константные паттерны
Когда константа C типа T используется как паттерн, мы сначала проверяем, что T: PartialEq.
Кроме того, мы требуем, чтобы значение C имело (рекурсивное) структурное равенство, которое рекурсивно определяется следующим образом:
- Целые числа, а также значения
str,boolиcharвсегда имеют структурное равенство.
- Кортежи, массивы и срезы имеют структурное равенство, если все их поля/элементы имеют структурное равенство.
(В частности,
()и[]всегда имеют структурное равенство.)
- Ссылки имеют структурное равенство, если значение, на которое они указывают, имеет структурное равенство.
- Значение типа
structилиenumимеет структурное равенство, если его экземплярPartialEqявляется производным через#[derive(PartialEq)], и все поля (для перечислений: активного варианта) имеют структурное равенство.
- Необработанный указатель имеет структурное равенство, если он был определён как целочисленная константа (и затем приведён/трансмутирован).
- Плавающее значение имеет структурное равенство, если оно не
NaN.
- Ничто другое не имеет структурного равенства.
В частности, значение C должно быть известно во время построения паттерна (что происходит до мономорфизации).
Это означает, что ассоциированные константы, включающие обобщённые параметры, не могут использоваться как паттерны.
Значение C не должно содержать никаких ссылок на изменяемые статические переменные (static mut элементы или внутренне изменяемые static элементы) или extern статические переменные.
После обеспечения выполнения всех условий значение константы преобразуется в паттерн и теперь ведёт себя точно так, как если бы этот паттерн был написан напрямую.
В частности, он полностью участвует в проверке исчерпываемости.
(Для необработанных указателей константы — единственный способ записать такие паттерны. Только _ когда-либо считается исчерпывающим для этих типов.)
ИЛИ-паттерны
ИЛИ-паттерны — это паттерны, которые сопоставляются с одним из двух или более подпаттернов (например, A | B | C).
Они могут вкладываться произвольно.
Синтаксически ИЛИ-паттерны разрешены в любом из мест, где разрешены другие паттерны (представленные продукцией Pattern), за исключением let-привязок и аргументов функций и замыканий (представленных продукцией PatternNoTopAlt).
Статическая семантика
-
Для паттерна
p | qна некоторой глубине для произвольных паттерновpиqпаттерн считается некорректным, если:- тип, выведенный для
p, не унифицируется с типом, выведенным дляq, или - один и тот же набор привязок не вводится в
pиq, или - тип любых двух привязок с одним и тем же именем в
pиqне унифицируется относительно типов или режимов привязки.
Унификация типов во всех упомянутых случаях точна, и неявные приведения типов не применяются.
- тип, выведенный для
- При проверке типа выражения
match e_s { a_1 => e_1, ... a_n => e_n }, для каждой ветки matcha_i, которая содержит паттерн формыp_i | q_i, паттернp_i | q_iсчитается некорректным, если на глубинеd, где он существует фрагментe_sна глубинеd, тип фрагмента выражения не унифицируется сp_i | q_i.
-
Относительно проверки исчерпываемости паттерн
p | qсчитается покрывающимp, а такжеq. Для некоторого конструктораc(x, ..)применяется дистрибутивный закон, такой чтоc(p | q, ..rest)покрывает тот же набор значений, что иc(p, ..rest) | c(q, ..rest). Это может применяться рекурсивно, пока не останется больше вложенных паттернов формыp | q, кроме тех, что существуют на верхнем уровне.Обратите внимание, что под “конструктором” мы подразумеваем не кортежно-структурные паттерны, а скорее паттерн для любого продуктивного типа. Это включает варианты перечислений, кортежные структуры, структуры с именованными полями, массивы, кортежи и срезы.
Динамическая семантика
- Динамическая семантика сопоставления с образцом выражения-образца
e_sс паттерномc(p | q, ..rest)на глубинеd, гдеc— некоторый конструктор,pиq— произвольные паттерны, аrest— опционально любые оставшиеся потенциальные факторы вc, определяется как такая же, как уc(p, ..rest) | c(q, ..rest).
Приоритет с другими неразделёнными паттернами
Как показано в других местах этой главы, существует несколько типов паттернов, которые синтаксически неразделённы, включая идентификаторные паттерны, ссылочные паттерны и ИЛИ-паттерны.
ИЛИ-паттерны всегда имеют самый низкий приоритет.
Это позволяет нам зарезервировать синтаксическое пространство для возможной будущей функции указания типа и также уменьшить неоднозначность.
Например, x @ A(..) | B(..) приведёт к ошибке, что x не привязан во всех паттернах.
&A(x) | B(x) приведёт к несоответствию типа между x в разных подпаттернах.
-
Синтаксис ObsoleteRangePattern был удалён в редакции 2021. ↩
Type system
Типы
Каждая переменная, элемент и значение в программе Rust имеет тип. Тип значения определяет интерпретацию памяти, содержащей его, и операции, которые могут быть выполнены над значением.
Встроенные типы тесно интегрированы в язык нетривиальными способами, которые невозможно эмулировать в пользовательских типах.
Пользовательские типы имеют ограниченные возможности.
Список типов:
- Примитивные типы:
- Логический —
bool - Числовые — целочисленные и с плавающей запятой
- Текстовые —
charиstr - Never —
!— тип без значений
- Логический —
- Последовательные типы:
- Пользовательские типы:
- Функциональные типы:
- Указательные типы:
- Трейт-типы:
Выражения типов
Syntax
Type →
TypeNoBounds
| ImplTraitType
| TraitObjectType
TypeNoBounds →
ParenthesizedType
| ImplTraitTypeOneBound
| TraitObjectTypeOneBound
| TypePath
| TupleType
| NeverType
| RawPointerType
| ReferenceType
| ArrayType
| SliceType
| InferredType
| QualifiedPathInType
| BareFunctionType
| MacroInvocation
Выражение типа, как определено в грамматическом правиле Type выше, — это синтаксис для ссылки на тип. Оно может ссылаться на:
- Пути типов, которые могут ссылаться на:
- Примитивные типы (логический, числовые, текстовые).
- Пути к элементу (структура, перечисление, объединение, псевдоним типа, трейт).
- Путь
Self, гдеSelf— реализующий тип. - Обобщённые параметры типов.
- Указательные типы (ссылка, необработанный указатель, указатель на функцию).
- Выводимый тип, который просит компилятор определить тип.
- Круглые скобки, которые используются для устранения неоднозначности.
- Трейт-типы: Трейт-объекты и impl trait.
- Макросы, которые раскрываются в выражение типа.
Типы в круглых скобках
Syntax
ParenthesizedType → ( Type )
В некоторых ситуациях комбинация типов может быть неоднозначной. Используйте круглые скобки
вокруг типа, чтобы избежать неоднозначности. Например, оператор + для границ типов внутри ссылочного типа неясен, где
применяется граница, поэтому использование круглых скобок обязательно. Грамматические правила, которые
требуют этого устранения неоднозначности, используют правило TypeNoBounds вместо
Type.
#![allow(unused)] fn main() { use std::any::Any; type T<'a> = &'a (dyn Any + Send); }
Рекурсивные типы
Номинальные типы — структуры, перечисления и объединения — могут быть
рекурсивными. То есть, каждый вариант enum или поле struct или union может
ссылаться, прямо или косвенно, на охватывающий тип enum или struct
сам.
Такая рекурсия имеет ограничения:
- Рекурсивные типы должны включать номинальный тип в рекурсии (не просто псевдонимы типов или другие структурные типы, такие как массивы или кортежи). Так что
type Rec = &'static [Rec]не разрешено. - Размер рекурсивного типа должен быть конечным; другими словами, рекурсивные поля типа должны быть указательными типами.
Пример рекурсивного типа и его использования:
#![allow(unused)] fn main() { enum List<T> { Nil, Cons(T, Box<List<T>>) } let a: List<i32> = List::Cons(7, Box::new(List::Cons(13, Box::new(List::Nil)))); }
Логический тип
#![allow(unused)] fn main() { let b: bool = true; }
Логический тип или bool — это примитивный тип данных, который может принимать одно из двух значений, называемых true (истина) и false (ложь).
Значения этого типа могут быть созданы с помощью выражения-литерала с использованием
ключевых слов true и false, соответствующих значениям с теми же именами.
Этот тип является частью прелюдии языка с именем bool.
Объект с логическим типом имеет размер и выравнивание по 1 байту каждый.
Значение false имеет битовый шаблон 0x00, а значение true имеет битовый шаблон
0x01. Неопределенное поведение — для объекта с логическим типом иметь
любой другой битовый шаблон.
Логический тип является типом многих операндов в различных выражениях:
- Операнд условия в выражениях if и выражениях while
- Операнды в выражениях ленивых логических операторов
Note
Логический тип ведет себя похоже, но не является перечислимым типом. На практике это в основном означает, что конструкторы не связаны с типом (например,
bool::true).
Как и все примитивы, логический тип реализует
трейты Clone, Copy, Sized,
Send и Sync.
Note
Смотрите документацию стандартной библиотеки для библиотечных операций.
Операции над логическими значениями
При использовании определенных операторных выражений с логическим типом для его операндов, они вычисляются с использованием правил булевой логики.
Логическое НЕ
b | !b |
|---|---|
true | false |
false | true |
Логическое ИЛИ
a | b | a | b |
|---|---|---|
true | true | true |
true | false | true |
false | true | true |
false | false | false |
Логическое И
a | b | a & b |
|---|---|---|
true | true | true |
true | false | false |
false | true | false |
false | false | false |
Логическое исключающее ИЛИ
a | b | a ^ b |
|---|---|---|
true | true | false |
true | false | true |
false | true | true |
false | false | false |
Сравнения
a | b | a == b |
|---|---|---|
true | true | true |
true | false | false |
false | true | false |
false | false | true |
a | b | a > b |
|---|---|---|
true | true | false |
true | false | true |
false | true | false |
false | false | false |
a != bэквивалентно!(a == b)
a >= bэквивалентноa == b | a > b
a < bэквивалентно!(a >= b)
a <= bэквивалентноa == b | a < b
Валидность битов
Единственный байт bool гарантированно инициализирован (другими словами,
transmute::<bool, u8>(...) всегда безопасен — но поскольку некоторые битовые шаблоны
являются недопустимыми для bool, обратное не всегда безопасно).
Числовые типы
Целочисленные типы
Беззнаковые целочисленные типы состоят из:
| Тип | Минимум | Максимум |
|---|---|---|
u8 | 0 | 28-1 |
u16 | 0 | 216-1 |
u32 | 0 | 232-1 |
u64 | 0 | 264-1 |
u128 | 0 | 2128-1 |
Знаковые целочисленные типы в дополнительном коде состоят из:
| Тип | Минимум | Максимум |
|---|---|---|
i8 | -(27) | 27-1 |
i16 | -(215) | 215-1 |
i32 | -(231) | 231-1 |
i64 | -(263) | 263-1 |
i128 | -(2127) | 2127-1 |
Типы с плавающей точкой
Типы с плавающей точкой IEEE 754-2008 “binary32” и “binary64” - это f32 и
f64 соответственно.
Машинно-зависимые целочисленные типы
Тип usize - это беззнаковый целочисленный тип с тем же количеством битов, что и
указательный тип платформы. Он может представлять каждый адрес памяти в процессе.
Note
Хотя
usizeможет представлять каждый адрес, преобразование указателя вusizeне обязательно является обратимой операцией. Для получения дополнительной информации смотрите документацию по выражениям приведения типа,std::ptrи в частности provenance.
Тип isize - это знаковый целочисленный тип с тем же количеством битов, что и
указательный тип платформы. Теоретическая верхняя граница размера объекта и массива
- это максимальное значение
isize. Это гарантирует, чтоisizeможет быть использован для вычисления разниц между указателями на объект или массив и может адресовать каждый байт внутри объекта вместе с одним байтом после конца.
usize и isize имеют ширину не менее 16 бит.
Note
Многие части кода на Rust могут предполагать, что указатели,
usizeиisizeявляются либо 32-битными, либо 64-битными. Как следствие, поддержка 16-битных указателей ограничена и может требовать явной заботы и подтверждения от библиотеки для поддержки.
Валидность битов
Для каждого числового типа T валидность битов T эквивалентна валидности битов
[u8; size_of::<T>()]. Неинициализированный байт не является допустимым u8.
Текстовые типы
Типы char и str содержат текстовые данные.
Значение типа char - это скалярное значение Юникода (т.е. кодовая точка, которая
не является суррогатом), представленное как 32-битное беззнаковое слово в диапазоне от 0x0000 до 0xD7FF
или от 0xE000 до 0x10FFFF.
Немедленное неопределенное поведение - создание
char, который выходит за пределы этого диапазона. [char] фактически является строкой UCS-4 / UTF-32
длиной 1.
Значение типа str представлено так же, как [u8], срез
8-битных беззнаковых байтов. Однако стандартная библиотека Rust делает дополнительные предположения
о str: методы, работающие с str, предполагают и гарантируют, что данные там
являются допустимыми UTF-8. Вызов метода str с буфером не в UTF-8 может вызвать
неопределенное поведение сейчас или в будущем.
Поскольку str является динамически sized типом, он может быть создан только через
тип указателя, такой как &str. Расположение &str такое же, как расположение
&[u8].
Расположение и валидность битов
char гарантированно имеет тот же размер и выравнивание, что и u32 на всех платформах.
Каждый байт char гарантированно инициализирован (другими словами,
transmute::<char, [u8; size_of::<char>()]>(...) всегда безопасен — но поскольку
некоторые битовые шаблоны являются недопустимыми для char, обратное не всегда безопасно).
Тип never
Syntax
NeverType → !
Тип never ! — это тип без значений, представляющий результат
вычислений, которые никогда не завершаются.
Выражения типа ! могут быть приведены к любому другому типу.
Тип ! в настоящее время может появляться только в типах возвращаемых значений функций,
указывая, что это расходящаяся функция, которая никогда не возвращается.
#![allow(unused)] fn main() { fn foo() -> ! { panic!("Этот вызов никогда не возвращается."); } }
#![allow(unused)] fn main() { unsafe extern "C" { pub safe fn no_return_extern_func() -> !; } }
Типы кортежей
Типы кортежей — это семейство структурных типов1 для гетерогенных списков других типов.
Синтаксис типа кортежа — это заключенный в круглые скобки, разделенный запятыми список типов.
Одноэлементные кортежи требуют запятую после типа их элемента, чтобы быть отличимыми от типа в скобках.
Тип кортежа имеет количество полей, равное длине списка типов.
Это количество полей определяет арность кортежа.
Кортеж с n полями называется n-арным кортежем.
Например, кортеж с 2 полями является 2-арным кортежем.
Поля кортежей именуются с использованием возрастающих числовых имен, соответствующих их позиции в списке типов.
Первое поле — 0.
Второе поле — 1.
И так далее.
Тип каждого поля — это тип той же позиции в списке типов кортежа.
Для удобства и по историческим причинам тип кортежа без полей (()) часто называют единицей или типом единицы.
Его единственное значение также называют единицей или значением единицы.
Некоторые примеры типов кортежей:
()(единица)(i32,)(1-арный кортеж)(f64, f64)(String, i32)(i32, String)(другой тип по сравнению с предыдущим примером)(i32, f64, Vec<String>, Option<bool>)
Значения этого типа конструируются с использованием выражения кортежа. Кроме того, различные выражения будут производить значение единицы, если для них нет другого значимого значения для вычисления.
К полям кортежа можно обращаться либо с помощью выражения индексации кортежа, либо сопоставления с образцом.
-
Структурные типы всегда эквивалентны, если их внутренние типы эквивалентны. Для номинальной версии кортежей см. кортежные структуры. ↩
Типы массивов
Syntax
ArrayType → [ Type ; Expression ]
Массив — это последовательность фиксированного размера из N элементов типа T. Тип массива
записывается как [T; N].
Размер является константным выражением, которое вычисляется в usize.
Примеры:
#![allow(unused)] fn main() { // Массив в стеке let array: [i32; 3] = [1, 2, 3]; // Массив в куче, приведенный к срезу let boxed_array: Box<[i32]> = Box::new([1, 2, 3]); }
Все элементы массивов всегда инициализированы, и доступ к массиву всегда проверяется на границы в безопасных методах и операторах.
Note
Стандартный тип библиотеки
Vec<T>предоставляет изменяемый размером массив в куче.
Типы срезов
Срез — это динамически sized тип, представляющий “вид” в последовательность
элементов типа T. Тип среза записывается как [T].
Типы срезов обычно используются через типы указателей. Например:
&[T]: “разделяемый срез”, часто называемый просто “срезом”. Он не владеет данными, на которые указывает; он заимствует их.&mut [T]: “изменяемый срез”. Он изменяемо заимствует данные, на которые указывает.Box<[T]>: “упакованный срез”
Примеры:
#![allow(unused)] fn main() { // Массив в куче, приведенный к срезу let boxed_array: Box<[i32]> = Box::new([1, 2, 3]); // (Разделяемый) срез массива let slice: &[i32] = &boxed_array[..]; }
Все элементы срезов всегда инициализированы, и доступ к срезу всегда проверяется на границы в безопасных методах и операторах.
Типы структур
Тип struct — это гетерогенное произведение других типов, называемых
полями типа.1
Новые экземпляры struct могут быть созданы с помощью выражения структуры.
Расположение в памяти struct по умолчанию не определено, чтобы позволить
компилятору выполнять оптимизации, такие как переупорядочивание полей, но оно может быть зафиксировано с помощью
атрибута repr. В любом случае поля могут быть указаны в любом порядке в
соответствующем выражении структуры; результирующее значение struct всегда
будет иметь одинаковое расположение в памяти.
Поля struct могут быть квалифицированы модификаторами видимости, чтобы разрешить
доступ к данным в структуре вне модуля.
Тип кортежной структуры похож на тип структуры, за исключением того, что поля являются анонимными.
Тип структуры-единицы похож на тип структуры, за исключением того, что у него нет полей. Единственное значение, созданное соответствующим выражением структуры, является единственным значением, которое населяет такой тип.
-
Типы
structаналогичны типамstructв C, записываемым типам семейства ML или структурным типам семейства Lisp. ↩
Перечислимые типы
Перечислимый тип — это номинальный, гетерогенный тип разделенного объединения, обозначаемый
именем элемента enum. 1
Элемент enum объявляет как тип, так и ряд вариантов, каждый из
которых имеет независимое имя и синтаксис структуры, кортежной структуры или
структуры-единицы.
Новые экземпляры enum могут быть созданы с помощью выражения структуры.
Любое значение enum потребляет столько памяти, сколько самый большой вариант для его
соответствующего типа enum, а также размер, необходимый для хранения дискриминанта.
Типы перечислений не могут быть обозначены структурно как типы, но должны быть обозначены
именованной ссылкой на элемент enum.
-
Тип
enumаналогичен объявлению конструктораdataв Haskell или pick ADT в Limbo. ↩
Типы объединений
Тип объединения — это номинальное, гетерогенное объединение в стиле C, обозначаемое именем
элемента union.
Объединения не имеют понятия “активного поля”. Вместо этого каждый доступ к объединению преобразует части содержимого объединения в тип доступного поля.
Поскольку преобразования могут вызвать неожиданное или неопределенное поведение, unsafe
требуется для чтения из поля объединения.
Типы полей объединения также ограничены подмножеством типов, которое гарантирует, что они никогда не требуют сброса. Смотрите документацию по элементу для дальнейших деталей.
Расположение в памяти union по умолчанию не определено (в частности, поля
не должны быть по смещению 0), но атрибут #[repr(...)] может быть использован для
фиксации расположения.
Типы функциональных элементов
При ссылке на функциональный элемент или конструктор кортежной структуры или варианта перечисления получается значение нулевого размера его типа функционального элемента.
Этот тип явно идентифицирует функцию - ее имя, аргументы типа и ранние аргументы времени жизни (но не поздние аргументы времени жизни, которые назначаются только при вызове функции) - поэтому значение не нужно содержать фактический указатель на функцию, и не требуется косвенное обращение при вызове функции.
Нет синтаксиса, который бы непосредственно ссылался на тип функционального элемента, но
компилятор будет отображать тип как что-то вроде fn(u32) -> i32 {fn_name} в
сообщениях об ошибках.
Поскольку тип функционального элемента явно идентифицирует функцию, типы элементов разных функций - разных элементов или одного и того же элемента с разными обобщениями - различны, и их смешение вызовет ошибку типа:
#![allow(unused)] fn main() { fn foo<T>() { } let x = &mut foo::<i32>; *x = foo::<u32>; //~ ОШИБКА: несоответствие типов }
Однако существует приведение из функциональных элементов в указатели на функции с
той же сигнатурой, которое срабатывает не только когда функциональный элемент используется
там, где ожидается указатель на функцию, но и когда разные типы функциональных элементов с той же сигнатурой встречаются в разных ветвях одного if или
match:
#![allow(unused)] fn main() { let want_i32 = false; fn foo<T>() { } // `foo_ptr_1` здесь имеет тип указателя на функцию `fn()` let foo_ptr_1: fn() = foo::<i32>; // ... и `foo_ptr_2` тоже - это проходит проверку типов. let foo_ptr_2 = if want_i32 { foo::<i32> } else { foo::<u32> }; }
Все функциональные элементы реализуют Copy, Clone, Send и Sync.
Fn, FnMut и FnOnce реализуются, если только функция не имеет любого из следующего:
- квалификатор
unsafe - атрибут
target_feature - ABI, отличный от
"Rust"
Типы замыканий
Выражение замыкания производит значение замыкания с уникальным, анонимным типом, который не может быть записан. Тип замыкания приблизительно эквивалентен структуре, содержащей захваченные значения. Например, следующее замыкание:
#![allow(unused)] fn main() { #[derive(Debug)] struct Point { x: i32, y: i32 } struct Rectangle { left_top: Point, right_bottom: Point } fn f<F : FnOnce() -> String> (g: F) { println!("{}", g()); } let mut rect = Rectangle { left_top: Point { x: 1, y: 1 }, right_bottom: Point { x: 0, y: 0 } }; let c = || { rect.left_top.x += 1; rect.right_bottom.x += 1; format!("{:?}", rect.left_top) }; f(c); // Печатает "Point { x: 2, y: 1 }". }
генерирует тип замыкания примерно следующим образом:
// Примечание: Это не точный перевод, это только для
// иллюстрации.
struct Closure<'a> {
left_top : &'a mut Point,
right_bottom_x : &'a mut i32,
}
impl<'a> FnOnce<()> for Closure<'a> {
type Output = String;
extern "rust-call" fn call_once(self, args: ()) -> String {
self.left_top.x += 1;
*self.right_bottom_x += 1;
format!("{:?}", self.left_top)
}
}
так что вызов f работает так, как если бы это было:
f(Closure{ left_top: &mut rect.left_top, right_bottom_x: &mut rect.right_bottom.x });
Режимы захвата
Режим захвата определяет, как выражение-место из окружения заимствуется или перемещается в замыкание. Режимы захвата:
- Неизменяемое заимствование (
ImmBorrow) — Выражение-место захватывается как разделяемая ссылка. - Уникальное неизменяемое заимствование (
UniqueImmBorrow) — Это похоже на неизменяемое заимствование, но должно быть уникальным, как описано ниже. - Изменяемое заимствование (
MutBorrow) — Выражение-место захватывается как изменяемая ссылка. - Перемещение (
ByValue) — Выражение-место захватывается перемещением значения в замыкание.
Выражения-места из окружения захватываются с первого режима, который совместим с тем, как захваченное значение используется внутри тела замыкания. Режим не зависит от кода, окружающего замыкание, такого как времена жизни задействованных переменных или полей, или самого замыкания.
Значения Copy
Значения, которые реализуют Copy и перемещаются в замыкание, захватываются в режиме ImmBorrow.
#![allow(unused)] fn main() { let x = [0; 1024]; let c = || { let y = x; // x захвачен через ImmBorrow }; }
Захват входных аргументов в async
Асинхронные замыкания всегда захватывают все входные аргументы, независимо от того, используются ли они внутри тела.
Точность захвата
Путь захвата — это последовательность, начинающаяся с переменной из окружения, за которой следует ноль или более проекций мест, примененных к этой переменной.
Проекция места — это доступ к полю, индекс кортежа, разыменование (и автоматические разыменования) или индекс массива или среза, примененный к переменной.
Замыкание заимствует или перемещает путь захвата, который может быть усечен на основе правил, описанных ниже.
Например:
#![allow(unused)] fn main() { struct SomeStruct { f1: (i32, i32), } let s = SomeStruct { f1: (1, 2) }; let c = || { let x = s.f1.1; // s.f1.1 захвачен через ImmBorrow }; c(); }
Здесь путь захвата — это локальная переменная s, за которой следует доступ к полю .f1, а затем индекс кортежа .1.
Это замыкание захватывает неизменяемое заимствование s.f1.1.
Общий префикс
В случае, когда путь захвата и один из предков этого пути оба захватываются замыканием, путь-предок захватывается с наивысшим режимом захвата среди двух захватов, CaptureMode = max(AncestorCaptureMode, DescendantCaptureMode), используя строго слабое упорядочение:
ImmBorrow < UniqueImmBorrow < MutBorrow < ByValue
Заметьте, что это может потребовать применения рекурсивно.
#![allow(unused)] fn main() { // В этом примере есть три разных пути захвата с общим предком: fn move_value<T>(_: T){} let s = String::from("S"); let t = (s, String::from("T")); let mut u = (t, String::from("U")); let c = || { println!("{:?}", u); // u захвачен через ImmBorrow u.1.truncate(0); // u.0 захвачен через MutBorrow move_value(u.0.0); // u.0.0 захвачен через ByValue }; c(); }
В целом это замыкание захватит u через ByValue.
Усечение по самой правой разделяемой ссылке
Путь захвата усекается на самом правом разыменовании в пути захвата, если разыменование применяется к разделяемой ссылке.
Это усечение разрешено, потому что поля, читаемые через разделяемую ссылку, всегда будут читаться через разделяемую ссылку или копию. Это помогает уменьшить размер захвата, когда дополнительная точность не дает никакой выгоды с точки зрения проверки заимствований.
Причина, по которой это самое правое разыменование, заключается в том, чтобы помочь избежать более короткого времени жизни, чем необходимо. Рассмотрим следующий пример:
#![allow(unused)] fn main() { struct Int(i32); struct B<'a>(&'a i32); struct MyStruct<'a> { a: &'static Int, b: B<'a>, } fn foo<'a, 'b>(m: &'a MyStruct<'b>) -> impl FnMut() + 'static { let c = || drop(&m.a.0); c } }
Если бы это захватывало m, то замыкание больше не переживало бы 'static, поскольку m ограничено 'a. Вместо этого оно захватывает (*(*m).a) через ImmBorrow.
Привязки с подстановочным образцом
Замыкания захватывают только данные, которые нужно прочитать.
Привязка значения с подстановочным образцом не считается чтением и, следовательно, не будет захвачена.
Например, следующие замыкания не захватят x:
#![allow(unused)] fn main() { let x = String::from("hello"); let c = || { let _ = x; // x не захвачен }; c(); let c = || match x { // x не захвачен _ => println!("Hello World!") }; c(); }
Это также включает деструктуризацию кортежей, структур и перечислений. Поля, сопоставленные с RestPattern или StructPatternEtCetera, также не считаются прочитанными, и поэтому эти поля не будут захвачены. Следующее иллюстрирует некоторые из них:
#![allow(unused)] fn main() { let x = (String::from("a"), String::from("b")); let c = || { let (first, ..) = x; // захватывает `x.0` через ByValue }; // Первое поле кортежа было перемещено в замыкание. // Второе поле кортежа все еще доступно. println!("{:?}", x.1); c(); }
#![allow(unused)] fn main() { struct Example { f1: String, f2: String, } let e = Example { f1: String::from("first"), f2: String::from("second"), }; let c = || { let Example { f2, .. } = e; // захватывает `e.f2` через ByValue }; // Поле f2 не может быть доступно, так как оно перемещено в замыкание. // Поле f1 все еще доступно. println!("{:?}", e.f1); c(); }
Частичные захваты массивов и срезов не поддерживаются; весь срез или массив всегда захватывается, даже если используется с подстановочным сопоставлением с образцом, индексацией или под-срезами. Например:
#![allow(unused)] fn main() { #[derive(Debug)] struct Example; let x = [Example, Example]; let c = || { let [first, _] = x; // захватывает весь `x` через ByValue }; c(); println!("{:?}", x[1]); // ОШИБКА: заимствование перемещенного значения: `x` }
Значения, сопоставленные с подстановочными символами, все равно должны быть инициализированы.
#![allow(unused)] fn main() { let x: i32; let c = || { let _ = x; // ОШИБКА: использованная привязка `x` не инициализирована }; }
Захват ссылок в контекстах перемещения
Поскольку не разрешено перемещать поля из ссылки, замыкания move будут захватывать только префикс пути захвата, который идет до, но не включая, первое разыменование ссылки.
Сама ссылка будет перемещена в замыкание.
#![allow(unused)] fn main() { struct T(String, String); let mut t = T(String::from("foo"), String::from("bar")); let t_mut_ref = &mut t; let mut c = move || { t_mut_ref.0.push_str("123"); // захватывает `t_mut_ref` через ByValue }; c(); }
Разыменование сырого указателя
Поскольку разыменование сырого указателя unsafe, замыкания будут захватывать только префикс пути захвата, который идет до, но не включая, первое разыменование сырого указателя.
#![allow(unused)] fn main() { struct T(String, String); let t = T(String::from("foo"), String::from("bar")); let t_ptr = &t as *const T; let c = || unsafe { println!("{}", (*t_ptr).0); // захватывает `t_ptr` через ImmBorrow }; c(); }
Поля объединений
Поскольку доступ к полю объединения unsafe, замыкания будут захватывать только префикс пути захвата, который идет до самого объединения.
#![allow(unused)] fn main() { union U { a: (i32, i32), b: bool, } let u = U { a: (123, 456) }; let c = || { let x = unsafe { u.a.0 }; // захватывает `u` через ByValue }; c(); // Это также включает запись в поля. let mut u = U { a: (123, 456) }; let mut c = || { u.b = true; // захватывает `u` через MutBorrow }; c(); }
Ссылка на невыровненные struct
Поскольку создание ссылок на невыровненные поля в структуре является неопределенным поведением,
замыкания будут захватывать только префикс пути захвата, который идет до, но не включая, первый доступ к полю в структуре, которая использует представление packed.
Это включает все поля, даже выровненные, чтобы защититься от проблем совместимости, если какие-либо поля в структуре изменятся в будущем.
#![allow(unused)] fn main() { #[repr(packed)] struct T(i32, i32); let t = T(2, 5); let c = || { let a = t.0; // захватывает `t` через ImmBorrow }; // Копирование из `t` допустимо. let (a, b) = (t.0, t.1); c(); }
Аналогично, взятие адреса невыровненного поля также захватывает всю структуру:
#![allow(unused)] fn main() { #[repr(packed)] struct T(String, String); let mut t = T(String::new(), String::new()); let c = || { let a = std::ptr::addr_of!(t.1); // захватывает `t` через ImmBorrow }; let a = t.0; // ОШИБКА: нельзя переместить из `t.0`, так как оно заимствовано c(); }
но вышеприведенное работает, если оно не упаковано, поскольку захватывает поле точно:
#![allow(unused)] fn main() { struct T(String, String); let mut t = T(String::new(), String::new()); let c = || { let a = std::ptr::addr_of!(t.1); // захватывает `t.1` через ImmBorrow }; // Перемещение здесь разрешено. let a = t.0; c(); }
Box против других реализаций Deref
Реализация трейта Deref для Box обрабатывается иначе, чем другие реализации Deref, поскольку она считается особой сущностью.
Например, рассмотрим примеры с Rc и Box. *rc десугарируется в вызов метода трейта deref, определенного на Rc, но поскольку *box обрабатывается иначе, можно точно захватить содержимое Box.
Box с не-move замыканием
В не-move замыкании, если содержимое Box не перемещается в тело замыкания, содержимое Box точно захватывается.
#![allow(unused)] fn main() { struct S(String); let b = Box::new(S(String::new())); let c_box = || { let x = &(*b).0; // захватывает `(*b).0` через ImmBorrow }; c_box(); // Сравните `Box` с другим типом, реализующим Deref: let r = std::rc::Rc::new(S(String::new())); let c_rc = || { let x = &(*r).0; // захватывает `r` через ImmBorrow }; c_rc(); }
Однако, если содержимое Box перемещается в замыкание, то весь box захватывается. Это делается для того, чтобы минимизировать объем данных, которые нужно переместить в замыкание.
#![allow(unused)] fn main() { // Это тот же пример, что и выше, за исключением того, что замыкание // перемещает значение вместо взятия ссылки на него. struct S(String); let b = Box::new(S(String::new())); let c_box = || { let x = (*b).0; // захватывает `b` через ByValue }; c_box(); }
Box с move замыканием
Аналогично перемещению содержимого Box в не-move замыкании, чтение содержимого Box в move замыкании захватит весь Box.
#![allow(unused)] fn main() { struct S(i32); let b = Box::new(S(10)); let c_box = move || { let x = (*b).0; // захватывает `b` через ByValue }; }
Уникальные неизменяемые заимствования в захватах
Захваты могут происходить через особый вид заимствования, называемый уникальным неизменяемым заимствованием, который не может быть использован где-либо еще в языке и не может быть записан явно. Оно происходит при изменении референта изменяемой ссылки, как в следующем примере:
#![allow(unused)] fn main() { let mut b = false; let x = &mut b; let mut c = || { // ImmBorrow и MutBorrow `x`. let a = &x; *x = true; // `x` захвачен через UniqueImmBorrow }; // Следующая строка является ошибкой: // let y = &x; c(); // Однако следующее допустимо. let z = &x; }
В этом случае заимствование x изменяемо невозможно, потому что x не mut.
Но в то же время заимствование x неизменяемо сделало бы присваивание незаконным,
потому что ссылка & &mut может быть не уникальной, поэтому ее нельзя безопасно использовать для изменения значения.
Поэтому используется уникальное неизменяемое заимствование: оно заимствует x неизменяемо, но, как и изменяемое заимствование, оно должно быть уникальным.
В приведенном выше примере раскомментирование объявления y приведет к ошибке, потому что это нарушит уникальность заимствования x замыканием; объявление z допустимо, потому что время жизни замыкания истекло в конце блока, освободив заимствование.
Трейты вызова и приведения
Типы замыканий все реализуют FnOnce, указывая, что они могут быть вызваны один раз
путем потребления владения замыканием. Кроме того, некоторые замыкания реализуют
более специфичные трейты вызова:
- Замыкание, которое не перемещает никакие захваченные переменные, реализует
FnMut, указывая, что оно может быть вызвано по изменяемой ссылке.
- Замыкание, которое не изменяет и не перемещает никакие захваченные переменные,
реализует
Fn, указывая, что оно может быть вызвано по разделяемой ссылке.
Note
Замыкания
moveвсе равно могут реализовыватьFnилиFnMut, даже если они захватывают переменные перемещением. Это связано с тем, что трейты, реализуемые типом замыкания, определяются тем, что замыкание делает с захваченными значениями, а не тем, как оно их захватывает.
Незахватывающие замыкания — это замыкания, которые ничего не захватывают из своего
окружения. Не-асинхронные, незахватывающие замыкания могут быть приведены к указателям на функции (например, fn())
с соответствующей сигнатурой.
#![allow(unused)] fn main() { let add = |x, y| x + y; let mut x = add(5,7); type Binop = fn(i32, i32) -> i32; let bo: Binop = add; x = bo(5,7); }
Трейты асинхронных замыканий
Асинхронные замыкания имеют дополнительное ограничение на то, реализуют ли они FnMut или Fn.
Future, возвращаемый асинхронным замыканием, имеет схожие характеристики захвата, как замыкание. Он захватывает выражения-места из асинхронного замыкания на основе того, как они используются. Говорят, что асинхронное замыкание одалживает своему Future, если оно имеет любое из следующих свойств:
Futureвключает изменяемый захват.- Асинхронное замыкание захватывает по значению, за исключением случаев, когда значение доступно с проекцией разыменования.
Если асинхронное замыкание одалживает своему Future, то FnMut и Fn не реализуются. FnOnce всегда реализуется.
Пример: Первое условие для изменяемого захвата можно проиллюстрировать следующим:
#![allow(unused)] fn main() { fn takes_callback<Fut: Future>(c: impl FnMut() -> Fut) {} fn f() { let mut x = 1i32; let c = async || { x = 2; // x захвачен через MutBorrow }; takes_callback(c); // ОШИБКА: асинхронное замыкание не реализует `FnMut` } }Второе условие для обычного захвата по значению можно проиллюстрировать следующим:
#![allow(unused)] fn main() { fn takes_callback<Fut: Future>(c: impl Fn() -> Fut) {} fn f() { let x = &1i32; let c = async move || { let a = x + 2; // x захвачен через ByValue }; takes_callback(c); // ОШИБКА: асинхронное замыкание не реализует `Fn` } }Исключение второго условия можно проиллюстрировать использованием разыменования, которое позволяет реализовать
FnиFnMut:#![allow(unused)] fn main() { fn takes_callback<Fut: Future>(c: impl Fn() -> Fut) {} fn f() { let x = &1i32; let c = async move || { let a = *x + 2; }; takes_callback(c); // OK: реализует `Fn` } }
Асинхронные замыкания реализуют AsyncFn, AsyncFnMut и AsyncFnOnce аналогично тому, как обычные замыкания реализуют Fn, FnMut и FnOnce; то есть в зависимости от использования захваченных переменных в его теле.
Другие трейты
Все типы замыканий реализуют Sized. Кроме того, типы замыканий реализуют
следующие трейты, если это разрешено типами захватов, которые они хранят:
Правила для Send и Sync соответствуют таковым для обычных структурных типов, в то время как
Clone и Copy ведут себя так, как если бы они были выведены. Для Clone порядок
клонирования захваченных значений не указан.
Поскольку захваты часто происходят по ссылке, возникают следующие общие правила:
- Замыкание
Sync, если все захваченные значенияSync. - Замыкание
Send, если все значения, захваченные по не-уникальной неизменяемой ссылке, являютсяSync, и все значения, захваченные по уникальной неизменяемой или изменяемой ссылке, копированием или перемещением, являютсяSend. - Замыкание
CloneилиCopy, если оно не захватывает никакие значения по уникальной неизменяемой или изменяемой ссылке, и если все значения, которые оно захватывает копированием или перемещением, являютсяCloneилиCopyсоответственно.
Порядок сброса
Если замыкание захватывает поле составных типов, таких как структуры, кортежи и перечисления, по значению, время жизни поля теперь будет привязано к замыканию. Как следствие, возможно, что несвязные поля составных типов будут сброшены в разное время.
#![allow(unused)] fn main() { { let tuple = (String::from("foo"), String::from("bar")); // --+ { // | let c = || { // ----------------------------+ | // tuple.0 захвачен в замыкание | | drop(tuple.0); // | | }; // | | } // 'c' и 'tuple.0' сброшены здесь ------------+ | } // tuple.1 сброшен здесь -----------------------------+ }
Редакция 2018 и ранее
Разница в типах замыканий
В Редакции 2018 и ранее замыкания всегда захватывали переменную целиком, без точного пути захвата. Это означает, что для примера, использованного в разделе Типы замыканий, сгенерированный тип замыкания вместо этого выглядел бы примерно так:
struct Closure<'a> {
rect : &'a mut Rectangle,
}
impl<'a> FnOnce<()> for Closure<'a> {
type Output = String;
extern "rust-call" fn call_once(self, args: ()) -> String {
self.rect.left_top.x += 1;
self.rect.right_bottom.x += 1;
format!("{:?}", self.rect.left_top)
}
}
и вызов f работал бы следующим образом:
f(Closure { rect: rect });
Разница в точности захвата
Составные типы, такие как структуры, кортежи и перечисления, всегда захватывались целиком, а не по отдельным полям. Как следствие, может потребоваться заимствовать в локальную переменную, чтобы захватить одно поле:
#![allow(unused)] fn main() { use std::collections::HashSet; struct SetVec { set: HashSet<u32>, vec: Vec<u32> } impl SetVec { fn populate(&mut self) { let vec = &mut self.vec; self.set.iter().for_each(|&n| { vec.push(n); }) } } }
Если бы вместо этого замыкание использовало self.vec напрямую, то оно попыталось бы захватить self по изменяемой ссылке. Но поскольку self.set уже заимствован для итерации, код не скомпилировался бы.
Если используется ключевое слово move, то все захваты происходят перемещением или, для типов Copy, копированием, независимо от того, сработало бы заимствование или нет. Ключевое слово move обычно используется, чтобы позволить замыканию пережить захваченные значения, например, если замыкание возвращается или используется для создания нового потока.
Независимо от того, будут ли данные прочитаны замыканием, т.е. в случае подстановочных образцов, если переменная, определенная вне замыкания, упоминается внутри замыкания, переменная будет захвачена целиком.
Разница в порядке сброса
Поскольку составные типы захватываются целиком, замыкание, которое захватывает один из этих составных типов по значению, сбросит всю захваченную переменную одновременно с тем, как замыкание будет сброшено.
#![allow(unused)] fn main() { { let tuple = (String::from("foo"), String::from("bar")); { let c = || { // --------------------------+ // tuple захвачен в замыкание | drop(tuple.0); // | }; // | } // 'c' и 'tuple' сброшены здесь ------------+ } }
Типы указателей
Все указатели являются явными значениями первого класса. Они могут быть перемещены или скопированы, сохранены в структуры данных и возвращены из функций.
Ссылки (& и &mut)
Syntax
ReferenceType → & Lifetime? mut? TypeNoBounds
Разделяемые ссылки (&)
Разделяемые ссылки указывают на память, которой владеет какое-то другое значение.
Когда создается разделяемая ссылка на значение, это предотвращает прямое изменение значения.
Внутренняя изменяемость предоставляет исключение для этого в определенных обстоятельствах.
Как следует из названия, может существовать любое количество разделяемых ссылок на значение.
Тип разделяемой ссылки записывается как &type или &'a type, когда нужно указать явное время жизни.
Копирование ссылки является “поверхностной” операцией:
она включает только копирование самого указателя, то есть указатели являются Copy.
Освобождение ссылки не влияет на значение, на которое она указывает, но ссылка на временное значение будет поддерживать его жизнь в течение области видимости самой ссылки.
Изменяемые ссылки (&mut)
Изменяемые ссылки указывают на память, которой владеет какое-то другое значение.
Тип изменяемой ссылки записывается как &mut type или &'a mut type.
Изменяемая ссылка (которая не была заимствована) является единственным способом доступа к значению, на которое она указывает, поэтому не является Copy.
Сырые указатели (*const и *mut)
Syntax
RawPointerType → * ( mut | const ) TypeNoBounds
Сырые указатели — это указатели без гарантий безопасности или жизнеспособности.
Сырые указатели записываются как *const T или *mut T.
Например, *const i32 означает сырой указатель на 32-битное целое число.
Копирование или сброс сырого указателя не влияет на жизненный цикл любого другого значения.
Разыменование сырого указателя является небезопасной операцией.
Это также может быть использовано для преобразования сырого указателя в ссылку путем перезаимствования (&* или &mut *).
Сырые указатели обычно не рекомендуются;
они существуют для поддержки взаимодействия с внешним кодом и написания производительных или низкоуровневых функций.
При сравнении сырых указателей они сравниваются по их адресу, а не по тому, на что они указывают. При сравнении сырых указателей на динамически sized типы также сравниваются их дополнительные данные.
Сырые указатели могут быть созданы напрямую с использованием &raw const для указателей *const и &raw mut для указателей *mut.
Умные указатели
Стандартная библиотека содержит дополнительные типы “умных указателей” помимо ссылок и сырых указателей.
Валидность битов
Несмотря на то, что указатели и ссылки похожи на usize в машинном коде, генерируемом на большинстве платформ,
семантика преобразования типа ссылки или указателя в не-указательный тип в настоящее время не определена.
Таким образом, может быть недопустимо преобразовывать тип указателя или ссылки, P, в [u8; size_of::<P>()].
Для тонких сырых указателей (т.е. для P = *const T или P = *mut T для T: Sized),
обратное направление (преобразование из целого числа или массива целых чисел в P) всегда допустимо.
Однако указатель, полученный таким преобразованием, не может быть разыменован (даже если T имеет нулевой размер).
Типы указателей на функции
Syntax
BareFunctionType →
ForLifetimes? FunctionTypeQualifiers fn
( FunctionParametersMaybeNamedVariadic? ) BareFunctionReturnType?
FunctionTypeQualifiers → unsafe? ( extern Abi? )?
BareFunctionReturnType → -> TypeNoBounds
FunctionParametersMaybeNamedVariadic →
MaybeNamedFunctionParameters | MaybeNamedFunctionParametersVariadic
MaybeNamedFunctionParameters →
MaybeNamedParam ( , MaybeNamedParam )* ,?
MaybeNamedParam →
OuterAttribute* ( ( IDENTIFIER | _ ) : )? Type
MaybeNamedFunctionParametersVariadic →
( MaybeNamedParam , )* MaybeNamedParam , OuterAttribute* ...
Типы указателей на функции, записываемые с использованием ключевого слова fn, ссылаются на функцию,
чья идентичность не обязательно известна во время компиляции.
Пример, где Binop определен как тип указателя на функцию:
#![allow(unused)] fn main() { fn add(x: i32, y: i32) -> i32 { x + y } let mut x = add(5,7); type Binop = fn(i32, i32) -> i32; let bo: Binop = add; x = bo(5,7); }
Указатели на функции могут быть созданы через приведение как из функциональных элементов, так и из незахватывающих, не-асинхронных замыканий.
Квалификатор unsafe указывает, что значение типа является небезопасной
функцией, а квалификатор extern указывает, что это внешняя функция.
Чтобы функция была вариадической, ее ABI extern должен быть одним из перечисленных в items.extern.variadic.conventions.
Атрибуты на параметрах указателей на функции
Атрибуты на параметрах указателей на функции следуют тем же правилам и ограничениям, что и обычные параметры функции.
Трейт-объекты
Syntax
TraitObjectType → dyn? TypeParamBounds
Трейт-объект — это непрозрачное значение другого типа, которое реализует набор трейтов. Набор трейтов состоит из dyn-совместимого базового трейта плюс любое количество автотрейтов.
Трейт-объекты реализуют базовый трейт, его автотрейты и любые супертрейты базового трейта.
Трейт-объекты записываются как ключевое слово dyn, за которым следует набор ограничений трейтов,
но со следующими ограничениями на ограничения трейтов.
Не может быть более одного не-автотрейта, не более одного
времени жизни, и отключающие ограничения (например, ?Sized) не разрешены. Кроме того,
пути к трейтам могут быть заключены в скобки.
Например, для трейта Trait следующие являются трейт-объектами:
dyn Traitdyn Trait + Senddyn Trait + Send + Syncdyn Trait + 'staticdyn Trait + Send + 'staticdyn Trait +dyn 'static + Trait.dyn (Trait)
2021 Edition differences
До редакции 2021 года ключевое слово
dynмогло быть опущено.
2018 Edition differences
В редакции 2015, если первое ограничение трейт-объекта — это путь, который начинается с
::, тоdynбудет рассматриваться как часть пути. Первый путь можно заключить в скобки, чтобы обойти это. Таким образом, если вы хотите трейт-объект с трейтом::your_module::Trait, вы должны записать его какdyn (::your_module::Trait).Начиная с редакции 2018,
dynявляется настоящим ключевым словом и не разрешен в путях, поэтому скобки не нужны.
Два типа трейт-объектов являются псевдонимами друг друга, если базовые трейты являются псевдонимами друг друга и
если наборы автотрейтов одинаковы и границы времени жизни одинаковы.
Например, dyn Trait + Send + UnwindSafe совпадает с
dyn Trait + UnwindSafe + Send.
Из-за непрозрачности того, какого конкретного типа значение, трейт-объекты являются
динамически sized типами. Как и все
DST, трейт-объекты используются
за каким-то типом указателя; например, &dyn SomeTrait или
Box<dyn SomeTrait>. Каждый экземпляр указателя на трейт-объект включает:
- указатель на экземпляр типа
T, который реализуетSomeTrait - таблицу виртуальных методов, часто называемую vtable, которая содержит для
каждого метода
SomeTraitи его супертрейтов, которыеTреализует, указатель на реализациюT(т.е. указатель на функцию).
Цель трейт-объектов — разрешить “позднее связывание” методов. Вызов метода на трейт-объекте приводит к виртуальной диспетчеризации во время выполнения: то есть указатель на функцию загружается из vtable трейт-объекта и вызывается косвенно. Фактическая реализация для каждой записи vtable может различаться для каждого объекта.
Пример трейт-объекта:
trait Printable { fn stringify(&self) -> String; } impl Printable for i32 { fn stringify(&self) -> String { self.to_string() } } fn print(a: Box<dyn Printable>) { println!("{}", a.stringify()); } fn main() { print(Box::new(10) as Box<dyn Printable>); }
В этом примере трейт Printable встречается как трейт-объект в
сигнатуре типа print и в выражении приведения в main.
Границы времени жизни трейт-объекта
Поскольку трейт-объект может содержать ссылки, время жизни этих ссылок
нужно выразить как часть трейт-объекта. Это время жизни записывается как
Trait + 'a. Существуют умолчания, которые позволяют этому времени жизни обычно
выводиться с разумным выбором.
Impl trait
Syntax
ImplTraitType → impl TypeParamBounds
ImplTraitTypeOneBound → impl TraitBound
impl Trait предоставляет способы указания неназванных, но конкретных типов, которые
реализуют определенный трейт.
Он может появляться в двух видах мест: позиция аргумента (где он может действовать как анонимный параметр типа для функций) и позиция возврата (где он может действовать как абстрактный тип возврата).
#![allow(unused)] fn main() { trait Trait {} impl Trait for () {} // позиция аргумента: анонимный параметр типа fn foo(arg: impl Trait) { } // позиция возврата: абстрактный тип возврата fn bar() -> impl Trait { } }
Анонимные параметры типа
Note
Это часто называют “impl Trait в позиции аргумента”. (Термин “параметр” здесь более корректен, но “impl Trait в позиции аргумента” — это формулировка, использовавшаяся при разработке этой функции, и она остается в частях реализации.)
Функции могут использовать impl, за которым следует набор ограничений трейтов, чтобы объявить параметр как имеющий анонимный тип.
Вызывающая сторона должна предоставить тип, удовлетворяющий ограничениям, объявленным анонимным параметром типа, и функция может использовать только методы, доступные через ограничения трейтов анонимного параметра типа.
Например, эти две формы почти эквивалентны:
#![allow(unused)] fn main() { trait Trait {} // обобщенный параметр типа fn with_generic_type<T: Trait>(arg: T) { } // impl Trait в позиции аргумента fn with_impl_trait(arg: impl Trait) { } }
То есть impl Trait в позиции аргумента — это синтаксический сахар для обобщенного параметра типа, такого как <T: Trait>, за исключением того, что тип анонимен и не появляется в списке GenericParams.
Note
Для параметров функции обобщенные параметры типа и
impl Traitне совсем эквивалентны. С обобщенным параметром, таким как<T: Trait>, вызывающая сторона имеет возможность явно указать обобщенный аргумент дляTна месте вызова с помощью GenericArgs, например,foo::<usize>(1). Изменение параметра с одного на другой может представлять собой критическое изменение для вызывающих функцию, поскольку это изменяет количество обобщенных аргументов.
Абстрактные типы возврата
Note
Это часто называют “impl Trait в позиции возврата”.
Функции могут использовать impl Trait для возврата абстрактного типа возврата.
Эти типы заменяют другой конкретный тип, где вызывающая сторона может использовать только методы, объявленные указанным Trait.
Каждое возможное возвращаемое значение из функции должно разрешаться в один и тот же конкретный тип.
impl Trait в позиции возврата позволяет функции возвращать неупакованный абстрактный тип.
Это особенно полезно с замыканиями и итераторами.
Например, замыкания имеют уникальный, не записываемый тип.
Ранее единственным способом вернуть замыкание из функции было использование трейт-объекта:
#![allow(unused)] fn main() { fn returns_closure() -> Box<dyn Fn(i32) -> i32> { Box::new(|x| x + 1) } }
Это могло повлечь штрафы производительности из-за выделения в куче и динамической диспетчеризации.
Было невозможно полностью указать тип замыкания, только использовать трейт Fn.
Это означает, что трейт-объект необходим.
Однако с impl Trait можно написать это проще:
#![allow(unused)] fn main() { fn returns_closure() -> impl Fn(i32) -> i32 { |x| x + 1 } }
что также избегает недостатков использования упакованного трейт-объекта.
Аналогично, конкретные типы итераторов могут стать очень сложными, включая типы всех предыдущих итераторов в цепочке.
Возврат impl Iterator означает, что функция раскрывает только трейт Iterator как ограничение на свой тип возврата, вместо явного указания всех других задействованных типов итераторов.
impl Trait в позиции возврата в трейтах и реализациях трейтов
Функции в трейтах также могут использовать impl Trait как синтаксис для анонимного ассоциированного типа.
Каждый impl Trait в типе возврата ассоциированной функции в трейте десугарируется в анонимный ассоциированный тип. Тип возврата, который появляется в сигнатуре функции реализации, используется для определения значения ассоциированного типа.
Захват
За каждым абстрактным типом impl Trait в позиции возврата стоит некоторый скрытый конкретный тип. Чтобы этот конкретный тип использовал обобщенный параметр, этот обобщенный параметр должен быть захвачен абстрактным типом.
Автоматический захват
Абстрактные типы impl Trait в позиции возврата автоматически захватывают все обобщенные параметры в области видимости, включая обобщенные параметры типа, константы и времени жизни (включая вышеранговые).
2024 Edition differences
До редакции 2024, в свободных функциях и в ассоциированных функциях и методах собственных реализаций, обобщенные параметры времени жизни, которые не появляются в ограничениях абстрактного типа возврата, не захватываются автоматически.
Точный захват
Набор обобщенных параметров, захватываемых абстрактным типом impl Trait в позиции возврата, может быть явно контролируем с помощью ограничения use<..>. Если присутствует, только обобщенные параметры, перечисленные в ограничении use<..>, будут захвачены. Например:
#![allow(unused)] fn main() { fn capture<'a, 'b, T>(x: &'a (), y: T) -> impl Sized + use<'a, T> { // ~~~~~~~~~~~~~~~~~~~~~~~ // Захватывает только `'a` и `T`. (x, y) } }
В настоящее время только одно ограничение use<..> может присутствовать в списке ограничений, все обобщенные параметры типа и константы в области видимости должны быть включены, и все параметры времени жизни, которые появляются в других ограничениях абстрактного типа, должны быть включены.
В пределах ограничения use<..> любые присутствующие параметры времени жизни должны появляться перед всеми обобщенными параметрами типа и константы, и опущенное время жизни ('_) может присутствовать, если ему разрешено появляться в типе возврата impl Trait.
Поскольку все обобщенные параметры типа в области видимости должны быть включены по имени, ограничение use<..> не может использоваться в сигнатуре элементов, которые используют impl Trait в позиции аргумента, так как эти элементы имеют анонимные параметры типа в области видимости.
Любое ограничение use<..>, которое присутствует в ассоциированной функции в определении трейта, должно включать все обобщенные параметры трейта, включая неявный обобщенный параметр типа Self трейта.
Различия между обобщениями и impl Trait в позиции возврата
В позиции аргумента impl Trait очень похож по семантике на обобщенный параметр типа.
Однако есть значительные различия между ними в позиции возврата.
С impl Trait, в отличие от обобщенного параметра типа, функция выбирает тип возврата, и вызывающая сторона не может выбрать тип возврата.
Функция:
#![allow(unused)] fn main() { trait Trait {} fn foo<T: Trait>() -> T { // ... panic!() } }
позволяет вызывающей стороне определить тип возврата, T, и функция возвращает этот тип.
Функция:
#![allow(unused)] fn main() { trait Trait {} impl Trait for () {} fn foo() -> impl Trait { // ... } }
не позволяет вызывающей стороне определить тип возврата.
Вместо этого функция выбирает тип возврата, но только обещает, что он будет реализовывать Trait.
Ограничения
impl Trait может появляться только как параметр или тип возврата не-extern функции.
Он не может быть типом привязки let, типом поля или появляться внутри псевдонима типа.
Параметры типа
Внутри тела элемента, который имеет объявления параметров типа, имена его параметров типа являются типами:
#![allow(unused)] fn main() { fn to_vec<A: Clone>(xs: &[A]) -> Vec<A> { if xs.is_empty() { return vec![]; } let first: A = xs[0].clone(); let mut rest: Vec<A> = to_vec(&xs[1..]); rest.insert(0, first); rest } }
Здесь first имеет тип A, ссылаясь на параметр типа A функции to_vec; и
rest имеет тип Vec<A>, вектор с типом элемента A.
Выводимый тип
Syntax
InferredType → _
Выводимый тип просит компилятор вывести тип, если это возможно, на основе доступной окружающей информации.
Example
Выводимый тип часто используется в обобщенных аргументах:
#![allow(unused)] fn main() { let x: Vec<_> = (0..10).collect(); }
Выводимый тип не может использоваться в сигнатурах элементов.
Типы динамического размера
Большинство типов имеют фиксированный размер, известный во время компиляции, и реализуют трейт Sized. Тип с размером, известным только во время выполнения, называется типом динамического размера (DST) или, неформально, типом без размера. Срезы, типы-объекты трейтов и str являются примерами DST.
Такие типы можно использовать только в определённых случаях:
- Указатели на DST имеют размер, но их размер в два раза больше размера указателей на типы с известным размером
- Указатели на срезы и
strтакже хранят количество элементов. - Указатели на объекты трейтов также хранят указатель на таблицу виртуальных методов (vtable).
- Указатели на срезы и
- DST могут быть переданы как
типовые аргументы обобщённым параметрам типа, имеющим специальное ограничение
?Sized. Они также могут использоваться для определений ассоциированных типов, когда соответствующее объявление ассоциированного типа имеет ограничение?Sized. По умолчанию любой параметр типа или ассоциированный тип имеет ограничениеSized, если оно не ослаблено с помощью?Sized.
- Трейты могут быть реализованы для DST.
В отличие от обобщённых параметров типа,
Self: ?Sizedявляется значением по умолчанию в определениях трейтов.
- Структуры могут содержать DST в качестве последнего поля; это делает саму структуру DST.
Note
Переменные, параметры функций, константы и статические переменные должны быть
Sized.
Расположение типов
Расположение типа — это его размер, выравнивание и относительные смещения его полей. Для перечислений частью расположения типа также является то, как размещается и интерпретируется дискриминант.
Расположение типа может меняться с каждой компиляцией. Вместо того чтобы пытаться документировать точные действия, мы документируем только то, что гарантировано на сегодняшний день.
Обратите внимание, что даже типы с одинаковым расположением могут различаться по способу передачи через границы функций. Для совместимости типов с ABI вызовов функций смотрите здесь.
Размер и выравнивание
Все значения имеют выравнивание и размер.
Выравнивание значения указывает, по каким адресам допустимо хранить значение.
Значение с выравниванием n должно храниться только по адресу, кратному n.
Например, значение с выравниванием 2 должно храниться по чётному адресу, а
значение с выравниванием 1 может храниться по любому адресу. Выравнивание
измеряется в байтах, должно быть не менее 1 и всегда является степенью двойки.
Выравнивание значения можно проверить с помощью функции align_of_val.
Размер значения — это смещение в байтах между последовательными элементами в
массиве с данным типом элемента, включая заполнение для выравнивания. Размер
значения всегда кратен его выравниванию. Обратите внимание, что некоторые типы
имеют нулевой размер; 0 считается кратным любому выравниванию (например, на
некоторых платформах тип [u16; 0] имеет размер 0 и выравнивание 2). Размер
значения можно проверить с помощью функции size_of_val.
Типы, у которых все значения имеют одинаковый размер и выравнивание, и оба
известны во время компиляции, реализуют трейт Sized и могут быть проверены
с помощью функций size_of и align_of. Типы, не являющиеся Sized,
известны как типы динамического размера. Поскольку
все значения типа Sized имеют одинаковый размер и выравнивание, мы ссылаемся
на эти общие значения как на размер типа и выравнивание типа соответственно.
Расположение примитивных данных
Размер большинства примитивов приведён в этой таблице.
| Тип | size_of::<Type>() |
|---|---|
bool | 1 |
u8 / i8 | 1 |
u16 / i16 | 2 |
u32 / i32 | 4 |
u64 / i64 | 8 |
u128 / i128 | 16 |
usize / isize | Смотрите ниже |
f32 | 4 |
f64 | 8 |
char | 4 |
usize и isize имеют размер, достаточный для хранения любого адреса на
целевой платформе. Например, на 32-битной цели это 4 байта, а на 64-битной
цели — 8 байт.
Выравнивание примитивов зависит от платформы.
В большинстве случаев их выравнивание равно их размеру, но может быть и меньше.
В частности, i128 и u128 часто выравниваются по 4 или 8 байт, даже если
их размер равен 16, а на многих 32-битных платформах i64, u64 и f64
выравниваются только по 4 байтам, а не по 8.
Расположение указателей и ссылок
Указатели и ссылки имеют одинаковое расположение. Изменяемость указателя или ссылки не меняет расположение.
Указатели на типы с известным размером имеют тот же размер и выравнивание, что и usize.
Указатели на типы без размера сами имеют размер. Гарантируется, что их размер и выравнивание как минимум равны размеру и выравниванию указателя.
Note
Хотя на это не стоит полагаться, в настоящее время все указатели на DST имеют размер в два раза больше, чем
usize, и то же выравнивание.
Расположение массивов
Массив [T; N] имеет размер size_of::<T>() * N и то же выравнивание, что и T.
Массивы располагаются так, что элемент массива с индексом n (начиная с нуля)
смещён от начала массива на n * size_of::<T>() байт.
Расположение срезов
Срезы имеют то же расположение, что и часть массива, которую они срезают.
Note
Речь идёт о самом типе
[T], а не об указателях (&[T],Box<[T]>и т.д.) на срезы.
Расположение str
Строковые срезы — это UTF-8 представление символов, которое имеет то же расположение, что и срезы типа [u8]. Ссылка &str имеет то же расположение, что и ссылка &[u8].
Расположение кортежей
Кортежи располагаются в соответствии с представлением Rust.
Исключением является кортеж-единица (()), который гарантированно является
типом с нулевым размером, имеет размер 0 и выравнивание 1.
Расположение объектов трейтов
Объекты трейтов имеют то же расположение, что и значение, которым является объект трейта.
Note
Речь идёт о самих типах объектов трейтов, а не об указателях (
&dyn Trait,Box<dyn Trait>и т.д.) на них.
Расположение замыканий
Для замыканий нет гарантий расположения.
Представления
Все пользовательские составные типы (struct, enum и union) имеют
представление, которое определяет расположение для этого типа.
Возможные представления для типа:
Rust(по умолчанию)C- Примитивные представления
transparent
Представление типа можно изменить, применив к нему атрибут repr.
Следующий пример показывает структуру с представлением C.
#![allow(unused)] fn main() { #[repr(C)] struct ThreeInts { first: i16, second: i8, third: i32 } }
Выравнивание может быть увеличено или уменьшено с помощью модификаторов align и packed
соответственно. Они изменяют представление, указанное в атрибуте.
Если представление не указано, изменяется представление по умолчанию.
#![allow(unused)] fn main() { // Представление по умолчанию, выравнивание снижено до 2. #[repr(packed(2))] struct PackedStruct { first: i16, second: i8, third: i32 } // Представление C, выравнивание повышено до 8 #[repr(C, align(8))] struct AlignedStruct { first: i16, second: i8, third: i32 } }
Note
Поскольку представление является атрибутом элемента, оно не зависит от обобщённых параметров. Любые два типа с одинаковым именем имеют одинаковое представление. Например,
Foo<Bar>иFoo<Baz>имеют одинаковое представление.
Представление типа может изменить заполнение между полями, но не меняет
расположение самих полей. Например, структура с представлением C,
содержащая структуру Inner с представлением Rust, не изменит расположение Inner.
Представление Rust
Представление Rust является представлением по умолчанию для именованных типов
без атрибута repr. Явное использование этого представления через атрибут
repr гарантированно эквивалентно полному отсутствию атрибута.
Единственные гарантии размещения данных, предоставляемые этим представлением, — это те, которые требуются для безопасности. А именно:
- Поля правильно выровнены.
- Поля не перекрываются.
- Выравнивание типа не меньше максимального выравнивания его полей.
Формально первая гарантия означает, что смещение любого поля делится на выравнивание этого поля.
Вторая гарантия означает, что поля можно упорядочить таким образом, что смещение плюс размер любого поля меньше или равно смещению следующего поля в этом порядке. Порядок не обязан совпадать с порядком, в котором поля указаны в объявлении типа.
Имейте в виду, что вторая гарантия не подразумевает, что поля имеют разные адреса: типы с нулевым размером могут иметь тот же адрес, что и другие поля в той же структуре.
Это представление не даёт никаких других гарантий относительно размещения данных.
Представление C
Представление C предназначено для двух целей. Первая цель — создание типов,
совместимых с языком C. Вторая цель — создание типов, над которыми можно безопасно
выполнять операции, зависящие от размещения данных, например, переинтерпретацию
значений как другой тип.
Из-за этой двойной цели возможно создание типов, которые не полезны для взаимодействия с языком программирования C.
Это представление может применяться к структурам, объединениям и перечислениям. Исключением
являются перечисления без вариантов, для которых представление C является ошибкой.
#[repr(C)] Структуры
Выравнивание структуры равно выравниванию самого выровненного поля в ней.
Размер и смещение полей определяются следующим алгоритмом.
Начните с текущего смещения 0 байт.
Для каждого поля в порядке объявления в структуре сначала определите размер и выравнивание поля. Если текущее смещение не кратно выравниванию поля, то добавьте байты заполнения к текущему смещению, пока оно не станет кратным выравниванию поля. Смещение для поля — это полученное текущее смещение. Затем увеличьте текущее смещение на размер поля.
Наконец, размер структуры — это текущее смещение, округлённое до ближайшего кратного выравниванию структуры.
Вот описание этого алгоритма в псевдокоде.
/// Возвращает количество байтов заполнения, необходимых после `offset`, чтобы гарантировать,
/// что следующий адрес будет выровнен по `alignment`.
fn padding_needed_for(offset: usize, alignment: usize) -> usize {
let misalignment = offset % alignment;
if misalignment > 0 {
// Округляем до следующего кратного `alignment`
alignment - misalignment
} else {
// Уже кратно `alignment`
0
}
}
struct.alignment = struct.fields().map(|field| field.alignment).max();
let current_offset = 0;
for field in struct.fields_in_declaration_order() {
// Увеличиваем текущее смещение так, чтобы оно стало кратно выравниванию
// этого поля. Для первого поля это всегда будет ноль.
// Пропущенные байты называются байтами заполнения.
current_offset += padding_needed_for(current_offset, field.alignment);
struct[field].offset = current_offset;
current_offset += field.size;
}
struct.size = current_offset + padding_needed_for(current_offset, struct.alignment);
Warning
Этот псевдокод использует наивный алгоритм, который игнорирует проблемы переполнения ради ясности. Для выполнения вычислений размещения памяти в реальном коде используйте
Layout.
Note
Этот алгоритм может создавать структуры с нулевым размером. В C объявление пустой структуры, такое как
struct Foo { }, недопустимо. Однако и gcc, и clang поддерживают опции для включения таких структур и присваивают им размер ноль. В C++, напротив, пустые структуры имеют размер 1, если они не унаследованы от другой структуры или не являются полями с атрибутом[[no_unique_address]], в этом случае они не увеличивают общий размер структуры.
#[repr(C)] Объединения
Объявление объединения с #[repr(C)] будет иметь тот же размер и выравнивание, что и
эквивалентное объявление объединения на языке C для целевой платформы.
Объединение будет иметь размер, равный максимальному размеру всех его полей, округлённому до его выравнивания, и выравнивание, равное максимальному выравниванию всех его полей. Эти максимумы могут происходить от разных полей.
#![allow(unused)] fn main() { #[repr(C)] union Union { f1: u16, f2: [u8; 4], } assert_eq!(std::mem::size_of::<Union>(), 4); // Из f2 assert_eq!(std::mem::align_of::<Union>(), 2); // Из f1 #[repr(C)] union SizeRoundedUp { a: u32, b: [u16; 3], } assert_eq!(std::mem::size_of::<SizeRoundedUp>(), 8); // Размер 6 из b, // округлённый до 8 из-за // выравнивания a. assert_eq!(std::mem::align_of::<SizeRoundedUp>(), 4); // Из a }
#[repr(C)] Перечисления без полей
Для перечислений без полей представление C имеет размер и выравнивание
по умолчанию для enum в соответствии с C ABI целевой платформы.
Note
Представление перечисления в C определяется реализацией, так что это действительно «наилучшее предположение». В частности, это может быть неверно, когда интересующий код C компилируется с определёнными флагами.
Warning
Существуют crucial различия между
enumв языке C и перечислениями без полей в Rust с этим представлением.enumв C — это в основномtypedefплюс некоторые именованные константы; другими словами, объект типаenumможет содержать любое целочисленное значение. Например, это часто используется для битовых флагов вC. В отличие от этого, перечисления без полей в Rust могут законно содержать только значения дискриминанта, всё остальное — неопределённое поведение. Поэтому использование перечисления без полей в FFI для моделирования Cenumчасто является ошибкой.
#[repr(C)] Перечисления с полями
Представление repr(C) перечисления с полями — это repr(C) структура с
двумя полями, также называемая «помеченное объединение» в C:
repr(C)версия перечисления со всеми удалёнными полями («метка»)
repr(C)объединениеrepr(C)структур для полей каждого варианта, которые имели поля («полезная нагрузка»)
Note
Из-за представления
repr(C)структур и объединений, если вариант имеет одно поле, нет разницы между помещением этого поля непосредственно в объединение или его обёртыванием в структуру; поэтому любая система, которая хочет манипулировать представлением такогоenum, может использовать ту форму, которая для неё более удобна или последовательна.
#![allow(unused)] fn main() { // Это перечисление имеет то же представление, что и ... #[repr(C)] enum MyEnum { A(u32), B(f32, u64), C { x: u32, y: u8 }, D, } // ... эта структура. #[repr(C)] struct MyEnumRepr { tag: MyEnumDiscriminant, payload: MyEnumFields, } // Это перечисление дискриминантов. #[repr(C)] enum MyEnumDiscriminant { A, B, C, D } // Это объединение вариантов. #[repr(C)] union MyEnumFields { A: MyAFields, B: MyBFields, C: MyCFields, D: MyDFields, } #[repr(C)] #[derive(Copy, Clone)] struct MyAFields(u32); #[repr(C)] #[derive(Copy, Clone)] struct MyBFields(f32, u64); #[repr(C)] #[derive(Copy, Clone)] struct MyCFields { x: u32, y: u8 } // Эта структура может быть опущена (она является типом с нулевым размером), но должна быть // в заголовках C/C++. #[repr(C)] #[derive(Copy, Clone)] struct MyDFields; }
Примитивные представления
Примитивные представления — это представления с теми же именами, что и
примитивные целочисленные типы. А именно: u8, u16, u32, u64, u128,
usize, i8, i16, i32, i64, i128 и isize.
Примитивные представления могут применяться только к перечислениям и имеют разное поведение в зависимости от того, есть ли у перечисления поля. Для перечислений без вариантов примитивное представление является ошибкой. Совмещение двух примитивных представлений вместе является ошибкой.
Примитивное представление перечислений без полей
Для перечислений без полей примитивные представления устанавливают размер и выравнивание
такими же, как у примитивного типа с тем же именем. Например, перечисление без полей
с представлением u8 может иметь дискриминанты только от 0 до 255 включительно.
Примитивное представление перечислений с полями
Представление перечисления с примитивным представлением — это repr(C) объединение
repr(C) структур для каждого варианта с полем. Первое поле каждой структуры
в объединении — это версия перечисления с примитивным представлением со всеми удалёнными полями
(«метка»), а остальные поля — это поля этого варианта.
Note
Это представление не изменится, если метке выделить собственный член в объединении, если это сделает манипуляции более понятными для вас (хотя, чтобы соответствовать стандарту C++, член-метка должен быть обёрнут в
struct).
#![allow(unused)] fn main() { // Это перечисление имеет то же представление, что и ... #[repr(u8)] enum MyEnum { A(u32), B(f32, u64), C { x: u32, y: u8 }, D, } // ... это объединение. #[repr(C)] union MyEnumRepr { A: MyVariantA, B: MyVariantB, C: MyVariantC, D: MyVariantD, } // Это перечисление дискриминантов. #[repr(u8)] #[derive(Copy, Clone)] enum MyEnumDiscriminant { A, B, C, D } #[repr(C)] #[derive(Clone, Copy)] struct MyVariantA(MyEnumDiscriminant, u32); #[repr(C)] #[derive(Clone, Copy)] struct MyVariantB(MyEnumDiscriminant, f32, u64); #[repr(C)] #[derive(Clone, Copy)] struct MyVariantC { tag: MyEnumDiscriminant, x: u32, y: u8 } #[repr(C)] #[derive(Clone, Copy)] struct MyVariantD(MyEnumDiscriminant); }
Комбинирование примитивных представлений перечислений с полями и #[repr(C)]
Для перечислений с полями также можно комбинировать repr(C) и
примитивное представление (например, repr(C, u8)). Это изменяет repr(C) путём
замены представления перечисления дискриминантов на выбранное примитивное.
Таким образом, если вы выбрали представление u8, то перечисление дискриминантов
будет иметь размер и выравнивание 1 байт.
Перечисление дискриминантов из предыдущего примера тогда становится:
#![allow(unused)] fn main() { #[repr(C, u8)] // `u8` был добавлен enum MyEnum { A(u32), B(f32, u64), C { x: u32, y: u8 }, D, } // ... #[repr(u8)] // Поэтому здесь используется `u8` вместо `C` enum MyEnumDiscriminant { A, B, C, D } // ... }
Например, для перечисления repr(C, u8) невозможно иметь 257 уникальных
дискриминантов («меток»), тогда как то же перечисление только с атрибутом repr(C)
скомпилируется без проблем.
Использование примитивного представления в дополнение к repr(C) может изменить размер
перечисления по сравнению с формой repr(C):
#![allow(unused)] fn main() { #[repr(C)] enum EnumC { Variant0(u8), Variant1, } #[repr(C, u8)] enum Enum8 { Variant0(u8), Variant1, } #[repr(C, u16)] enum Enum16 { Variant0(u8), Variant1, } // Размер представления C зависит от платформы assert_eq!(std::mem::size_of::<EnumC>(), 8); // Один байт для дискриминанта и один байт для значения в Enum8::Variant0 assert_eq!(std::mem::size_of::<Enum8>(), 2); // Два байта для дискриминанта и один байт для значения в Enum16::Variant0 // плюс один байт заполнения. assert_eq!(std::mem::size_of::<Enum16>(), 4); }
Модификаторы выравнивания
Модификаторы align и packed могут использоваться для повышения или понижения
выравнивания struct и union соответственно. packed также может изменять заполнение
между полями (хотя он не будет изменять заполнение внутри любого поля).
Само по себе align и packed не дают гарантий относительно порядка
полей в расположении структуры или расположении варианта перечисления, хотя
они могут комбинироваться с представлениями (такими как C), которые предоставляют такие
гарантии.
Выравнивание задаётся целочисленным параметром в форме
#[repr(align(x))] или #[repr(packed(x))]. Значение выравнивания должно быть
степенью двойки от 1 до 229. Для packed, если значение не задано,
как в #[repr(packed)], то значение равно 1.
Для align, если указанное выравнивание меньше выравнивания типа
без модификатора align, то выравнивание не изменяется.
Для packed, если указанное выравнивание больше выравнивания типа
без модификатора packed, то выравнивание и расположение не изменяются.
Выравнивания каждого поля, для целей позиционирования полей, берутся как меньшее из указанного выравнивания и выравнивания типа поля.
Заполнение между полями гарантированно является минимально необходимым для
удовлетворения (возможно, изменённого) выравнивания каждого поля (хотя обратите внимание,
что само по себе packed не даёт никаких гарантий относительно порядка полей). Важным
следствием этих правил является то, что тип с #[repr(packed(1))]
(или #[repr(packed)]) не будет иметь заполнения между полями.
Модификаторы align и packed не могут применяться к одному и тому же типу, и
packed тип не может транзитивно содержать другой align тип. align и
packed могут применяться только к представлениям Rust и C.
Модификатор align также может применяться к enum.
Когда это сделано, эффект на выравнивание enum такой же, как если бы enum
был обёрнут в новую структуру (struct) с тем же модификатором align.
Note
Ссылки на невыровненные поля запрещены, так как это неопределённое поведение. Когда поля не выровнены из-за модификатора выравнивания, рассмотрите следующие варианты использования ссылок и разыменований:
#![allow(unused)] fn main() { #[repr(packed)] struct Packed { f1: u8, f2: u16, } let mut e = Packed { f1: 1, f2: 2 }; // Вместо создания ссылки на поле скопируйте значение в локальную переменную. let x = e.f2; // Или в ситуациях, подобных `println!`, которые создают ссылку, используйте фигурные скобки // чтобы изменить её на копию значения. println!("{}", {e.f2}); // Или если вам нужен указатель, используйте невыровненные методы для чтения и записи // вместо прямого разыменования указателя. let ptr: *const u16 = &raw const e.f2; let value = unsafe { ptr.read_unaligned() }; let mut_ptr: *mut u16 = &raw mut e.f2; unsafe { mut_ptr.write_unaligned(3) } }
Представление transparent
Представление transparent может использоваться только на struct
или enum с одним вариантом, который имеет:
- любое количество полей с размером 0 и выравниванием 1 (например,
PhantomData<T>), и - не более одного другого поля.
Структуры и перечисления с этим представлением имеют то же расположение и ABI, что и единственное поле с ненулевым размером или выравниванием не 1, если оно присутствует, или как unit-тип в противном случае.
Это отличается от представления C, потому что
структура с представлением C всегда будет иметь ABI C struct,
в то время как, например, структура с представлением transparent с
примитивным полем будет иметь ABI этого примитивного поля.
Поскольку это представление делегирует расположение типа другому типу, оно не может быть использовано с любым другим представлением.
Внутренняя изменяемость
Иногда возникает необходимость изменять тип, имея несколько псевдонимов на него. В Rust это достигается с использованием паттерна, называемого внутренняя изменяемость.
Тип обладает внутренней изменяемостью, если его внутреннее состояние может быть изменено через разделяемую ссылку на него.
Это противоречит обычному требованию, что значение, на которое указывает разделяемая ссылка, не должно изменяться.
Тип std::cell::UnsafeCell<T> — это единственный разрешённый способ отключить
данное требование. Когда UnsafeCell<T> имеет неизменяемый псевдоним, всё ещё безопасно
изменять или получать изменяемую ссылку на содержащийся в нём T.
Как и для всех других типов, наличие нескольких псевдонимов &mut UnsafeCell<T>
является неопределённым поведением.
Другие типы с внутренней изменяемостью могут быть созданы путём использования UnsafeCell<T> в качестве
поля. Стандартная библиотека предоставляет множество типов, которые обеспечивают безопасные
API для внутренней изменяемости.
Например, std::cell::RefCell<T> использует проверки заимствований во время выполнения для обеспечения обычных правил работы с множественными ссылками.
Модуль std::sync::atomic содержит типы, которые оборачивают значение, доступ к которому осуществляется
только с помощью атомарных операций, что позволяет совместно использовать и изменять значение
между потоками.
Подтипы и вариативность
Подтипизация является неявной и может происходить на любом этапе проверки типов или вывода типов.
Подтипизация ограничена двумя случаями: вариативность по времени жизни и между типами с высшими рангами времён жизни. Если бы мы стёрли времена жизни из типов, то подтипизация была бы возможна только благодаря равенству типов.
Рассмотрим следующий пример: строковые литералы всегда имеют время жизни 'static.
Тем не менее, мы можем присвоить s переменной t:
#![allow(unused)] fn main() { fn bar<'a>() { let s: &'static str = "hi"; let t: &'a str = s; } }
Поскольку 'static переживает параметр времени жизни 'a, &'static str является
подтипом &'a str.
Указатели на функции и объекты трейтов с высшими рангами имеют другое отношение подтипов. Они являются подтипами типов, полученных путём подстановок времён жизни высшего ранга. Несколько примеров:
#![allow(unused)] fn main() { // Здесь 'a заменяется на 'static let subtype: &(for<'a> fn(&'a i32) -> &'a i32) = &((|x| x) as fn(&_) -> &_); let supertype: &(fn(&'static i32) -> &'static i32) = subtype; // Это аналогично работает для объектов трейтов let subtype: &(dyn for<'a> Fn(&'a i32) -> &'a i32) = &|x| x; let supertype: &(dyn Fn(&'static i32) -> &'static i32) = subtype; // Мы также можем подставить одно время жизни высшего ранга вместо другого let subtype: &(for<'a, 'b> fn(&'a i32, &'b i32)) = &((|x, y| {}) as fn(&_, &_)); let supertype: &for<'c> fn(&'c i32, &'c i32) = subtype; }
Вариативность
Вариативность — это свойство, которое обобщённые типы имеют по отношению к своим аргументам. Вариативность обобщённого типа в параметре описывает, как отношение подтипов для этого параметра влияет на отношение подтипов для самого типа.
F<T>является ковариантным поT, если из того, чтоT— подтипU, следует, чтоF<T>— подтипF<U>(подтипизация “проходит насквозь”)
F<T>является контравариантным поT, если из того, чтоT— подтипU, следует, чтоF<U>— подтипF<T>
F<T>является инвариантным поTв остальных случаях (не может быть выведено никакое отношение подтипов)
Вариативность типов автоматически определяется следующим образом
| Тип | Вариативность по 'a | Вариативность по T |
|---|---|---|
&'a T | ковариантный | ковариантный |
&'a mut T | ковариантный | инвариантный |
*const T | ковариантный | |
*mut T | инвариантный | |
[T] и [T; n] | ковариантный | |
fn() -> T | ковариантный | |
fn(T) -> () | контравариантный | |
std::cell::UnsafeCell<T> | инвариантный | |
std::marker::PhantomData<T> | ковариантный | |
dyn Trait<T> + 'a | ковариантный | инвариантный |
Вариативность других типов struct, enum и union определяется
путем анализа вариативности типов их полей. Если параметр используется
в позициях с разной вариативностью, то параметр является инвариантным.
Например, следующая структура является ковариантной по 'a и T и инвариантной по 'b, 'c,
и U.
#![allow(unused)] fn main() { use std::cell::UnsafeCell; struct Variance<'a, 'b, 'c, T, U: 'a> { x: &'a U, // Это делает `Variance` ковариантной по 'a, и // сделало бы её ковариантной по U, но U используется позже y: *const T, // Ковариантный по T z: UnsafeCell<&'b f64>, // Инвариантный по 'b w: *mut U, // Инвариантный по U, делает всю структуру инвариантной f: fn(&'c ()) -> &'c () // Одновременно ко- и контравариантный, делает 'c инвариантным // в структуре. } }
При использовании вне struct, enum или union, вариативность для параметров проверяется в каждом месте отдельно.
#![allow(unused)] fn main() { use std::cell::UnsafeCell; fn generic_tuple<'short, 'long: 'short>( // 'long используется внутри кортежа и в ковариантной, и в инвариантной позиции. x: (&'long u32, UnsafeCell<&'long u32>), ) { // Поскольку вариативность в этих позициях вычисляется отдельно, // мы можем свободно сужать 'long в ковариантной позиции. let _: (&'short u32, UnsafeCell<&'long u32>) = x; } fn takes_fn_ptr<'short, 'middle: 'short>( // 'middle используется и в ковариантной, и в контравариантной позиции. f: fn(&'middle ()) -> &'middle (), ) { // Поскольку вариативность в этих позициях вычисляется отдельно, // мы можем свободно сужать 'middle в ковариантной позиции // и расширять его в контравариантной позиции. let _: fn(&'static ()) -> &'short () = f; } }
Ограничения трейтов и времён жизни
Syntax
TypeParamBounds → TypeParamBound ( + TypeParamBound )* +?
TypeParamBound → Lifetime | TraitBound | UseBound
TraitBound →
( ? | ForLifetimes )? TypePath
| ( ( ? | ForLifetimes )? TypePath )
LifetimeBounds → ( Lifetime + )* Lifetime?
Lifetime →
LIFETIME_OR_LABEL
| 'static
| '_
UseBound → use UseBoundGenericArgs
UseBoundGenericArgs →
< >
| < ( UseBoundGenericArg , )* UseBoundGenericArg ,? >
UseBoundGenericArg →
Lifetime
| IDENTIFIER
| Self
Ограничения трейтов и времён жизни предоставляют способ для обобщённых элементов ограничивать, какие типы и времена жизни используются в качестве их параметров. Ограничения могут быть указаны для любого типа в where clause. Также существуют сокращённые формы для некоторых распространённых случаев:
- Ограничения, записанные после объявления обобщённого параметра:
fn f<A: Copy>() {}эквивалентноfn f<A>() where A: Copy {}. - В объявлениях трейтов как супертрейты:
trait Circle : Shape {}эквивалентноtrait Circle where Self : Shape {}. - В объявлениях трейтов как ограничения на ассоциированные типы:
trait A { type B: Copy; }эквивалентноtrait A where Self::B: Copy { type B; }.
Ограничения на элемент должны выполняться при использовании элемента. При проверке типов и
заимствований обобщённого элемента ограничения могут использоваться для определения, что
трейт реализован для типа. Например, при условии Ty: Trait
- В теле обобщённой функции методы из
Traitмогут вызываться на значенияхTy. Аналогично могут использоваться ассоциированные константы изTrait. - Могут использоваться ассоциированные типы из
Trait. - Обобщённые функции и типы с ограничениями
T: Traitмогут использоваться сTyв качествеT.
#![allow(unused)] fn main() { type Surface = i32; trait Shape { fn draw(&self, surface: Surface); fn name() -> &'static str; } fn draw_twice<T: Shape>(surface: Surface, sh: T) { sh.draw(surface); // Можно вызвать метод, т.к. T: Shape sh.draw(surface); } fn copy_and_draw_twice<T: Copy>(surface: Surface, sh: T) where T: Shape { let shape_copy = sh; // не перемещает sh, потому что T: Copy draw_twice(surface, sh); // Можно использовать обобщённую функцию, т.к. T: Shape } struct Figure<S: Shape>(S, S); fn name_figure<U: Shape>( figure: Figure<U>, // Тип Figure<U> корректен, потому что U: Shape ) { println!( "Figure of two {}", U::name(), // Можно использовать ассоциированную функцию ); } }
Ограничения, которые не используют параметры элемента или времена жизни высшего ранга, проверяются при определении элемента. Ошибкой является ложность такого ограничения.
Ограничения Copy, Clone и Sized также проверяются для некоторых обобщённых типов при использовании элемента, даже если использование не предоставляет конкретный тип.
Ошибкой является наличие Copy или Clone в качестве ограничения для изменяемой ссылки, объекта трейта или среза.
Ошибкой является наличие Sized в качестве ограничения для объекта трейта или среза.
#![allow(unused)] fn main() { struct A<'a, T> where i32: Default, // Разрешено, но бесполезно i32: Iterator, // Ошибка: `i32` не является итератором &'a mut T: Copy, // (при использовании) Ошибка: ограничение трейта не выполнено [T]: Sized, // (при использовании) Ошибка: размер не может быть известен при компиляции { f: &'a T, } struct UsesA<'a, T>(A<'a, T>); }
Ограничения трейтов и времён жизни также используются для именования объектов трейтов.
?Sized
? используется только для ослабления неявного ограничения трейта Sized для параметров типа или ассоциированных типов.
?Sized не может использоваться как ограничение для других типов.
Ограничения времени жизни
Ограничения времени жизни могут применяться к типам или к другим временам жизни.
Ограничение 'a: 'b обычно читается как 'a переживает 'b.
'a: 'b означает, что 'a длится по крайней мере так же долго, как 'b, поэтому ссылка &'a () действительна всегда, когда действительна &'b ().
#![allow(unused)] fn main() { fn f<'a, 'b>(x: &'a i32, mut y: &'b i32) where 'a: 'b { y = x; // &'a i32 является подтипом &'b i32, потому что 'a: 'b let r: &'b &'a i32 = &&0; // &'b &'a i32 корректен, потому что 'a: 'b } }
T: 'a означает, что все параметры времени жизни T переживают 'a.
Например, если 'a — это неограниченный параметр времени жизни, то i32: 'static и &'static str: 'a выполняются, но Vec<&'a ()>: 'static — нет.
Ограничения трейтов высшего ранга
Syntax
ForLifetimes → for GenericParams
Ограничения трейтов могут быть высшего ранга по временам жизни. Эти ограничения указывают условие,
которое выполняется для всех времён жизни. Например, ограничение вида for<'a> &'a T: PartialEq<i32> потребует реализации вида:
#![allow(unused)] fn main() { struct T; impl<'a> PartialEq<i32> for &'a T { // ... fn eq(&self, other: &i32) -> bool {true} } }
и затем может использоваться для сравнения &'a T с любым временем жизни и i32.
Только ограничение высшего ранга может быть использовано здесь, потому что время жизни ссылки короче любого возможного параметра времени жизни функции:
#![allow(unused)] fn main() { fn call_on_ref_zero<F>(f: F) where for<'a> F: Fn(&'a i32) { let zero = 0; f(&zero); } }
Времена жизни высшего ранга также могут указываться непосредственно перед трейтом: единственное различие — это область видимости параметра времени жизни, которая простирается только до конца следующего трейта, а не всего ограничения. Эта функция эквивалентна предыдущей.
#![allow(unused)] fn main() { fn call_on_ref_zero<F>(f: F) where F: for<'a> Fn(&'a i32) { let zero = 0; f(&zero); } }
Подразумеваемые ограничения
Иногда выводятся ограничения времени жизни, необходимые для корректности типов.
#![allow(unused)] fn main() { fn requires_t_outlives_a<'a, T>(x: &'a T) {} }
Для типа &'a T требуется, чтобы параметр типа T переживал 'a.
Это выводится, потому что сигнатура функции содержит тип &'a T, который
корректен только если выполняется T: 'a.
Подразумеваемые ограничения добавляются для всех параметров и выходных значений функций. Внутри requires_t_outlives_a
вы можете предполагать, что T: 'a выполняется, даже если вы не указали это явно:
#![allow(unused)] fn main() { fn requires_t_outlives_a_not_implied<'a, T: 'a>() {} fn requires_t_outlives_a<'a, T>(x: &'a T) { // Это компилируется, потому что `T: 'a` подразумевается // типом ссылки `&'a T`. requires_t_outlives_a_not_implied::<'a, T>(); } }
#![allow(unused)] fn main() { fn requires_t_outlives_a_not_implied<'a, T: 'a>() {} fn not_implied<'a, T>() { // Это ошибка, потому что `T: 'a` не подразумевается // сигнатурой функции. requires_t_outlives_a_not_implied::<'a, T>(); } }
Подразумеваются только ограничения времени жизни, ограничения трейтов всё ещё должны добавляться явно. Следующий пример поэтому вызывает ошибку:
#![allow(unused)] fn main() { use std::fmt::Debug; struct IsDebug<T: Debug>(T); // error[E0277]: `T` doesn't implement `Debug` fn doesnt_specify_t_debug<T>(x: IsDebug<T>) {} }
Ограничения времени жизни также выводятся для определений типов и блоков impl для любого типа:
#![allow(unused)] fn main() { struct Struct<'a, T> { // Это требует, чтобы `T: 'a` было корректно, // что выводится компилятором. field: &'a T, } enum Enum<'a, T> { // Это требует, чтобы `T: 'a` было корректно, // что выводится компилятором. // // Заметьте, что `T: 'a` требуется даже при использовании // только `Enum::OtherVariant`. SomeVariant(&'a T), OtherVariant, } trait Trait<'a, T: 'a> {} // Это вызвало бы ошибку, потому что `T: 'a` не подразумевается никаким типом // в заголовке impl. // impl<'a, T> Trait<'a, T> for () {} // Это компилируется, так как `T: 'a` подразумевается типом self `&'a T`. impl<'a, T> Trait<'a, T> for &'a T {} }
Use-ограничения
Некоторые списки ограничений могут включать ограничение use<..>, чтобы контролировать, какие обобщённые параметры захватываются абстрактным возвращаемым типом impl Trait. Смотрите precise capturing для подробностей.
Приведения типов
Приведения типов — это неявные операции, которые изменяют тип значения. Они происходят автоматически в определённых местах и сильно ограничены в том, какие типы фактически могут приводиться.
Любые преобразования, разрешённые приведением, также могут быть явно выполнены с помощью
оператора приведения типа, as.
Приведения были первоначально определены в RFC 401 и расширены в RFC 1558.
Места приведения
Приведение может происходить только в определённых местах программы; это обычно места, где желаемый тип явный или может быть выведен путём распространения от явных типов (без вывода типа). Возможные места приведения:
-
Операторы
let, где указан явный тип.Например,
&mut 42приводится к типу&i8в следующем:#![allow(unused)] fn main() { let _: &i8 = &mut 42; }
- Объявления статических и константных элементов (аналогично операторам
let).
-
Аргументы вызовов функций
Приводимое значение — это фактический параметр, и оно приводится к типу формального параметра.
Например,
&mut 42приводится к типу&i8в следующем:fn bar(_: &i8) { } fn main() { bar(&mut 42); }Для вызовов методов тип получателя (параметр
self) приводится по-другому, смотрите документацию по method-call expressions для подробностей.
-
Инициализации полей структур, объединений или вариантов перечислений
Например,
&mut 42приводится к типу&i8в следующем:struct Foo<'a> { x: &'a i8 } fn main() { Foo { x: &mut 42 }; }
-
Результаты функций — либо последняя строка блока, если она не завершена точкой с запятой, либо любое выражение в операторе
returnНапример,
xприводится к типу&dyn Displayв следующем:#![allow(unused)] fn main() { use std::fmt::Display; fn foo(x: &u32) -> &dyn Display { x } }
Если выражение в одном из этих мест приведения является распространяющим приведение выражением, то соответствующие подвыражения в этом выражении также являются местами приведения. Распространение рекурсивно продолжается от этих новых мест приведения. Распространяющие выражения и их соответствующие подвыражения:
- Литералы массивов, где массив имеет тип
[U; n]. Каждое подвыражение в литерале массива является местом приведения к типуU.
- Литералы массивов с синтаксисом повторения, где массив имеет тип
[U; n]. Повторяющееся подвыражение является местом приведения к типуU.
- Кортежи, где кортеж является местом приведения к типу
(U_0, U_1, ..., U_n). Каждое подвыражение является местом приведения к соответствующему типу, например, нулевое подвыражение является местом приведения к типуU_0.
- Подвыражения в круглых скобках (
(e)): если выражение имеет типU, то подвыражение является местом приведения кU.
- Блоки: если блок имеет тип
U, то последнее выражение в блоке (если оно не завершено точкой с запятой) является местом приведения кU. Это включает блоки, которые являются частью операторов управления потоком, таких какif/else, если блок имеет известный тип.
Типы приведений
Приведение разрешено между следующими типами:
TкU, еслиTявляется подтипомU(рефлексивный случай)
-
T_1кT_3, гдеT_1приводится кT_2иT_2приводится кT_3(транзитивный случай)Обратите внимание, что это ещё не полностью поддерживается.
&mut Tк&T
*mut Tк*const T
&Tк*const T
&mut Tк*mut T
-
&Tили&mut Tк&U, еслиTреализуетDeref<Target = U>. Например:use std::ops::Deref; struct CharContainer { value: char, } impl Deref for CharContainer { type Target = char; fn deref<'a>(&'a self) -> &'a char { &self.value } } fn foo(arg: &char) {} fn main() { let x = &mut CharContainer { value: 'y' }; foo(x); // &mut CharContainer приводится к &char. }
&mut Tк&mut U, еслиTреализуетDerefMut<Target = U>.
-
TyCtor(
T) к TyCtor(U), где TyCtor(T) является одним из&T&mut T*const T*mut TBox<T>
и где
Uможет быть получен изTс помощью неразмерного приведения.
- Типы функциональных элементов к указателям на функции (
fnpointers)
- Замыкания без захвата к указателям на функции (
fnpointers)
!к любомуT
Неразмерные приведения
Следующие приведения называются неразмерными приведениями, поскольку они
связаны с преобразованием типов в неразмерные типы, и разрешены в нескольких
случаях, где другие приведения не разрешены, как описано выше. Они всё ещё могут происходить
в любом другом месте, где возможно приведение.
Два трейта, Unsize и CoerceUnsized, используются
для помощи в этом процессе и предоставления его для использования в библиотеках. Следующие
приведения являются встроенными, и если T может быть приведён к U с помощью одного из них, то
будет предоставлена реализация Unsize<U> для T:
[T; n]к[T].
Tкdyn U, когдаTреализуетU + Sized, иUявляется dyn compatible.
dyn Tкdyn U, когдаUявляется одним из супертрейтовT.- Это позволяет отбрасывать автоматические трейты, т.е. приведение
dyn T + Autoкdyn Uразрешено. - Это позволяет добавлять автоматические трейты, если главный трейт имеет автоматический трейт как супертрейт, т.е. при
trait T: U + Send {}, приведенияdyn Tкdyn T + Sendили кdyn U + Sendразрешены.
- Это позволяет отбрасывать автоматические трейты, т.е. приведение
Foo<..., T, ...>кFoo<..., U, ...>, когда:Fooявляется структурой.TреализуетUnsize<U>.- Последнее поле
Fooимеет тип, включающийT. - Если это поле имеет тип
Bar<T>, тоBar<T>реализуетUnsize<Bar<U>>. Tне является частью типа любых других полей.
Дополнительно, тип Foo<T> может реализовать CoerceUnsized<Foo<U>>, когда T
реализует Unsize<U> или CoerceUnsized<Foo<U>>. Это позволяет ему предоставлять
неразмерное приведение к Foo<U>.
Note
Хотя определение неразмерных приведений и их реализация были стабилизированы, сами трейты ещё не стабильны и поэтому не могут использоваться напрямую в стабильной Rust.
Приведения к наименьшей верхней границе
В некоторых контекстах компилятор должен приводить несколько типов друг к другу, чтобы найти наиболее общий тип. Это называется приведением к “Наименьшей Верхней Границе” (LUB). LUB-приведение используется и используется только в следующих ситуациях:
- Чтобы найти общий тип для серии ветвей if.
- Чтобы найти общий тип для серии ветвей match.
- Чтобы найти общий тип для элементов массива.
- Чтобы найти тип возвращаемого значения замыкания с несколькими операторами return.
- Чтобы проверить тип возвращаемого значения функции с несколькими операторами return.
В каждом таком случае есть набор типов T0..Tn, которые должны быть взаимно приведены
к некоторому целевому типу T_t, который изначально неизвестен.
Вычисление LUB-приведения
выполняется итеративно. Целевой тип T_t начинается как тип T0.
Для каждого нового типа Ti мы рассматриваем, может ли
- Если
Tiможет быть приведён к текущему целевому типуT_t, то изменений не вносится.
- Иначе, проверяется, может ли
T_tбыть приведён кTi; если да, тоT_tизменяется наTi. (Эта проверка также обусловлена тем, имеют ли все рассмотренные до сих пор исходные выражения неявные приведения.)
- Если нет, пытаетсяcя вычислить взаимный супертип
T_tиTi, который станет новым целевым типом.
Примеры:
#![allow(unused)] fn main() { let (a, b, c) = (0, 1, 2); // Для ветвей if let bar = if true { a } else if false { b } else { c }; // Для ветвей match let baw = match 42 { 0 => a, 1 => b, _ => c, }; // Для элементов массива let bax = [a, b, c]; // Для замыкания с несколькими операторами return let clo = || { if true { a } else if false { b } else { c } }; let baz = clo(); // Для проверки типа функции с несколькими операторами return fn foo() -> i32 { let (a, b, c) = (0, 1, 2); match 42 { 0 => a, 1 => b, _ => c, } } }
В этих примерах типы ba* находятся с помощью LUB-приведения. И компилятор
проверяет, является ли результат LUB-приведения a, b, c типом i32 в
процессе обработки функции foo.
Предостережение
Это описание, очевидно, неформально. Уточнение этого описания ожидается в рамках общей работы по более точной спецификации системы типов Rust.
Деструкторы
Когда инициализированная переменная или временное значение выходит из области видимости, её деструктор запускается, или она сбрасывается. Присваивание также запускает деструктор своего левого операнда, если он инициализирован. Если переменная была частично инициализирована, сбрасываются только её инициализированные поля.
Деструктор типа T состоит из:
- Если
T: Drop, вызов<T as core::ops::Drop>::drop - Рекурсивный запуск деструктора всех его полей.
- Поля структуры сбрасываются в порядке объявления.
- Поля активного варианта перечисления сбрасываются в порядке объявления.
- Поля кортежа сбрасываются по порядку.
- Элементы массива или владеющего среза сбрасываются от первого элемента к последнему.
- Переменные, захваченные замыканием по перемещению, сбрасываются в неопределённом порядке.
- Объекты трейтов запускают деструктор базового типа.
- Другие типы не приводят к каким-либо дополнительным сбросам.
Если деструктор должен быть запущен вручную, например, при реализации собственного умного
указателя, можно использовать core::ptr::drop_in_place.
Некоторые примеры:
#![allow(unused)] fn main() { struct PrintOnDrop(&'static str); impl Drop for PrintOnDrop { fn drop(&mut self) { println!("{}", self.0); } } let mut overwritten = PrintOnDrop("drops when overwritten"); overwritten = PrintOnDrop("drops when scope ends"); let tuple = (PrintOnDrop("Tuple first"), PrintOnDrop("Tuple second")); let moved; // Деструктор не запускается при присваивании. moved = PrintOnDrop("Drops when moved"); // Сбрасывается сейчас, но затем становится неинициализированным. moved; // Неинициализированная переменная не сбрасывается. let uninitialized: PrintOnDrop; // После частичного перемещения сбрасываются только оставшиеся поля. let mut partial_move = (PrintOnDrop("first"), PrintOnDrop("forgotten")); // Выполняется частичное перемещение, оставляя инициализированным только `partial_move.0`. core::mem::forget(partial_move.1); // Когда область видимости partial_move заканчивается, сбрасывается только первое поле. }
Области сброса
Каждая переменная или временное значение связаны с областью сброса. Когда поток управления покидает область сброса, все переменные, связанные с этой областью, сбрасываются в обратном порядке объявления (для переменных) или создания (для временных значений).
Области сброса могут быть определены путём замены выражений for, if и while
эквивалентными выражениями с использованием match, loop и
break.
Перегруженные операторы не отличаются от встроенных операторов, и режимы связывания не учитываются.
Для функции или замыкания существуют области сброса для:
- Всей функции
- Каждого оператора
- Каждого выражения
- Каждого блока, включая тело функции
- В случае блочного выражения, область видимости для блока и выражения совпадают.
- Каждой ветки выражения
match
Области сброса вложены друг в друга следующим образом. Когда несколько областей покидаются одновременно, например, при возврате из функции, переменные сбрасываются изнутри наружу.
- Область видимости всей функции является самой внешней областью.
- Блок тела функции содержится внутри области видимости всей функции.
- Родителем выражения в операторе-выражении является область видимости оператора.
- Родителем инициализатора оператора
letявляется область видимости оператораlet.
- Родителем области видимости оператора является область видимости блока, содержащего оператор.
- Родителем выражения для ограничителя
matchявляется область видимости ветки, для которой предназначен ограничитель.
- Родителем выражения после
=>в выраженииmatchявляется область видимости ветки, в которой оно находится.
- Родителем области видимости ветки является область видимости выражения
match, к которому она принадлежит.
- Родителем всех других областей видимости является область видимости непосредственно охватывающего выражения.
Области видимости параметров функции
Все параметры функции находятся в области видимости всего тела функции, поэтому сбрасываются последними при оценке функции. Каждый фактический параметр функции сбрасывается после любых привязок, введённых в шаблоне этого параметра.
#![allow(unused)] fn main() { struct PrintOnDrop(&'static str); impl Drop for PrintOnDrop { fn drop(&mut self) { println!("drop({})", self.0); } } // Сбрасывает `y`, затем второй параметр, затем `x`, затем первый параметр fn patterns_in_parameters( (x, _): (PrintOnDrop, PrintOnDrop), (_, y): (PrintOnDrop, PrintOnDrop), ) {} // порядок сброса: 3 2 0 1 patterns_in_parameters( (PrintOnDrop("0"), PrintOnDrop("1")), (PrintOnDrop("2"), PrintOnDrop("3")), ); }
Области видимости локальных переменных
Локальные переменные, объявленные в операторе let, связаны с областью видимости
блока, содержащего оператор let. Локальные переменные, объявленные в
выражении match, связаны с областью видимости ветки match, в которой они
объявлены.
#![allow(unused)] fn main() { struct PrintOnDrop(&'static str); impl Drop for PrintOnDrop { fn drop(&mut self) { println!("drop({})", self.0); } } let declared_first = PrintOnDrop("Dropped last in outer scope"); { let declared_in_block = PrintOnDrop("Dropped in inner scope"); } let declared_last = PrintOnDrop("Dropped first in outer scope"); }
Переменные в шаблонах сбрасываются в обратном порядке объявления внутри шаблона.
#![allow(unused)] fn main() { struct PrintOnDrop(&'static str); impl Drop for PrintOnDrop { fn drop(&mut self) { println!("drop({})", self.0); } } let (declared_first, declared_last) = ( PrintOnDrop("Dropped last"), PrintOnDrop("Dropped first"), ); }
Для целей порядка сброса, or-шаблоны объявляют привязки в порядке, заданном первым подшаблоном.
#![allow(unused)] fn main() { struct PrintOnDrop(&'static str); impl Drop for PrintOnDrop { fn drop(&mut self) { println!("drop({})", self.0); } } // Сбрасывает `x` перед `y`. fn or_pattern_drop_order<T>( (Ok([x, y]) | Err([y, x])): Result<[T; 2], [T; 2]> // ^^^^^^^^^^ ^^^^^^^^^^^ Это второй подшаблон. // | // Это первый подшаблон. // // В первом подшаблоне `x` объявлен перед `y`. Поскольку это // первый подшаблон, этот порядок используется, даже если сопоставляется // второй подшаблон, где привязки объявлены в противоположном // порядке. ) {} // Здесь мы сопоставляем первый подшаблон, и сбросы происходят согласно // порядку объявления в первом подшаблоне. or_pattern_drop_order(Ok([ PrintOnDrop("Declared first, dropped last"), PrintOnDrop("Declared last, dropped first"), ])); // Здесь мы сопоставляем второй подшаблон, и сбросы всё ещё происходят // согласно порядку объявления в первом подшаблоне. or_pattern_drop_order(Err([ PrintOnDrop("Declared last, dropped first"), PrintOnDrop("Declared first, dropped last"), ])); }
Области видимости временных значений
Область видимости временного значения выражения — это область видимости, которая используется для временной переменной, содержащей результат этого выражения, когда оно используется в контексте места, если оно не продвинуто.
Помимо расширения времени жизни, область видимости временного значения выражения — это наименьшая область видимости, содержащая выражение и являющаяся одной из следующих:
- Вся функция.
- Оператор.
- Тело выражения
if,whileилиloop. - Блок
elseвыраженияif. - Условие без сопоставления с образцом выражения
ifилиwhile, или ограничительmatch. - Выражение тела для ветки match.
- Каждый операнд ленивого булева выражения.
- Условие(я) сопоставления с образцом и последующее тело
if(destructors.scope.temporary.edition2024). - Условие сопоставления с образцом и тело цикла
while. - Все завершающее выражение блока (destructors.scope.temporary.edition2024).
Note
Скретини выражения
matchне является областью видимости временного значения, поэтому временные значения в скрутини могут быть сброшены после выраженияmatch. Например, временное значение для1вmatch 1 { ref mut z => z };живёт до конца оператора.
Note
Десугаризация деструктурирующего присваивания ограничивает область видимости временного значения своего операнда присваиваемого значения (правой части). Подробности см. в expr.assign.destructure.tmp-scopes.
2024 Edition differences
В редакции 2024 года были добавлены два новых правила сужения области видимости временных значений: временные значения
if letсбрасываются перед блокомelse, и временные значения завершающих выражений блоков сбрасываются сразу после оценки завершающего выражения.
Некоторые примеры:
#![allow(unused)] fn main() { #![allow(irrefutable_let_patterns)] struct PrintOnDrop(&'static str); impl Drop for PrintOnDrop { fn drop(&mut self) { println!("drop({})", self.0); } } let local_var = PrintOnDrop("local var"); // Сбрасывается после оценки условия if PrintOnDrop("If condition").0 == "If condition" { // Сбрасывается в конце блока PrintOnDrop("If body").0 } else { unreachable!() }; if let "if let scrutinee" = PrintOnDrop("if let scrutinee").0 { PrintOnDrop("if let consequent").0 // `if let consequent` сбрасывается здесь } // `if let scrutinee` сбрасывается здесь else { PrintOnDrop("if let else").0 // `if let else` сбрасывается здесь }; while let x = PrintOnDrop("while let scrutinee").0 { PrintOnDrop("while let loop body").0; break; // `while let loop body` сбрасывается здесь. // `while let scrutinee` сбрасывается здесь. } // Сбрасывается перед первым || (PrintOnDrop("first operand").0 == "" // Сбрасывается перед ) || PrintOnDrop("second operand").0 == "") // Сбрасывается перед ; || PrintOnDrop("third operand").0 == ""; // Скретини сбрасывается в конце функции, перед локальными переменными // (поскольку это завершающее выражение блока тела функции). match PrintOnDrop("Matched value in final expression") { // Сбрасывается после оценки условия _ if PrintOnDrop("guard condition").0 == "" => (), _ => (), } }
Операнды
Временные значения также создаются для хранения результата операндов выражения пока оцениваются другие операнды. Временные значения связаны с областью видимости выражения с этим операндом. Поскольку временные значения перемещаются из после оценки выражения, их сброс не имеет эффекта, если один из операндов выражения не выходит из выражения, не возвращается или не паникует.
#![allow(unused)] fn main() { struct PrintOnDrop(&'static str); impl Drop for PrintOnDrop { fn drop(&mut self) { println!("drop({})", self.0); } } loop { // Выражение кортежа не завершает оценку, поэтому операнды сбрасываются в обратном порядке ( PrintOnDrop("Outer tuple first"), PrintOnDrop("Outer tuple second"), ( PrintOnDrop("Inner tuple first"), PrintOnDrop("Inner tuple second"), break, ), PrintOnDrop("Never created"), ); } }
Продвижение констант
Продвижение выражения значения в слот 'static происходит, когда выражение
может быть записано в константу и заимствовано, и это заимствование может быть разыменовано там,
где выражение было изначально записано, без изменения поведения во время выполнения.
То есть, продвинутое выражение может быть вычислено во время компиляции, и
результирующее значение не содержит внутренней изменяемости или деструкторов (эти
свойства определяются на основе значения, где это возможно, например, &None
всегда имеет тип &'static Option<_>, так как он не содержит ничего запрещённого).
Расширение времени жизни временных значений
Note
Точные правила расширения времени жизни временных значений могут быть изменены. Здесь описывается только текущее поведение.
Области видимости временных значений для выражений в операторах let иногда
расширяются до области видимости блока, содержащего оператор let. Это
делается, когда обычная область видимости временного значения была бы слишком мала, на основе определённых
синтаксических правил. Например:
#![allow(unused)] fn main() { let x = &mut 0; // Обычно временное значение было бы сброшено к этому моменту, но временное значение для `0` живёт // до конца блока. println!("{}", x); }
Расширение времени жизни также применяется к элементам static и const, где оно
заставляет временные значения жить до конца программы. Например:
#![allow(unused)] fn main() { const C: &Vec<i32> = &Vec::new(); // Обычно это была бы висячая ссылка, так как `Vec` будет // существовать только внутри инициализатора `C`, но вместо этого // заимствование получает расширенное время жизни, так что оно эффективно имеет время жизни `'static`. println!("{:?}", C); }
Если заимствование, разыменование, поле или индексация кортежа имеют расширенную область видимости временного значения, то его операнд также имеет её. Если индексация имеет расширенную область видимости временного значения, то индексируемое выражение также имеет расширенную область видимости временного значения.
Расширение на основе шаблонов
Расширяющий шаблон — это либо:
-
Идентификаторный шаблон, который связывает по ссылке или изменяемой ссылке.
#![allow(unused)] fn main() { fn temp() {} let ref x = temp(); // Связывает по ссылке. x; let ref mut x = temp(); // Связывает по изменяемой ссылке. x; } -
Структурный, кортежный, кортежно-структурный, срезовый или or-шаблон, где по крайней мере один из прямых подшаблонов является расширяющим шаблоном.
#![allow(unused)] fn main() { use core::sync::atomic::{AtomicU64, Ordering::Relaxed}; static X: AtomicU64 = AtomicU64::new(0); struct W<T>(T); impl<T> Drop for W<T> { fn drop(&mut self) { X.fetch_add(1, Relaxed); } } let W { 0: ref x } = W(()); // Структурный шаблон. x; let W(ref x) = W(()); // Кортежно-структурный шаблон. x; let (W(ref x),) = (W(()),); // Кортежный шаблон. x; let [W(ref x), ..] = [W(())]; // Срезовый шаблон. x; let (Ok(W(ref x)) | Err(&ref x)) = Ok(W(())); // Or-шаблон. x; // // Все временные значения выше всё ещё живы здесь. assert_eq!(0, X.load(Relaxed)); }
Таким образом, ref x, V(ref x) и [ref x, y] — все расширяющие шаблоны, но x, &ref x и &(ref x,) — нет.
Если шаблон в операторе let является расширяющим шаблоном, то область видимости временного значения
инициализатора расширяется.
#![allow(unused)] fn main() { fn temp() {} // Это расширяющий шаблон, поэтому область видимости временного значения расширена. let ref x = *&temp(); // OK x; }
#![allow(unused)] fn main() { fn temp() {} // Это ни расширяющий шаблон, ни расширяющее выражение, // поэтому временное значение сбрасывается в точке с запятой. let &ref x = *&&temp(); // ERROR x; }
#![allow(unused)] fn main() { fn temp() {} // Это не расширяющий шаблон, но это расширяющее выражение, // поэтому временное значение живёт за пределами оператора `let`. let &ref x = &*&temp(); // OK x; }
Расширение на основе выражений
Для оператора let с инициализатором, расширяющее выражение — это выражение, которое является одним из следующих:
- Выражение инициализатора.
- Операнд расширяющего выражения заимствования.
- Супер-операнды расширяющего вызова супер-макроса.
- Операнд(ы) расширяющего выражения массива, приведения, блочной структуры или кортежа.
- Аргументы расширяющего конструктора кортежной структуры или кортежного варианта перечисления.
- Завершающее выражение расширяющего блочного выражения, за исключением асинхронного блочного выражения.
- Завершающее выражение расширяющего блока следствия,
else ifилиelseвыраженияif. - Выражение ветки расширяющего выражения
match.
Note
Десугаризация деструктурирующего присваивания делает его операнд присваиваемого значения (правую часть) расширяющим выражением внутри вновь введённого блока. Подробности см. в expr.assign.destructure.tmp-ext.
Таким образом, выражения заимствования в &mut 0, (&1, &mut 2) и Some(&mut 3)
— все расширяющие выражения. Заимствования в &0 + &1 и f(&mut 0) — нет.
Операнд расширяющего выражения заимствования имеет свою область видимости временного значения расширенной.
Супер-временные значения расширяющего вызова супер-макроса имеют свои области видимости расширенными.
Note
rustcне рассматривает операнды повторения массива расширяющих выражений массива как расширяющие выражения. Должен ли он — открытый вопрос.Подробности см. в Rust issue #146092.
Примеры
Вот некоторые примеры, где выражения имеют расширенные области видимости временных значений:
#![allow(unused)] fn main() { use core::pin::pin; use core::sync::atomic::{AtomicU64, Ordering::Relaxed}; static X: AtomicU64 = AtomicU64::new(0); #[derive(Debug)] struct S; impl Drop for S { fn drop(&mut self) { X.fetch_add(1, Relaxed); } } const fn temp() -> S { S } let x = &temp(); // Операнд заимствования. x; let x = &raw const *&temp(); // Операнд сырого заимствования. assert_eq!(X.load(Relaxed), 0); let x = &temp() as &dyn Send; // Операнд приведения. x; let x = (&*&temp(),); // Операнд конструктора кортежа. x; struct W<T>(T); let x = W(&temp()); // Аргумент конструктора кортежной структуры. x; let x = Some(&temp()); // Аргумент конструктора кортежного варианта перечисления. x; let x = { [Some(&temp())] }; // Завершающее выражение блока. x; let x = const { &temp() }; // Завершающее выражение блока `const`. x; let x = unsafe { &temp() }; // Завершающее выражение блока `unsafe`. x; let x = if true { &temp() } else { &temp() }; // ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ // Завершающие выражения блоков `if`/`else`. x; let x = match () { _ => &temp() }; // Выражение ветки `match`. x; let x = pin!(temp()); // Супер-операнд выражения вызова супер-макроса. x; let x = pin!({ &mut temp() }); // Как выше. x; let x = format_args!("{:?}", temp()); // Как выше. x; // // Все временные значения выше всё ещё живы здесь. assert_eq!(0, X.load(Relaxed)); }
Вот некоторые примеры, где выражения не имеют расширенных областей видимости временных значений:
#![allow(unused)] fn main() { fn temp() {} // Аргументы вызовов функций не являются расширяющими выражениями. // Временное значение сбрасывается в точке с запятой. let x = core::convert::identity(&temp()); // ERROR x; }
#![allow(unused)] fn main() { fn temp() {} trait Use { fn use_temp(&self) -> &Self { self } } impl Use for () {} // Получатели вызовов методов не являются расширяющими выражениями. let x = (&temp()).use_temp(); // ERROR x; }
#![allow(unused)] fn main() { fn temp() {} // Скретини выражений match не являются расширяющими выражениями. let x = match &temp() { x => x }; // ERROR x; }
#![allow(unused)] fn main() { fn temp() {} // Завершающие выражения `async` блоков не являются расширяющими выражениями. let x = async { &temp() }; // ERROR x; }
#![allow(unused)] fn main() { fn temp() {} // Завершающие выражения замыканий не являются расширяющими выражениями. let x = || &temp(); // ERROR x; }
#![allow(unused)] fn main() { fn temp() {} // Операнды break в циклах не являются расширяющими выражениями. let x = loop { break &temp() }; // ERROR x; }
#![allow(unused)] fn main() { fn temp() {} // Операнды break к меткам не являются расширяющими выражениями. let x = 'a: { break 'a &temp() }; // ERROR x; }
#![allow(unused)] fn main() { use core::pin::pin; fn temp() {} // Аргумент `pin!` является расширяющим выражением только если вызов // является расширяющим выражением. Поскольку это не так, внутренний блок не // является расширяющим выражением, поэтому временные значения в его завершающем // выражении сбрасываются немедленно. pin!({ &temp() }); // ERROR }
#![allow(unused)] fn main() { fn temp() {} // Как выше. format_args!("{:?}", { &temp() }); // ERROR }
Невыполнение деструкторов
Подавление деструкторов вручную
core::mem::forget может быть использован для предотвращения запуска деструктора переменной,
и core::mem::ManuallyDrop предоставляет обёртку для предотвращения
автоматического сброса переменной или поля.
Note
Предотвращение запуска деструктора с помощью
core::mem::forgetили другими средствами безопасно, даже если тип не является'static. Помимо мест, где деструкторы гарантированно выполняются, как определено в этом документе, типы не могут безопасно полагаться на выполнение деструктора для корректности.
Завершение процесса без раскрутки стека
Существуют способы завершить процесс без раскрутки стека, и в этом случае деструкторы не будут запущены.
Стандартная библиотека предоставляет std::process::exit и std::process::abort для явного выполнения этого. Кроме того, если обработчик паники установлен в abort, паника всегда будет завершать процесс без запуска деструкторов.
Есть один дополнительный случай, о котором следует знать: когда паника достигает границы ABI без раскрутки, либо не будет запущен ни один деструктор, либо будут запущены все деструкторы до границы ABI.
Сокрытие времён жизни
В Rust есть правила, которые позволяют скрывать времена жизни в различных местах, где компилятор может вывести разумный выбор по умолчанию.
Сокрытие времён жизни в функциях
Чтобы сделать распространённые паттерны более эргономичными, аргументы времени жизни могут быть скрыты в сигнатурах функциональных элементов, указателей на функции и трейтов замыканий. Следующие правила используются для вывода параметров времени жизни для скрытых времён жизни.
Ошибкой является скрытие параметров времени жизни, которые не могут быть выведены.
Заполнитель времени жизни, '_, также может быть использован для вывода времени жизни
таким же образом. Для времён жизни в путях использование '_ предпочтительнее.
Времена жизни объектов трейтов следуют другим правилам, обсуждаемым ниже.
- Каждое скрытое время жизни в параметрах становится отдельным параметром времени жизни.
- Если в параметрах используется ровно одно время жизни (скрытое или нет), то это время жизни присваивается всем скрытым выходным временам жизни.
В сигнатурах методов есть ещё одно правило
- Если получатель имеет тип
&Selfили&mut Self, то время жизни этой ссылки наSelfприсваивается всем скрытым параметрам выходного времени жизни.
Примеры:
#![allow(unused)] fn main() { trait T {} trait ToCStr {} struct Thing<'a> {f: &'a i32} struct Command; trait Example { fn print1(s: &str); // скрыто fn print2(s: &'_ str); // также скрыто fn print3<'a>(s: &'a str); // раскрыто fn debug1(lvl: usize, s: &str); // скрыто fn debug2<'a>(lvl: usize, s: &'a str); // раскрыто fn substr1(s: &str, until: usize) -> &str; // скрыто fn substr2<'a>(s: &'a str, until: usize) -> &'a str; // раскрыто fn get_mut1(&mut self) -> &mut dyn T; // скрыто fn get_mut2<'a>(&'a mut self) -> &'a mut dyn T; // раскрыто fn args1<T: ToCStr>(&mut self, args: &[T]) -> &mut Command; // скрыто fn args2<'a, 'b, T: ToCStr>(&'a mut self, args: &'b [T]) -> &'a mut Command; // раскрыто fn other_args1<'a>(arg: &str) -> &'a str; // скрыто fn other_args2<'a, 'b>(arg: &'b str) -> &'a str; // раскрыто fn new1(buf: &mut [u8]) -> Thing<'_>; // скрыто - предпочтительно fn new2(buf: &mut [u8]) -> Thing; // скрыто fn new3<'a>(buf: &'a mut [u8]) -> Thing<'a>; // раскрыто } type FunPtr1 = fn(&str) -> &str; // скрыто type FunPtr2 = for<'a> fn(&'a str) -> &'a str; // раскрыто type FunTrait1 = dyn Fn(&str) -> &str; // скрыто type FunTrait2 = dyn for<'a> Fn(&'a str) -> &'a str; // раскрыто }
#![allow(unused)] fn main() { // Следующие примеры показывают ситуации, где не разрешено скрывать // параметр времени жизни. trait Example { // Не может быть выведено, так как нет параметров для вывода. fn get_str() -> &str; // НЕЗАКОННО // Не может быть выведено, неясно, заимствовано ли из первого или второго параметра. fn frob(s: &str, t: &str) -> &str; // НЕЗАКОННО } }
Времена жизни объектов трейтов по умолчанию
Предполагаемое время жизни ссылок, удерживаемых объектом трейта, называется его ограничением времени жизни объекта по умолчанию. Они были определены в RFC 599 и изменены в RFC 1156.
Эти ограничения времени жизни объекта по умолчанию используются вместо правил скрытия параметров времени жизни, определённых выше, когда ограничение времени жизни опущено полностью.
Если '_ используется как ограничение времени жизни, то ограничение следует обычным правилам сокрытия.
Если объект трейта используется как типовой аргумент обобщённого типа, то содержащий тип сначала используется для попытки вывести ограничение.
- Если есть уникальное ограничение из содержащего типа, то оно используется по умолчанию.
- Если есть более одного ограничения из содержащего типа, то явное ограничение должно быть указано.
Если ни одно из этих правил не применяется, то используются ограничения на трейте:
- Если трейт определён с единственным ограничением времени жизни, то это ограничение используется.
- Если
'staticиспользуется для любого ограничения времени жизни, то используется'static.
- Если у трейта нет ограничений времени жизни, то время жизни выводится в
выражениях и является
'staticвне выражений.
#![allow(unused)] fn main() { // Для следующего трейта... trait Foo { } // Эти два одинаковы, потому что Box<T> не имеет ограничения времени жизни на T type T1 = Box<dyn Foo>; type T2 = Box<dyn Foo + 'static>; // ...и так же эти: impl dyn Foo {} impl dyn Foo + 'static {} // ...так же эти, потому что &'a T требует T: 'a type T3<'a> = &'a dyn Foo; type T4<'a> = &'a (dyn Foo + 'a); // std::cell::Ref<'a, T> также требует T: 'a, так что эти одинаковы type T5<'a> = std::cell::Ref<'a, dyn Foo>; type T6<'a> = std::cell::Ref<'a, dyn Foo + 'a>; }
#![allow(unused)] fn main() { // Это пример ошибки. trait Foo { } struct TwoBounds<'a, 'b, T: ?Sized + 'a + 'b> { f1: &'a i32, f2: &'b i32, f3: T, } type T7<'a, 'b> = TwoBounds<'a, 'b, dyn Foo>; // ^^^^^^^ // Ошибка: ограничение времени жизни для этого объектного типа не может быть выведено из контекста }
Обратите внимание, что самый внутренний объект устанавливает ограничение, так что &'a Box<dyn Foo> всё ещё
&'a Box<dyn Foo + 'static>.
#![allow(unused)] fn main() { // Для следующего трейта... trait Bar<'a>: 'a { } // ...эти два одинаковы: type T1<'a> = Box<dyn Bar<'a>>; type T2<'a> = Box<dyn Bar<'a> + 'a>; // ...и так же эти: impl<'a> dyn Bar<'a> {} impl<'a> dyn Bar<'a> + 'a {} }
Сокрытие в const и static
Как константные, так и статические объявления ссылочных типов имеют неявное
время жизни 'static, если явное время жизни не указано. Таким образом, приведённые выше
объявления констант, включающие 'static, могут быть записаны без указания
времён жизни.
#![allow(unused)] fn main() { // STRING: &'static str const STRING: &str = "bitstring"; struct BitsNStrings<'a> { mybits: [u32; 2], mystring: &'a str, } // BITS_N_STRINGS: BitsNStrings<'static> const BITS_N_STRINGS: BitsNStrings<'_> = BitsNStrings { mybits: [1, 2], mystring: STRING, }; }
Обратите внимание, что если элементы static или const включают ссылки на функции или замыкания,
которые сами включают ссылки, компилятор сначала попробует
применить стандартные правила сокрытия. Если он не сможет разрешить времена жизни по своим
обычным правилам, то выдаст ошибку. В качестве примера:
#![allow(unused)] fn main() { struct Foo; struct Bar; struct Baz; fn somefunc(a: &Foo, b: &Bar, c: &Baz) -> usize {42} // Разрешено как `for<'a> fn(&'a str) -> &'a str`. const RESOLVED_SINGLE: fn(&str) -> &str = |x| x; // Разрешено как `for<'a, 'b, 'c> Fn(&'a Foo, &'b Bar, &'c Baz) -> usize`. const RESOLVED_MULTIPLE: &dyn Fn(&Foo, &Bar, &Baz) -> usize = &somefunc; }
#![allow(unused)] fn main() { struct Foo; struct Bar; struct Baz; fn somefunc<'a,'b>(a: &'a Foo, b: &'b Bar) -> &'a Baz {unimplemented!()} // Недостаточно информации, чтобы ограничить время жизни возвращаемой ссылки // относительно времён жизни аргументов, так что это ошибка. const RESOLVED_STATIC: &dyn Fn(&Foo, &Bar) -> &Baz = &somefunc; // ^ // возвращаемый тип этой функции содержит заимствованное значение, но сигнатура // не указывает, заимствовано ли оно из аргумента 1 или аргумента 2 }
Специальные типы и трейты
Определённые типы и трейты, существующие в стандартной библиотеке, известны компилятору Rust. Эта глава документирует специальные особенности этих типов и трейтов.
Box<T>
Box<T> имеет несколько специальных особенностей, которые Rust в настоящее время не позволяет для пользовательских типов.
- Оператор разыменования для
Box<T>создаёт место, из которого можно перемещать. Это означает, что оператор*и деструкторBox<T>встроены в язык.
- Методы могут принимать
Box<Self>в качестве получателя.
- Трейт может быть реализован для
Box<T>в том же крейте, что иT, что правила сирот запрещают для других обобщённых типов.
Rc<T>
Методы могут принимать Rc<Self> в качестве получателя.
Arc<T>
Методы могут принимать Arc<Self> в качестве получателя.
Pin<P>
Методы могут принимать Pin<P> в качестве получателя.
UnsafeCell<T>
std::cell::UnsafeCell<T> используется для внутренней изменяемости. Он гарантирует, что
компилятор не выполняет оптимизации, которые некорректны для таких типов.
Он также гарантирует, что static элементы, которые имеют тип с внутренней
изменяемостью, не размещаются в памяти, помеченной как только для чтения.
PhantomData<T>
std::marker::PhantomData<T> — это тип с нулевым размером и минимальным выравниванием, который
считается владеющим T для целей ковариантности, проверки удаления и
автотрейтов.
Трейты операторов
Трейты в std::ops и std::cmp используются для перегрузки операторов,
индексных выражений и вызовов выражений.
Deref и DerefMut
Помимо перегрузки унарного оператора *, Deref и DerefMut также
используются в разрешении методов и приведениях разыменования.
Drop
Трейт Drop предоставляет деструктор, который будет запущен, когда значение этого типа должно быть уничтожено.
Copy
Трейт Copy изменяет семантику типа, который его реализует.
Значения, тип которых реализует Copy, копируются при присваивании, а не перемещаются.
Copy может быть реализован только для типов, которые не реализуют Drop, и все поля которых Copy.
Для перечислений это означает, что все поля всех вариантов должны быть Copy.
Для объединений это означает, что все варианты должны быть Copy.
Copy реализуется компилятором для
- Кортежей из
Copyтипов
- Замыканий, которые не захватывают значений или захватывают только значения
Copyтипов
Clone
Трейт Clone является супертрейтом Copy, поэтому ему также нужны сгенерированные компилятором реализации.
Он реализуется компилятором для следующих типов:
- Типов со встроенной реализацией
Copy(см. выше)
- Кортежей из
Cloneтипов
- Замыканий, которые захватывают только значения
Cloneтипов или не захватывают значений из окружения
Send
Трейт Send указывает, что значение этого типа безопасно отправлять из одного потока в другой.
Sync
Трейт Sync указывает, что значением этого типа безопасно делиться между несколькими потоками.
Этот трейт должен быть реализован для всех типов, используемых в неизменяемых static элементах.
Termination
Трейт Termination указывает допустимые возвращаемые типы для главной функции и тестовых функций.
Автотрейты
Трейты Send, Sync, Unpin, UnwindSafe и RefUnwindSafe являются автотрейтами.
Автотрейты имеют специальные свойства.
Если для автотрейта для данного типа не написана явная реализация или отрицательная реализация, то компилятор реализует его автоматически согласно следующим правилам:
&T,&mut T,*const T,*mut T,[T; n]и[T]реализуют трейт, еслиTделает это.
- Типы функциональных элементов и указатели на функции автоматически реализуют трейт.
- Структуры, перечисления, объединения и кортежи реализуют трейт, если все их поля делают это.
- Замыкания реализуют трейт, если типы всех их захватов делают это.
Замыкание, которое захватывает
Tпо общей ссылке иUпо значению, реализует любые автотрейты, которые реализуют и&T, иU.
Для обобщённых типов (считая встроенные типы выше обобщёнными над T), если доступна обобщённая реализация,
то компилятор не реализует её автоматически для типов, которые могли бы использовать реализацию, но не удовлетворяют необходимым ограничениям трейтов.
Например, стандартная библиотека реализует Send для всех &T, где T — Sync; это означает, что компилятор не будет
реализовывать Send для &T, если T — Send, но не Sync.
Автотрейты также могут иметь отрицательные реализации, показанные как impl !AutoTrait for T в документации стандартной библиотеки,
которые переопределяют автоматические реализации. Например, *mut T имеет отрицательную реализацию Send,
и поэтому *mut T не Send, даже если T — Send. В настоящее время нет стабильного способа
указывать дополнительные отрицательные реализации; они существуют только в стандартной библиотеке.
Автотрейты могут быть добавлены как дополнительное ограничение к любому трейт-объекту, даже
хотя обычно разрешён только один трейт. Например, Box<dyn Debug + Send + UnwindSafe> является допустимым типом.
Sized
Трейт Sized указывает, что размер этого типа известен во время компиляции; то есть, это не динамически sized тип.
Параметры типов (кроме Self в трейтах) являются Sized по умолчанию, как и ассоциированные типы.
Sized всегда автоматически реализуется компилятором, а не элементами реализации.
Эти неявные ограничения Sized могут быть ослаблены с использованием специального ограничения ?Sized.
Имена
Сущность — это языковая конструкция, на которую можно ссылаться каким-либо образом в исходной программе, обычно через path. Сущности включают типы, элементы, обобщённые параметры, переменные привязки, метки циклов, времена жизни, поля, атрибуты и линты.
Объявление — это синтаксическая конструкция, которая может вводить имя для ссылки на сущность. Имена сущностей действительны в пределах области видимости — области исходного текста, где на это имя можно ссылаться.
Некоторые сущности явно объявлены в исходном коде, а некоторые неявно объявлены как часть языка или расширений компилятора.
Пути используются для ссылки на сущность, возможно, в другом модуле или типе.
Времена жизни и метки циклов используют специальный синтаксис с предшествующей кавычкой.
Имена разделены на различные пространства имён, позволяя сущностям в разных пространствах имён иметь одинаковое имя без конфликта.
Разрешение имён — это процесс во время компиляции, связывающий пути, идентификаторы и метки с объявлениями сущностей.
Доступ к определённым именам может быть ограничен на основе их видимости.
Явно объявленные сущности
Сущности, которые явно вводят имя в исходном коде:
- Элементы:
- Объявления модулей
- Объявления внешних крейтов
- Use-объявления
- Объявления функций и параметры функций
- Псевдонимы типов
- Объявления структур, объединений, перечислений, вариантов перечислений и их именованные поля
- Объявления констант
- Объявления статических элементов
- Объявления элементов трейтов и их ассоциированные элементы
- Элементы внешних блоков
- Объявления
macro_rulesи метапеременные матчеров - Ассоциированные элементы реализаций
- Привязки образцов оператора
let
- Атрибут
macro_useможет вводить имена макросов из другого крейта
- Атрибут
macro_exportможет вводить псевдоним для макроса в корень крейта
Кроме того, вызовы макросов и атрибуты могут вводить имена путём разворачивания в один из вышеперечисленных элементов.
Неявно объявленные сущности
Следующие сущности неявно определены языком или введены опциями компилятора и расширениями:
- Прелюдия языка:
- Логический тип —
bool - Текстовые типы —
charиstr - Целочисленные типы —
i8,i16,i32,i64,i128,u8,u16,u32,u64,u128 - Машинно-зависимые целочисленные типы —
usizeиisize - Типы с плавающей точкой —
f32иf64
- Логический тип —
- Элементы, атрибуты и макросы прелюдии стандартной библиотеки
- Крейты стандартной библиотеки в корневом модуле
- Внешние крейты, связанные компилятором
- Вспомогательные атрибуты derive действительны внутри элемента без явного импорта
- Время жизни
'static
Кроме того, корневой модуль крейта не имеет имени, но на него можно ссылаться с помощью определённых квалификаторов путей или псевдонимов.
Пространства имён
Пространство имён — это логическая группировка объявленных имён. Имена разделены на отдельные пространства имён в зависимости от вида сущности, на которую ссылается имя. Пространства имён позволяют появлению имени в одном пространстве имён не конфликтовать с тем же именем в другом пространстве имён.
Существует несколько различных пространств имён, каждое из которых содержит различные виды сущностей. Использование имени будет искать объявление этого имени в разных пространствах имён, в зависимости от контекста, как описано в главе о разрешении имён.
Следующий список представляет пространства имён с соответствующими им сущностями:
- Пространство имён типов
- Объявления модулей
- Объявления внешних крейтов
- Элементы прелюдии внешних крейтов
- Объявления структур, объединений, перечислений, вариантов перечислений
- Объявления элементов трейтов
- Псевдонимы типов
- Объявления ассоциированных типов
- Встроенные типы: логический, числовые и текстовые
- Обобщённые параметры типа
- Тип
Self - Модули инструментальных атрибутов
- Пространство имён значений
- Объявления функций
- Объявления констант
- Объявления статических элементов
- Конструкторы структур
- Конструкторы вариантов перечислений
- Конструкторы
Self - Обобщённые параметры констант
- Объявления ассоциированных констант
- Объявления ассоциированных функций
- Локальные привязки —
let,if let,while let,for, рукиmatch, параметры функций, параметры замыканий - Захваченные переменные замыканий
- Пространство имён макросов
- Пространство имён времён жизни
- Пространство имён меток
Пример того, как перекрывающиеся имена в разных пространствах имён могут быть использованы однозначно:
#![allow(unused)] fn main() { // Foo вводит тип в пространстве имён типов и конструктор в пространстве // имён значений. struct Foo(u32); // Макрос `Foo` объявлен в пространстве имён макросов. macro_rules! Foo { () => {}; } // `Foo` в типе параметра `f` ссылается на `Foo` в пространстве имён типов. // `'Foo` вводит новое время жизни в пространстве имён времён жизни. fn example<'Foo>(f: Foo) { // `Foo` ссылается на конструктор `Foo` в пространстве имён значений. let ctor = Foo; // `Foo` ссылается на макрос `Foo` в пространстве имён макросов. Foo!{} // `'Foo` вводит метку в пространстве имён меток. 'Foo: loop { // `'Foo` ссылается на параметр времени жизни `'Foo`, а `Foo` // ссылается на пространство имён типов. let x: &'Foo Foo; // `'Foo` ссылается на метку. break 'Foo; } } }
Именованные сущности без пространства имён
Следующие сущности имеют явные имена, но имена не являются частью какого-либо конкретного пространства имён.
Поля
Несмотря на то, что поля структур, перечислений и объединений именованы, именованные поля не находятся в явном пространстве имён. К ним можно получить доступ только через выражение поля, которое проверяет только имена полей конкретного типа, к которому осуществляется доступ.
Use-объявления
Use-объявление имеет именованные псевдонимы, которые оно импортирует в область видимости, но
сам элемент use не принадлежит к определённому пространству имён. Вместо этого он может
вводить псевдонимы в несколько пространств имён, в зависимости от вида импортируемого
элемента.
Подпространства имён
Пространство имён макросов разделено на два подпространства имён: одно для макросов с восклицательным знаком и одно для атрибутов. Когда атрибут разрешается, любые макросы с восклицательным знаком в области видимости игнорируются. И наоборот, разрешение макроса с восклицательным знаком игнорирует атрибутные макросы в области видимости. Это предотвращает затенение одного стиля другим.
Например, атрибут cfg и макрос cfg — это две разные сущности с одним и тем же именем в пространстве имён макросов, но они всё равно могут использоваться в своих соответствующих контекстах.
Всё ещё ошибкой является затенение одним импортом use другого макроса, независимо от их подпространств имён.
Области видимости
Область видимости — это область исходного текста, где на именованную сущность можно ссылаться по этому имени. Следующие разделы предоставляют подробности о правилах и поведении областей видимости, которые зависят от вида сущности и места её объявления. Процесс того, как имена разрешаются в сущности, описан в главе о разрешении имён. Дополнительная информация об “областях сброса”, используемых для запуска деструкторов, может быть найдена в главе о деструкторах.
Области видимости элементов
Имя элемента, объявленного непосредственно в модуле, имеет область видимости, которая простирается от начала модуля до конца модуля. Эти элементы также являются членами модуля и на них можно ссылаться с помощью пути, ведущего из их модуля.
Имя элемента, объявленного как оператор, имеет область видимости, которая простирается от начала блока, в котором находится оператор элемента, до конца блока.
Ошибкой является введение элемента с дублирующим именем другого элемента в том же пространстве имён в пределах того же модуля или блока. Звёздочные глобальные импорты имеют особое поведение для работы с дублирующими именами и затенением, см. связанную главу для подробностей.
Элементы в модуле могут затенять элементы в прелюдии.
Имена элементов из внешних модулей не находятся в области видимости внутри вложенного модуля. Путь может быть использован для ссылки на элемент в другом модуле.
Области видимости ассоциированных элементов
Ассоциированные элементы не имеют области видимости и на них можно ссылаться только с помощью пути, ведущего от типа или трейта, с которым они ассоциированы. Методы также могут быть вызваны через выражения вызова.
Подобно элементам внутри модуля или блока, ошибкой является введение элемента внутри трейта или реализации, который дублирует другой элемент в трейте или реализации в том же пространстве имён.
Области видимости привязок образцов
Область видимости привязки образца локальной переменной зависит от того, где она используется:
- Привязки оператора
letдействуют от момента сразу после оператораletдо конца блока, где она объявлена.
- Привязки параметров функции находятся в теле функции.
- Привязки параметров замыкания находятся в теле замыкания.
- Привязки
forнаходятся в теле цикла.
- Привязки веток
matchнаходятся в ограничителе match и выражении ветки match.
Области видимости локальных переменных не распространяются на объявления элементов.
Затенение привязок образцов
Привязкам образцов разрешено затенять любое имя в области видимости со следующими исключениями, которые являются ошибкой:
- Обобщённые параметры констант
- Статические элементы
- Константные элементы
- Конструкторы для структур и перечислений
Следующий пример иллюстрирует, как локальные привязки могут затенять объявления элементов:
#![allow(unused)] fn main() { fn shadow_example() { // Поскольку локальных переменных в области видимости ещё нет, это разрешается в функцию. foo(); // печатает `function` let foo = || println!("closure"); fn foo() { println!("function"); } // Это разрешается в локальное замыкание, поскольку оно затеняет элемент. foo(); // печатает `closure` } }
Области видимости обобщённых параметров
Обобщённые параметры объявляются в списке GenericParams. Область видимости обобщённого параметра находится в пределах элемента, на котором он объявлен.
Все параметры находятся в области видимости внутри списка обобщённых параметров независимо от порядка их объявления. Следующее показывает некоторые примеры, где на параметр можно ссылаться до его объявления:
#![allow(unused)] fn main() { // Ограничение 'b упоминается до его объявления. fn params_scope<'a: 'b, 'b>() {} trait SomeTrait<const Z: usize> {} // Константа N упоминается в ограничении трейта до её объявления. fn f<T: SomeTrait<N>, const N: usize>() {} }
Обобщённые параметры также находятся в области видимости для ограничений типов и where-предложений, например:
#![allow(unused)] fn main() { trait SomeTrait<'a, T> {} // <'a, U> для `SomeTrait` ссылаются на параметры 'a и U функции `bounds_scope`. fn bounds_scope<'a, T: SomeTrait<'a, U>, U>() {} fn where_scope<'a, T, U>() where T: SomeTrait<'a, U> {} }
Ошибкой является ссылка элементов, объявленных внутри функции, на обобщённый параметр из их внешней области видимости.
#![allow(unused)] fn main() { fn example<T>() { fn inner(x: T) {} // ОШИБКА: нельзя использовать обобщённые параметры из внешней функции } }
Затенение обобщённых параметров
Ошибкой является затенение обобщённого параметра, за исключением того, что элементам, объявленным внутри функций, разрешено затенять имена обобщённых параметров из функции.
#![allow(unused)] fn main() { fn example<'a, T, const N: usize>() { // Элементам внутри функций разрешено затенять обобщённые параметры в области видимости. fn inner_lifetime<'a>() {} // OK fn inner_type<T>() {} // OK fn inner_const<const N: usize>() {} // OK } }
#![allow(unused)] fn main() { trait SomeTrait<'a, T, const N: usize> { fn example_lifetime<'a>() {} // ОШИБКА: 'a уже используется fn example_type<T>() {} // ОШИБКА: T уже используется fn example_const<const N: usize>() {} // ОШИБКА: N уже используется fn example_mixed<const T: usize>() {} // ОШИБКА: T уже используется } }
Области видимости времён жизни
Параметры времени жизни объявляются в списке GenericParams и ограничениях трейтов высшего ранга.
Время жизни 'static и заполнитель времени жизни '_ имеют специальное значение и не могут быть объявлены как параметры.
Области видимости параметров времени жизни
Константные и статические элементы и константные контексты всегда допускают только ссылки с временем жизни 'static, поэтому никакое другое время жизни не может находиться в области видимости внутри них.
Ассоциированные константы допускают ссылки на времена жизни, объявленные в их трейте или реализации.
Области видимости ограничений трейтов высшего ранга
Область видимости параметра времени жизни, объявленного как ограничение трейта высшего ранга, зависит от сценария, где он используется.
- Как TypeBoundWhereClauseItem объявленные времена жизни находятся в области видимости в типе и ограничениях типов.
- Как TraitBound объявленные времена жизни находятся в области видимости в пути типа ограничения.
- Как BareFunctionType объявленные времена жизни находятся в области видимости в параметрах функции и возвращаемом типе.
#![allow(unused)] fn main() { trait Trait<'a>{} fn where_clause<T>() // 'a находится в области видимости как в типе, так и в ограничениях типов. where for <'a> &'a T: Trait<'a> {} fn bound<T>() // 'a находится в области видимости внутри ограничения. where T: for <'a> Trait<'a> {} struct Example<'a> { field: &'a u32 } // 'a находится в области видимости как в параметрах, так и в возвращаемом типе. type FnExample = for<'a> fn(x: Example<'a>) -> Example<'a>; }
Ограничения Impl trait
Типы Impl trait могут ссылаться только на времена жизни, объявленные в функции или реализации.
#![allow(unused)] fn main() { trait Trait1 { type Item; } trait Trait2<'a> {} struct Example; impl Trait1 for Example { type Item = Element; } struct Element; impl<'a> Trait2<'a> for Element {} // `impl Trait2` здесь не может ссылаться на 'b, но может // ссылаться на 'a. fn foo<'a>() -> impl for<'b> Trait1<Item = impl Trait2<'a> + use<'a>> { // ... Example } }
Области видимости меток циклов
Метки циклов могут быть объявлены выражением цикла.
Область видимости метки цикла простирается от точки её объявления до конца выражения цикла.
Область видимости не распространяется на элементы, замыкания, асинхронные блоки, константные аргументы, константные контексты и выражение итератора определяющего цикла for.
#![allow(unused)] fn main() { 'a: for n in 0..3 { if n % 2 == 0 { break 'a; } fn inner() { // Использование 'a здесь было бы ошибкой. // break 'a; } } // Метка находится в области видимости для выражения циклов `while`. 'a: while break 'a {} // Цикл не выполняется. 'a: while let _ = break 'a {} // Цикл не выполняется. // Метка не находится в области видимости в определяющем цикле `for`: 'a: for outer in 0..5 { // Это прервёт внешний цикл, пропустив внутренний цикл и остановив // внешний цикл. 'a: for inner in { break 'a; 0..1 } { println!("{}", inner); // Это не выполняется. } println!("{}", outer); // Это тоже не выполняется. } }
Метки циклов могут затенять метки с тем же именем во внешних областях видимости. Ссылки на метку относятся к ближайшему определению.
#![allow(unused)] fn main() { // Пример затенения метки цикла. 'a: for outer in 0..5 { 'a: for inner in 0..5 { // Это завершает внутренний цикл, но внешний цикл продолжает выполняться. break 'a; } } }
Области видимости прелюдий
Прелюдии вводят сущности в область видимости каждого модуля. Сущности не являются членами модуля, но неявно запрашиваются во время разрешения имён.
Имена прелюдий могут быть затенены объявлениями в модуле.
Прелюдии наслаиваются таким образом, что одна затеняет другую, если они содержат сущности с одинаковым именем. Порядок, в котором прелюдии могут затенять другие прелюдии, следующий, где более ранние записи могут затенять более поздние:
- Внешняя прелюдия
- Инструментальная прелюдия
- Прелюдия
macro_use - Прелюдия стандартной библиотеки
- Языковая прелюдия
Области видимости macro_rules
Область видимости макросов macro_rules описана в главе Макросы по примеру.
Поведение зависит от использования атрибутов macro_use и macro_export.
Вспомогательные атрибуты derive-макросов
Вспомогательные атрибуты derive-макросов находятся в области видимости в элементе, где указан их соответствующий атрибут derive.
Область видимости простирается от момента сразу после атрибута derive до конца элемента.
Вспомогательные атрибуты затеняют другие атрибуты с тем же именем в области видимости.
Область видимости Self
Хотя Self является ключевым словом со специальным значением, оно взаимодействует с разрешением имён способом, похожим на обычные имена.
Неявный тип Self в определении структуры, перечисления, объединения, трейта или реализации обрабатывается аналогично обобщённому параметру и находится в области видимости так же, как обобщённый параметр типа.
Неявный конструктор Self в пространстве имён значений реализации находится в области видимости в теле реализации (в ассоциированных элементах реализации).
#![allow(unused)] fn main() { // Тип Self в определении структуры. struct Recursive { f1: Option<Box<Self>> } // Тип Self в обобщённых параметрах. struct SelfGeneric<T: Into<Self>>(T); // Конструктор значения Self в реализации. struct ImplExample(); impl ImplExample { fn example() -> Self { // Тип Self Self() // Конструктор значения Self } } }
Прелюдии
Прелюдия — это коллекция имён, которые автоматически вводятся в область видимости каждого модуля в крейте.
Эти имена прелюдий не являются частью самого модуля: они неявно
запрашиваются во время разрешения имён. Например, даже если что-то вроде
Box находится в области видимости в каждом модуле, вы не можете ссылаться на него как на self::Box,
потому что он не является членом текущего модуля.
Существует несколько различных прелюдий:
- Прелюдия стандартной библиотеки
- Внешняя прелюдия
- Языковая прелюдия
- Прелюдия
macro_use - Инструментальная прелюдия
Прелюдия стандартной библиотеки
Каждый крейт имеет прелюдию стандартной библиотеки, которая состоит из имён из одного модуля стандартной библиотеки.
Используемый модуль зависит от редакции крейта и от того, применён ли атрибут no_std к крейту:
| Редакция | no_std не применён | no_std применён |
|---|---|---|
| 2015 | std::prelude::rust_2015 | core::prelude::rust_2015 |
| 2018 | std::prelude::rust_2018 | core::prelude::rust_2018 |
| 2021 | std::prelude::rust_2021 | core::prelude::rust_2021 |
| 2024 | std::prelude::rust_2024 | core::prelude::rust_2024 |
Note
std::prelude::rust_2015иstd::prelude::rust_2018имеют то же содержимое, что иstd::prelude::v1.
core::prelude::rust_2015иcore::prelude::rust_2018имеют то же содержимое, что иcore::prelude::v1.
Внешняя прелюдия
Внешние крейты, импортированные с помощью extern crate в корневом модуле или предоставленные
компилятору (как с флагом --extern в rustc), добавляются в
внешнюю прелюдию. Если импортированы с псевдонимом, например extern crate orig_name as new_name, то символ new_name вместо этого добавляется в прелюдию.
Крейт core всегда добавляется во внешнюю прелюдию.
Крейт std добавляется, если атрибут no_std не указан в корне крейта.
2018 Edition differences
В редакции 2015 года на крейты во внешней прелюдии нельзя ссылаться через use-объявления, поэтому общепринятой практикой является включение объявлений
extern crateдля их ввода в область видимости.Начиная с редакции 2018 года, use-объявления могут ссылаться на крейты во внешней прелюдии, поэтому использование
extern crateсчитается неидиоматичным.
Note
Дополнительные крейты, поставляемые с
rustc, такие какallocиtest, не включаются автоматически с флагом--externпри использовании Cargo. Они должны быть введены в область видимости с помощью объявленияextern crate, даже в редакции 2018.#![allow(unused)] fn main() { extern crate alloc; use alloc::rc::Rc; }Cargo добавляет
proc_macroво внешнюю прелюдию только для proc-макросных крейтов.
Атрибут no_std
По умолчанию стандартная библиотека автоматически включается в корневой модуль
крейта. Крейт std добавляется в корень вместе с неявным
атрибутом macro_use, подтягивающим все макросы, экспортированные из std, в
прелюдию macro_use. И core, и std добавляются во внешнюю
прелюдию.
Атрибут no_std может быть применён на уровне крейта, чтобы предотвратить
автоматическое добавление крейта std в область видимости.
Он делает три вещи:
- Предотвращает добавление
stdво внешнюю прелюдию.
- Влияет на то, какой модуль используется для составления прелюдии стандартной библиотеки (как описано выше).
- Внедряет крейт
coreв корень крейта вместоstdи подтягивает все макросы, экспортированные изcore, в прелюдиюmacro_use.
Note
Использование прелюдии core вместо стандартной прелюдии полезно, когда крейт предназначен для платформы, которая не поддерживает стандартную библиотеку, или целенаправленно не использует возможности стандартной библиотеки. Эти возможности в основном включают динамическое выделение памяти (например,
BoxиVec) и возможности работы с файлами и сетью (например,std::fsиstd::io).
Warning
Использование
no_stdне предотвращает связывание со стандартной библиотекой. Всё ещё допустимо помещатьextern crate std;в крейт, и зависимости также могут связывать её.
Языковая прелюдия
Языковая прелюдия включает имена типов и атрибутов, которые встроены в язык. Языковая прелюдия всегда находится в области видимости.
Она включает следующее:
- Пространство имён типов
- Логический тип —
bool - Текстовые типы —
charиstr - Целочисленные типы —
i8,i16,i32,i64,i128,u8,u16,u32,u64,u128 - Машинно-зависимые целочисленные типы —
usizeиisize - Типы с плавающей точкой —
f32иf64
- Логический тип —
- Пространство имён макросов
Прелюдия macro_use
Прелюдия macro_use включает макросы из внешних крейтов, которые были
импортированы атрибутом macro_use, применённым к extern crate.
Инструментальная прелюдия
Инструментальная прелюдия включает имена инструментов для внешних инструментов в пространстве имён типов. Смотрите раздел инструментальные атрибуты для подробностей.
Атрибут no_implicit_prelude
Атрибут no_implicit_prelude может быть применён на уровне крейта или
в модуле, чтобы указать, что не следует автоматически вводить прелюдию
стандартной библиотеки, внешнюю прелюдию или инструментальную прелюдию в область видимости для этого
модуля или любого из его потомков.
Этот атрибут не влияет на языковую прелюдию.
2018 Edition differences
В редакции 2015 атрибут
no_implicit_preludeне влияет на прелюдиюmacro_use, и все макросы, экспортированные из стандартной библиотеки, всё ещё включаются в прелюдиюmacro_use. Начиная с редакции 2018, он будет удалять прелюдиюmacro_use.
Пути
Путь — это последовательность из одного или более сегментов пути, разделённых токенами ::.
Пути используются для ссылки на элементы, значения, типы, макросы и атрибуты.
Два примера простых путей, состоящих только из идентификаторных сегментов:
x;
x::y::z;
Типы путей
Простые пути
Syntax
SimplePath →
::? SimplePathSegment ( :: SimplePathSegment )*
SimplePathSegment →
IDENTIFIER | super | self | crate | $crate
Простые пути используются в маркерах видимости, атрибутах, макросах и элементах use.
Например:
#![allow(unused)] fn main() { use std::io::{self, Write}; mod m { #[clippy::cyclomatic_complexity = "0"] pub (in super) fn f1() {} } }
Пути в выражениях
Syntax
PathInExpression →
::? PathExprSegment ( :: PathExprSegment )*
PathExprSegment →
PathIdentSegment ( :: GenericArgs )?
PathIdentSegment →
IDENTIFIER | super | self | Self | crate | $crate
GenericArgs →
< >
| < ( GenericArg , )* GenericArg ,? >
GenericArg →
Lifetime | Type | GenericArgsConst | GenericArgsBinding | GenericArgsBounds
GenericArgsConst →
BlockExpression
| LiteralExpression
| - LiteralExpression
| SimplePathSegment
GenericArgsBinding →
IDENTIFIER GenericArgs? = Type
GenericArgsBounds →
IDENTIFIER GenericArgs? : TypeParamBounds
Пути в выражениях позволяют указывать пути с обобщёнными аргументами. Они используются в различных местах в выражениях и образцах.
Токен :: требуется перед открывающим < для обобщённых аргументов, чтобы избежать
неоднозначности с оператором “меньше”. Это в просторечии известно как синтаксис “турбо-рыбы”.
#![allow(unused)] fn main() { (0..10).collect::<Vec<_>>(); Vec::<u8>::with_capacity(1024); }
Порядок обобщённых аргументов ограничен: сначала аргументы времени жизни, затем аргументы типов, затем аргументы констант, затем ограничения равенства.
Аргументы констант должны быть окружены фигурными скобками, если они не являются литералом, выводимой константой или путём из одного сегмента. Выводимая константа не может быть окружена фигурными скобками.
#![allow(unused)] fn main() { mod m { pub const C: usize = 1; } const C: usize = m::C; fn f<const N: usize>() -> [u8; N] { [0; N] } let _ = f::<1>(); // Литерал. let _: [_; 1] = f::<_>(); // Выводимая константа. let _: [_; 1] = f::<(((_)))>(); // Выводимая константа. let _ = f::<C>(); // Путь из одного сегмента. let _ = f::<{ m::C }>(); // Путь из нескольких сегментов должен быть в скобках. }
#![allow(unused)] fn main() { fn f<const N: usize>() -> [u8; N] { [0; _] } let _: [_; 1] = f::<{ _ }>(); // ^ ОШИБКА: `_` не разрешён здесь }
Note
В списке обобщённых аргументов выводимая константа разбирается как выводимый тип, но затем семантически обрабатывается как отдельный вид аргумента обобщённой константы.
Синтетические параметры типа, соответствующие типам impl Trait, являются неявными,
и они не могут быть указаны явно.
Квалифицированные пути
Syntax
QualifiedPathInExpression → QualifiedPathType ( :: PathExprSegment )+
QualifiedPathType → < Type ( as TypePath )? >
QualifiedPathInType → QualifiedPathType ( :: TypePathSegment )+
Полностью квалифицированные пути позволяют устранять неоднозначность пути для реализаций трейтов и для указания канонических путей. При использовании в спецификации типа они поддерживают использование синтаксиса типа, указанного ниже.
#![allow(unused)] fn main() { struct S; impl S { fn f() { println!("S"); } } trait T1 { fn f() { println!("T1 f"); } } impl T1 for S {} trait T2 { fn f() { println!("T2 f"); } } impl T2 for S {} S::f(); // Вызывает собственную реализацию. <S as T1>::f(); // Вызывает функцию трейта T1. <S as T2>::f(); // Вызывает функцию трейта T2. }
Пути в типах
Syntax
TypePath → ::? TypePathSegment ( :: TypePathSegment )*
TypePathSegment → PathIdentSegment ( ::? ( GenericArgs | TypePathFn ) )?
TypePathFn → ( TypePathFnInputs? ) ( -> TypeNoBounds )?
TypePathFnInputs → Type ( , Type )* ,?
Пути типов используются в определениях типов, ограничениях трейтов, ограничениях параметров типа и квалифицированных путях.
Хотя токен :: разрешён перед обобщёнными аргументами, он не требуется,
поскольку нет неоднозначности, как в PathInExpression.
#![allow(unused)] fn main() { mod ops { pub struct Range<T> {f1: T} pub trait Index<T> {} pub struct Example<'a> {f1: &'a i32} } struct S; impl ops::Index<ops::Range<usize>> for S { /*...*/ } fn i<'a>() -> impl Iterator<Item = ops::Example<'a>> { // ... const EXAMPLE: Vec<ops::Example<'static>> = Vec::new(); EXAMPLE.into_iter() } type G = std::boxed::Box<dyn std::ops::FnOnce(isize) -> isize>; }
Квалификаторы путей
Пути могут обозначаться различными ведущими квалификаторами для изменения смысла того, как они разрешаются.
::
Пути, начинающиеся с ::, считаются глобальными путями, где сегменты пути
начинают разрешаться из места, которое различается в зависимости от редакции. Каждый идентификатор в
пути должен разрешаться в элемент.
2018 Edition differences
В редакции 2015 идентификаторы разрешаются из “корня крейта” (
crate::в редакции 2018), который содержит различные элементы, включая внешние крейты, крейты по умолчанию, такие какstdилиcore, и элементы на верхнем уровне крейта (включая импортыuse).Начиная с редакции 2018, пути, начинающиеся с
::, разрешаются из крейтов во внешней прелюдии. То есть за ними должно следовать имя крейта.
#![allow(unused)] fn main() { pub fn foo() { // В редакции 2018 это обращается к `std` через внешнюю прелюдию. // В редакции 2015 это обращается к `std` через корень крейта. let now = ::std::time::Instant::now(); println!("{:?}", now); } }
// Редакция 2015 mod a { pub fn foo() {} } mod b { pub fn foo() { ::a::foo(); // вызывает функцию foo из `a` // В Rust 2018 `::a` интерпретировался бы как крейт `a`. } } fn main() {}
self
self разрешает путь относительно текущего модуля.
self может использоваться только как первый сегмент, без предшествующего ::.
В теле метода путь, состоящий из одного сегмента self, разрешается в параметр self метода.
fn foo() {} fn bar() { self::foo(); } struct S(bool); impl S { fn baz(self) { self.0; } } fn main() {}
Self
Self с заглавной “S” используется для ссылки на текущий тип, который реализуется или определяется. Он может использоваться в следующих ситуациях:
- В определении трейта он ссылается на тип, реализующий трейт.
- В реализации он ссылается на тип, который реализуется. При реализации структуры кортежа или единичной структуры он также ссылается на конструктор в пространстве имён значений.
- В определении структуры, перечисления или объединения он ссылается на определяемый тип. Определение не может быть бесконечно рекурсивным (должна быть косвенность).
Область видимости Self ведёт себя аналогично обобщённому параметру; см. раздел область видимости Self для подробностей.
Self может использоваться только как первый сегмент, без предшествующего ::.
Путь Self не может включать обобщённые аргументы (как в Self::<i32>).
#![allow(unused)] fn main() { trait T { type Item; const C: i32; // `Self` будет тем типом, который реализует `T`. fn new() -> Self; // `Self::Item` будет псевдонимом типа в реализации. fn f(&self) -> Self::Item; } struct S; impl T for S { type Item = i32; const C: i32 = 9; fn new() -> Self { // `Self` - это тип `S`. S } fn f(&self) -> Self::Item { // `Self::Item` - это тип `i32`. Self::C // `Self::C` - это константное значение `9`. } } // `Self` находится в области видимости внутри обобщённых параметров определения трейта, // для ссылки на определяемый тип. trait Add<Rhs = Self> { type Output; // `Self` также может ссылаться на ассоциированные элементы // реализуемого типа. fn add(self, rhs: Rhs) -> Self::Output; } struct NonEmptyList<T> { head: T, // Структура может ссылаться на себя (пока это не // бесконечная рекурсия). tail: Option<Box<Self>>, } }
super
super в пути разрешается в родительский модуль.
Он может использоваться только в ведущих сегментах пути, возможно после начального сегмента self.
mod a { pub fn foo() {} } mod b { pub fn foo() { super::a::foo(); // вызывает функцию foo из a } } fn main() {}
super может повторяться несколько раз после первого super или self для ссылки на
модули-предки.
mod a { fn foo() {} mod b { mod c { fn foo() { super::super::foo(); // вызывает функцию foo из a self::super::super::foo(); // вызывает функцию foo из a } } } } fn main() {}
crate
crate разрешает путь относительно текущего крейта.
crate может использоваться только как первый сегмент, без предшествующего ::.
fn foo() {} mod a { fn bar() { crate::foo(); } } fn main() {}
$crate
$crate используется только внутри транскриберов макросов и может использоваться только как первый
сегмент, без предшествующего ::.
$crate развернётся в путь для доступа к элементам из
верхнего уровня крейта, где определён макрос, независимо от того, в каком крейте макрос
вызывается.
pub fn increment(x: u32) -> u32 { x + 1 } #[macro_export] macro_rules! inc { ($x:expr) => ( $crate::increment($x) ) } fn main() { }
Канонические пути
Элементы, определённые в модуле или реализации, имеют канонический путь, который соответствует месту внутри его крейта, где он определён.
Все другие пути к этим элементам являются псевдонимами.
Канонический путь определяется как префикс пути, к которому добавлен сегмент пути, который определяет сам элемент.
Реализации и use-объявления не имеют канонических путей, хотя элементы, которые определяют реализации, имеют их. Элементы, определённые в блочных выражениях, не имеют канонических путей. Элементы, определённые в модуле, который не имеет канонического пути, не имеют канонического пути. Ассоциированные элементы, определённые в реализации, которая ссылается на элемент без канонического пути, например, как на реализуемый тип, реализуемый трейт, параметр типа или ограничение на параметр типа, не имеют канонических путей.
Префикс пути для модулей — это канонический путь к этому модулю.
Для простых реализаций это канонический путь элемента, который реализуется,
окружённый угловыми (<>) скобками.
Для реализаций трейтов это канонический путь элемента, который реализуется,
за которым следует as, за которым следует канонический путь к трейту, всё окружённое
угловыми (<>) скобками.
Канонический путь имеет смысл только в пределах данного крейта. Нет глобального пространства имён между крейтами; канонический путь элемента лишь идентифицирует его в пределах крейта.
// Комментарии показывают канонический путь элемента. mod a { // crate::a pub struct Struct; // crate::a::Struct pub trait Trait { // crate::a::Trait fn f(&self); // crate::a::Trait::f } impl Trait for Struct { fn f(&self) {} // <crate::a::Struct as crate::a::Trait>::f } impl Struct { fn g(&self) {} // <crate::a::Struct>::g } } mod without { // crate::without fn canonicals() { // crate::without::canonicals struct OtherStruct; // None trait OtherTrait { // None fn g(&self); // None } impl OtherTrait for OtherStruct { fn g(&self) {} // None } impl OtherTrait for crate::a::Struct { fn g(&self) {} // None } impl crate::a::Trait for OtherStruct { fn f(&self) {} // None } } } fn main() {}
Name resolution
Note
This is a placeholder for future expansion.
Видимость и приватность
Syntax
Visibility →
pub
| pub ( crate )
| pub ( self )
| pub ( super )
| pub ( in SimplePath )
Эти два термина часто используются взаимозаменяемо, и то, что они пытаются передать, — это ответ на вопрос “Можно ли использовать этот элемент в этом месте?”
Разрешение имён в Rust работает на глобальной иерархии пространств имён. Каждый уровень в иерархии можно рассматривать как некоторый элемент. Элементы — это один из тех, что упомянуты выше, но также включают внешние крейты. Объявление или определение нового модуля можно рассматривать как вставку нового дерева в иерархию в месте определения.
Чтобы контролировать, могут ли интерфейсы использоваться между модулями, Rust проверяет каждое использование элемента, чтобы определить, должно ли оно быть разрешено или нет. Здесь генерируются предупреждения о приватности, или иначе “вы использовали приватный элемент другого модуля, и это не было разрешено.”
По умолчанию всё является приватным, с двумя исключениями: Ассоциированные
элементы в pub трейте являются публичными по умолчанию; Варианты перечислений
в pub перечислении также являются публичными по умолчанию. Когда элемент объявлен как pub,
его можно рассматривать как доступный внешнему миру. Например:
fn main() {} // Объявляем приватную структуру struct Foo; // Объявляем публичную структуру с приватным полем pub struct Bar { field: i32, } // Объявляем публичное перечисление с двумя публичными вариантами pub enum State { PubliclyAccessibleState, PubliclyAccessibleState2, }
С понятием элемента как публичного или приватного Rust разрешает доступ к элементам в двух случаях:
- Если элемент публичный, то он может быть доступен извне из некоторого модуля
m, если вы можете получить доступ ко всем модулям-предкам элемента изm. Вы также потенциально можете называть элемент через реэкспорты. Смотрите ниже. - Если элемент приватный, он может быть доступен текущим модулем и его потомками.
Эти два случая удивительно мощны для создания иерархий модулей, открывающих публичные API, скрывая внутренние детали реализации. Чтобы помочь объяснить, вот несколько случаев использования и что они влекут:
-
Разработчику библиотеки нужно предоставить функциональность крейтам, которые линкуются с их библиотекой. Как следствие первого случая, это означает, что всё, что можно использовать извне, должно быть
pubот корня до целевого элемента. Любой приватный элемент в цепочке запретит внешние доступы. -
Крейту нужен глобально доступный “вспомогательный модуль” для себя, но он не хочет раскрывать вспомогательный модуль как публичное API. Чтобы достичь этого, корень иерархии крейта имел бы приватный модуль, который затем внутренне имеет “публичное API”. Поскольку весь крейт является потомком корня, то весь локальный крейт может обращаться к этому приватному модулю через второй случай.
-
При написании модульных тестов для модуля часто общепринятой идиомой является наличие непосредственного потомка тестируемого модуля с именем
mod test. Этот модуль мог бы обращаться к любым элементам родительского модуля через второй случай, что означает, что внутренние детали реализации также могут быть бесшовно протестированы из дочернего модуля.
Во втором случае упоминается, что приватный элемент “может быть доступен” текущим модулем и его потомками, но точное значение доступа к элементу зависит от того, что представляет собой элемент.
Доступ к модулю, например, означал бы заглядывание внутрь него (чтобы импортировать больше элементов). С другой стороны, доступ к функции означал бы, что она вызывается. Дополнительно, выражения путей и операторы импорта считаются доступом к элементу в том смысле, что импорт/выражение действителен только если цель находится в текущей области видимости.
Вот пример программы, которая иллюстрирует три случая, описанные выше:
// Этот модуль приватный, что означает, что никакой внешний крейт не может получить доступ к этому // модулю. Однако, поскольку он приватный в корне текущего крейта, любой // модуль в крейте может получить доступ к любому публично видимому элементу в этом модуле. mod crate_helper_module { // Эта функция может использоваться чем угодно в текущем крейте pub fn crate_helper() {} // Эта функция *не может* использоваться чем-либо ещё в крейте. Она не // публично видима вне `crate_helper_module`, поэтому только этот // текущий модуль и его потомки могут получить к ней доступ. fn implementation_detail() {} } // Эта функция "публична для корня", что означает, что она доступна внешним // крейтам, линкующимся с этим. pub fn public_api() {} // Аналогично 'public_api', этот модуль публичный, поэтому внешние крейты могут заглядывать // внутрь него. pub mod submodule { use crate::crate_helper_module; pub fn my_method() { // Любой элемент в локальном крейте может вызывать публичный интерфейс // вспомогательного модуля через комбинацию двух правил выше. crate_helper_module::crate_helper(); } // Эта функция скрыта от любого модуля, который не является потомком // `submodule` fn my_implementation() {} #[cfg(test)] mod test { #[test] fn test_my_implementation() { // Поскольку этот модуль является потомком `submodule`, ему разрешено // обращаться к приватным элементам внутри `submodule` без нарушения // приватности. super::my_implementation(); } } } fn main() {}
Чтобы программа на Rust прошла проверку приватности, все пути должны быть допустимыми доступами согласно двум правилам выше. Это включает все use-операторы, выражения, типы и т.д.
pub(in path), pub(crate), pub(super), и pub(self)
В дополнение к публичному и приватному, Rust позволяет пользователям объявлять элемент как
видимый только в пределах заданной области. Правила для ограничений pub таковы:
pub(in path)делает элемент видимым в пределах предоставленногоpath.pathдолжен быть простым путём, который разрешается в модуль-предок элемента, чья видимость объявляется. Каждый идентификатор вpathдолжен ссылаться непосредственно на модуль (а не на имя, введённое операторомuse).
pub(crate)делает элемент видимым в пределах текущего крейта.
pub(super)делает элемент видимым для родительского модуля. Это эквивалентноpub(in super).
pub(self)делает элемент видимым для текущего модуля. Это эквивалентноpub(in self)или отсутствиюpubвообще.
2018 Edition differences
Начиная с редакции 2018, пути для
pub(in path)должны начинаться сcrate,selfилиsuper. Редакция 2015 также может использовать пути, начинающиеся с::или модули из корня крейта.
Вот пример:
pub mod outer_mod { pub mod inner_mod { // Эта функция видима в пределах `outer_mod` pub(in crate::outer_mod) fn outer_mod_visible_fn() {} // То же, что выше, это действительно только в редакции 2015. pub(in outer_mod) fn outer_mod_visible_fn_2015() {} // Эта функция видима для всего крейта pub(crate) fn crate_visible_fn() {} // Эта функция видима в пределах `outer_mod` pub(super) fn super_mod_visible_fn() { // Эта функция видима, поскольку мы в том же `mod` inner_mod_visible_fn(); } // Эта функция видима только в пределах `inner_mod`, // что то же самое, что оставить её приватной. pub(self) fn inner_mod_visible_fn() {} } pub fn foo() { inner_mod::outer_mod_visible_fn(); inner_mod::crate_visible_fn(); inner_mod::super_mod_visible_fn(); // Эта функция больше не видима, поскольку мы вне `inner_mod` // Ошибка! `inner_mod_visible_fn` приватна //inner_mod::inner_mod_visible_fn(); } } fn bar() { // Эта функция всё ещё видима, поскольку мы в том же крейте outer_mod::inner_mod::crate_visible_fn(); // Эта функция больше не видима, поскольку мы вне `outer_mod` // Ошибка! `super_mod_visible_fn` приватна //outer_mod::inner_mod::super_mod_visible_fn(); // Эта функция больше не видима, поскольку мы вне `outer_mod` // Ошибка! `outer_mod_visible_fn` приватна //outer_mod::inner_mod::outer_mod_visible_fn(); outer_mod::foo(); } fn main() { bar() }
Note
Этот синтаксис только добавляет ещё одно ограничение к видимости элемента. Он не гарантирует, что элемент видим во всех частях указанной области. Чтобы получить доступ к элементу, все его родительские элементы до текущей области также должны быть видимы.
Реэкспорт и видимость
Rust позволяет публично реэкспортировать элементы через директиву pub use. Поскольку
это публичная директива, это позволяет использовать элемент в текущем
модуле через правила выше. Это по существу позволяет публичный доступ к
реэкспортированному элементу. Например, эта программа действительна:
pub use self::implementation::api; mod implementation { pub mod api { pub fn f() {} } } fn main() {}
Это означает, что любой внешний крейт, ссылающийся на implementation::api::f,
получил бы нарушение приватности, в то время как путь api::f был бы разрешён.
При реэкспорте приватного элемента можно считать, что это позволяет “цепочке приватности” быть сокращённой через реэкспорт вместо прохождения через иерархию пространств имён, как это было бы обычно.
Модель памяти
Warning
Модель памяти Rust не завершена и не полностью утверждена.
Байты
Самая базовая единица памяти в Rust — это байт.
Note
Хотя байты обычно преобразуются в аппаратные байты, Rust использует «абстрактное» представление о байтах, которое может проводить различия, отсутствующие в аппаратном обеспечении, такие как неинициализированное состояние или хранение части указателя. Эти различия могут влиять на наличие в вашей программе неопределённого поведения, поэтому они всё же оказывают ощутимое влияние на то, как скомпилированные программы Rust ведут себя.
Каждый байт может иметь одно из следующих значений:
- Инициализированный байт, содержащий значение
u8и опциональную провиниенцию,
- Неинициализированный байт.
Note
Вышеуказанный список пока не гарантированно является исчерпывающим.
Выделение памяти и время жизни
Элементы программы — это те функции, модули и типы, значения которых вычисляются во время компиляции и уникальным образом хранятся в образе памяти процесса Rust. Элементы не выделяются и не освобождаются динамически.
Куча — это общий термин, описывающий Box-ы (значения в куче). Время жизни выделения в куче зависит от времени жизни значений Box, указывающих на него. Поскольку значения Box сами могут передаваться внутрь и из стека вызовов (фреймов) или храниться в куче, выделения в куче могут переживать фрейм, в пределах которого они были выделены. Гарантируется, что выделение в куче будет находиться по единственному адресу в куче в течение всего времени жизни этого выделения — оно никогда не будет перемещено в результате перемещения значения Box.
Переменные
Переменная — это компонент стека вызовов (фрейма), являющийся либо именованным параметром функции, либо анонимным временным значением, либо именованной локальной переменной.
Локальная переменная (или стек-локальное выделение памяти) хранит значение напрямую, размещенное в памяти стека. Значение является частью стека вызовов (фрейма).
Локальные переменные являются неизменяемыми, если не объявлены иначе. Например: let mut x = ....
Параметры функций являются неизменяемыми, если не объявлены с mut. Ключевое слово mut применяется только к следующему за ним параметру. Например: |mut x, y| и fn f(mut x: Box<i32>, y: Box<i32>) объявляют одну изменяемую переменную x и одну неизменяемую переменную y.
Локальные переменные не инициализируются при выделении памяти. Вместо этого весь набор локальных переменных фрейма выделяется (при входе во фрейм) в неинициализированном состоянии. Последующие операторы внутри функции могут инициализировать локальные переменные, а могут и не делать этого. Локальные переменные могут использоваться только после того, как они были инициализированы по всем достижимым путям потока управления.
В следующем примере init_after_if инициализируется после if выражения, тогда как uninit_after_if — нет, потому что она не инициализируется в ветке else.
#![allow(unused)] fn main() { fn random_bool() -> bool { true } fn initialization_example() { let init_after_if: (); let uninit_after_if: (); if random_bool() { init_after_if = (); uninit_after_if = (); } else { init_after_if = (); } init_after_if; // ok // uninit_after_if; // ошибка: использование возможно неинициализированной `uninit_after_if` } }
Паника (Panic)
Rust предоставляет механизм, который позволяет функции не возвращать управление обычным образом, а вместо этого “паниковать”. Это реакция на условие ошибки, которое обычно считается невосстановимым в контексте, где эта ошибка возникла.
Некоторые языковые конструкции, такие как индексация массива за его пределами, паникуют автоматически.
Также существуют языковые возможности, которые предоставляют определенный уровень контроля над поведением паники:
- Обработчик паники определяет поведение при панике.
- FFI ABI могут изменять поведение паники.
Note
Стандартная библиотека предоставляет возможность явно вызвать панику с помощью макроса
panic!.
Атрибут panic_handler
Атрибут panic_handler может быть применен к функции для определения поведения при панике.
Атрибут panic_handler может быть применен только к функции с сигнатурой fn(&PanicInfo) -> !.
Note
Структура
PanicInfoсодержит информацию о месте возникновения паники.
В графе зависимостей должна присутствовать ровно одна функция panic_handler.
Ниже показана функция panic_handler, которая записывает сообщение о панике и затем останавливает поток.
#![no_std]
use core::fmt::{self, Write};
use core::panic::PanicInfo;
struct Sink {
// ..
_0: (),
}
impl Sink {
fn new() -> Sink { Sink { _0: () }}
}
impl fmt::Write for Sink {
fn write_str(&mut self, _: &str) -> fmt::Result { Ok(()) }
}
#[panic_handler]
fn panic(info: &PanicInfo) -> ! {
let mut sink = Sink::new();
// записывает "panicked at '$reason', src/main.rs:27:4" в некоторый `sink`
let _ = writeln!(sink, "{}", info);
loop {}
}
Стандартное поведение
std предоставляет два различных обработчика паники:
unwind— раскручивает стек и потенциально является восстанавливаемым.abort— аварийно завершает процесс и является невосстанавливаемым.
Не все целевые платформы могут предоставлять обработчик unwind.
Note
Обработчик паники, используемый при линковке с
std, может быть задан с помощью флага командной строки-C panic. По умолчанию для большинства целей используетсяunwind.Поведение паники в стандартной библиотеке может быть изменено во время выполнения с помощью функции
std::panic::set_hook.
Линковка бинарного файла no_std, динамической библиотеки (dylib, cdylib) или статической библиотеки (staticlib) потребует указания собственного обработчика паники.
Стратегия паники (Panic strategy)
Стратегия паники определяет тип поведения при панике, для поддержки которого собирается крейт.
Note
Стратегия паники может быть выбрана в
rustcс помощью флага командной строки-C panic.При создании бинарного файла, динамической библиотеки (dylib, cdylib) или статической библиотеки (staticlib) и линковке с
std, флаг-C panicтакже влияет на то, какой обработчик паники будет использован.
Note
При компиляции кода со стратегией паники
abortоптимизатор может предполагать, что раскрутка стека через фреймы Rust невозможна, что может привести как к уменьшению размера кода, так и к повышению скорости выполнения.
Note
См. link.unwinding об ограничениях на линковку крейтов с разными стратегиями паники. Из этого следует, что крейты, собранные со стратегией
unwind, могут использовать обработчик паникиabort, но стратегияabortне может использовать обработчик паникиunwind.
Раскрутка стека (Unwinding)
Паника может быть либо восстанавливаемой, либо невосстанавливаемой, хотя это можно настроить (выбрав обработчик паники, не производящий раскрутку) так, чтобы она всегда была невосстанавливаемой. (Обратное неверно: обработчик unwind не гарантирует, что все паники являются восстанавливаемыми, а лишь то, что паника, вызванная макросом panic! и подобными механизмами стандартной библиотеки, является восстанавливаемой.)
Когда возникает паника, обработчик unwind “раскручивает” фреймы Rust, подобно тому как throw в C++ раскручивает фреймы C++, до тех пор пока паника не достигнет точки восстановления (например, на границе потока). Это означает, что пока паника проходит через фреймы Rust, у живых объектов в этих фреймах, которые реализуют Drop, будут вызваны их методы drop. Таким образом, когда нормальное выполнение возобновится, более недоступные объекты будут “очищены” так, как если бы они вышли из области видимости обычным образом.
Note
Пока сохраняется эта гарантия очистки ресурсов, “раскрутка” может быть реализована без фактического использования механизма, используемого C++ для целевой платформы.
Note
Стандартная библиотека предоставляет два механизма для восстановления после паники:
std::panic::catch_unwind(который позволяет восстановиться внутри паникующего потока) иstd::thread::spawn(который автоматически настраивает восстановление после паники для порожденного потока, чтобы другие потоки могли продолжать работу).
Раскрутка через границы FFI
Возможна раскрутка стека через границы FFI с использованием соответствующего объявления ABI. Хотя это полезно в определенных случаях, это создает уникальные возможности для неопределенного поведения, особенно когда задействованы среды выполнения нескольких языков.
Раскрутка стека с неправильным ABI является неопределенным поведением:
- Вызов раскрутки в код Rust из внешней функции, которая была вызвана через объявление функции или указатель, объявленный с непредусматривающим-раскрутку ABI, таким как
"C","system"и т.д. (Например, это происходит, когда такая функция, написанная на C++, генерирует исключение, которое не перехватывается и передается в Rust.) - Вызов Rust
externфункции, которая производит раскрутку (сextern "C-unwind"или другим ABI, разрешающим раскрутку), из кода, который не поддерживает раскрутку, такого как код, скомпилированный с помощью GCC или Clang с использованием-fno-exceptions.
Перехват операции раскрутки извне (такой как исключение C++) с помощью std::panic::catch_unwind, std::thread::JoinHandle::join или позволение ей распространиться за пределы функции Rust main() или корня потока приведет к одному из двух видов поведения, и не указано, какое именно произойдет:
- Процесс аварийно завершается.
- Функция возвращает
Result::Err, содержащий непрозрачный тип.
Note
Код Rust, скомпилированный или слинкованный с другим экземпляром стандартной библиотеки Rust, считается “исключением извне” для целей этой гарантии. Таким образом, библиотека, которая использует
panic!и слинкована с одной версией стандартной библиотеки Rust, вызванная из приложения, которое использует другую версию стандартной библиотеки, может привести к аварийному завершению всего приложения, даже если библиотека используется только внутри дочернего потока.
В настоящее время нет гарантий относительно поведения, которое происходит, когда внешняя среда выполнения пытается обработать или повторно сгенерировать полезную нагрузку (payload) Rust panic. Другими словами, раскрутка, исходящая из среды выполнения Rust, должна либо привести к завершению процесса, либо быть перехвачена той же самой средой выполнения.
Компоновка (Linkage)
Note
Этот раздел описывается скорее с точки зрения компилятора, чем языка.
Компилятор поддерживает различные методы для компоновки крейтов вместе как статически, так и динамически. В этом разделе будут рассмотрены различные методы компоновки крейтов, а дополнительная информация о нативных библиотеках может быть найдена в разделе FFI книги.
rffi: ../book/ch19-01-unsafe-rust.html#using-extern-functions-to-call-external-code
За одну сессию компиляции компилятор может сгенерировать несколько артефактов с использованием либо флагов командной строки, либо атрибута crate_type. Если указан один или несколько флагов командной строки, все атрибуты crate_type будут проигнорированы в пользу сборки только тех артефактов, которые указаны в командной строке.
--crate-type=bin,#![crate_type = "bin"]- Будет создан запускаемый исполняемый файл. Это требует наличия функцииmainв крейте, которая будет запущена при начале выполнения программы. Это слинкует все зависимости Rust и нативные зависимости, производя единый распространяемый бинарный файл. Это тип крейта по умолчанию.
--crate-type=lib,#![crate_type = "lib"]- Будет создана библиотека Rust. Это неоднозначное понятие, потому что библиотека может проявляться в нескольких формах. Цель этой общей опцииlib- сгенерировать “рекомендуемый компилятором” стиль библиотеки. Выходная библиотека всегда будет пригодна для использования rustc, но фактический тип библиотеки может меняться время от времени. Оставшиеся типы вывода - это все различные варианты библиотек, и типlibможно рассматривать как псевдоним одного из них (но фактический выбор определяется компилятором).
--crate-type=dylib,#![crate_type = "dylib"]- Будет создана динамическая библиотека Rust. Это отличается от типа выводаlibтем, что это принудительно генерирует динамическую библиотеку. Полученная динамическая библиотека может быть использована как зависимость для других библиотек и/или исполняемых файлов. Этот тип вывода создаст файлы*.soна Linux,*.dylibна macOS и*.dllна Windows.
-
--crate-type=staticlib,#![crate_type = "staticlib"]- Будет создана статическая системная библиотека. Это отличается от других выходных форматов библиотек тем, что компилятор никогда не будет пытаться линковаться с выводамиstaticlib. Цель этого типа вывода - создать статическую библиотеку, содержащую весь код локального крейта вместе со всеми восходящими зависимостями. Этот тип вывода создаст файлы*.aна Linux, macOS и Windows (MinGW) и файлы*.libна Windows (MSVC). Этот формат рекомендуется для использования в таких ситуациях, как связывание кода Rust в существующее приложение не на Rust, потому что у него не будет динамических зависимостей от другого кода Rust.Обратите внимание, что любые динамические зависимости, которые может иметь статическая библиотека (такие как зависимости от системных библиотек или зависимости от библиотек Rust, которые скомпилированы как динамические библиотеки), должны быть указаны вручную при линковке этой статической библиотеки откуда-либо. Флаг
--print=native-static-libsможет помочь с этим.Обратите внимание, что, поскольку результирующая статическая библиотека содержит код всех зависимостей, включая стандартную библиотеку, а также экспортирует все их публичные символы, линковка статической библиотеки в исполняемый файл или общую библиотеку может потребовать особого внимания. В случае общей библиотеки список экспортируемых символов должен быть ограничен с помощью, например, скрипта линковщика или скрипта управления версиями символов, списка экспортируемых символов (macOS) или файла определений модуля (Windows). Дополнительно, неиспользуемые секции могут быть удалены, чтобы убрать весь код зависимостей, который фактически не используется (например,
--gc-sectionsили-dead_stripдля macOS).
--crate-type=cdylib,#![crate_type = "cdylib"]- Будет создана динамическая системная библиотека. Это используется при компиляции динамической библиотеки для загрузки из другого языка. Этот тип вывода создаст файлы*.soна Linux,*.dylibна macOS и*.dllфайлы на Windows.
--crate-type=rlib,#![crate_type = "rlib"]- Будет создан файл “библиотеки Rust”. Это используется как промежуточный артефакт, и его можно рассматривать как “статическую библиотеку Rust”. Эти файлыrlib, в отличие от файловstaticlib, интерпретируются компилятором при будущей линковке. Это essentially означает, чтоrustcбудет искать метаданные в файлахrlibтак же, как он ищет метаданные в динамических библиотеках. Эта форма вывода используется для производства статически слинкованных исполняемых файлов, а также выводовstaticlib.
--crate-type=proc-macro,#![crate_type = "proc-macro"]- Производимый вывод не специфицирован, но если к нему предоставлен путь-L, то компилятор распознает выходные артефакты как макрос, и он может быть загружен для программы. Крейты, скомпилированные с этим типом крейта, должны экспортировать только процедурные макросы. Компилятор автоматически установит опцию конфигурацииproc_macro. Крейты всегда компилируются с той же целью, с которой был построен сам компилятор. Например, если вы запускаете компилятор из Linux с процессоромx86_64, целью будетx86_64-unknown-linux-gnu, даже если крейт является зависимостью другого крейта, который строится для другой цели.
Обратите внимание, что эти выходные данные являются складываемыми в том смысле, что если указано несколько, то компилятор произведет каждую форму вывода без необходимости перекомпиляции. Однако это применяется только для выходных данных, указанных одним и тем же методом. Если указаны только атрибуты crate_type, то все они будут построены, но если указаны один или несколько флагов командной строки --crate-type, то будут построены только эти выходные данные.
Со всеми этими различными видами выходных данных, если крейт A зависит от крейта B, то компилятор может найти B в различных формах по всей системе. Однако единственные формы, которые ищет компилятор, - это формат rlib и формат динамической библиотеки. Имея эти два варианта для зависимой библиотеки, компилятор в какой-то момент должен сделать выбор между этими двумя форматами. Имея это в виду, компилятор следует этим правилам при определении того, какой формат зависимостей будет использоваться:
-
Если производится статическая библиотека, все восходящие зависимости должны быть доступны в форматах
rlib. Это требование вытекает из того, что динамическую библиотеку нельзя преобразовать в статический формат.Обратите внимание, что невозможно линковать нативные динамические зависимости в статическую библиотеку, и в этом случае будут выводиться предупреждения о всех неслинкованных нативных динамических зависимостях.
-
Если производится файл
rlib, то нет ограничений на то, в каком формате доступны восходящие зависимости. Просто требуется, чтобы все восходящие зависимости были доступны для чтения метаданных из них.Причина этого в том, что файлы
rlibне содержат никаких своих восходящих зависимостей. Было бы не очень эффективно, если бы все файлыrlibсодержали копиюlibstd.rlib!
- Если производится исполняемый файл и флаг
-C prefer-dynamicне указан, то сначала предпринимается попытка найти зависимости в форматеrlib. Если некоторые зависимости недоступны в формате rlib, то предпринимается попытка динамической линковки (см. ниже).
-
Если производится динамическая библиотека или исполняемый файл, который линкуется динамически, то компилятор попытается согласовать доступные зависимости в формате rlib или dylib, чтобы создать конечный продукт.
Основная цель компилятора - обеспечить, чтобы библиотека никогда не появлялась более одного раза в любом артефакте. Например, если динамические библиотеки B и C были статически слинкованы с библиотекой A, то крейт не может слинковаться с B и C вместе, потому что было бы две копии A. Компилятор позволяет смешивать форматы rlib и dylib, но это ограничение должно быть соблюдено.
Компилятор в настоящее время не реализует никакого метода указания, с каким форматом должна быть слинкована библиотека. При динамической линковке компилятор попытается максимизировать динамические зависимости, все еще позволяя некоторым зависимостям быть слинкованными через rlib.
Для большинства ситуаций рекомендуется иметь все библиотеки доступными как dylib, если выполняется динамическая линковка. Для других ситуаций компилятор выдаст предупреждение, если он не сможет определить, с какими форматами линковать каждую библиотеку.
В общем, --crate-type=bin или --crate-type=lib должно быть достаточно для всех потребностей компиляции, а другие опции просто доступны, если требуется более детальный контроль над форматом вывода крейта.
Статические и динамические C рантаймы
Стандартная библиотека в целом стремится поддерживать как статически линкованные, так и динамически линкованные C рантаймы для целей, где это уместно. Например, цели x86_64-pc-windows-msvc и x86_64-unknown-linux-musl обычно поставляются с обоими рантаймами, и пользователь выбирает, какой он хотел бы. Все цели в компиляторе имеют режим по умолчанию для линковки с C рантаймом. Обычно цели линкуются динамически по умолчанию, но есть исключения, которые являются статическими по умолчанию, такие как:
arm-unknown-linux-musleabiarm-unknown-linux-musleabihfarmv7-unknown-linux-musleabihfi686-unknown-linux-muslx86_64-unknown-linux-musl
Линковка C рантайма настроена на соблюдение целевой функции crt-static. Эти целевые функции обычно настраиваются из командной строки через флаги для самого компилятора. Например, чтобы включить статический рантайм, вы должны выполнить:
rustc -C target-feature=+crt-static foo.rs
тогда как для динамической линковки с C рантаймом вы должны выполнить:
rustc -C target-feature=-crt-static foo.rs
Цели, которые не поддерживают переключение между линковкой C рантайма, проигнорируют этот флаг. Рекомендуется проверить полученный бинарный файл, чтобы убедиться, что он слинкован так, как вы ожидаете, после успешного завершения работы компилятора.
Крейты также могут узнавать о том, как линкуется C рантайм. Код на MSVC, например, должен компилироваться по-разному (например, с /MT или /MD) в зависимости от линкуемого рантайма. В настоящее время это экспортируется через опцию target_feature атрибута cfg:
#![allow(unused)] fn main() { #[cfg(target_feature = "crt-static")] fn foo() { println!("C рантайм должен быть статически слинкован"); } #[cfg(not(target_feature = "crt-static"))] fn foo() { println!("C рантайм должен быть динамически слинкован"); } }
Также обратите внимание, что скрипты сборки Cargo могут узнавать об этой функции через переменные окружения. В скрипте сборки вы можете обнаружить линковку через:
use std::env; fn main() { let linkage = env::var("CARGO_CFG_TARGET_FEATURE").unwrap_or(String::new()); if linkage.contains("crt-static") { println!("C рантайм будет статически слинкован"); } else { println!("C рантайм будет динамически слинкован"); } }
Чтобы использовать эту функцию локально, вы обычно будете использовать переменную окружения RUSTFLAGS для указания флагов компилятору через Cargo. Например, чтобы скомпилировать статически слинкованный бинарный файл на MSVC, вы должны выполнить:
RUSTFLAGS='-C target-feature=+crt-static' cargo build --target x86_64-pc-windows-msvc
Смешанные кодовые базы Rust и кода других языков
Если вы смешиваете Rust с кодом других языков (например, C, C++) и хотите создать единый бинарный файл, содержащий оба типа кода, у вас есть два подхода для финальной линковки бинарного файла:
- Используйте
rustc. Передавайте любые не-Rust библиотеки, используя аргументы-L <directory>и-l<library>в rustc, и/или директивы#[link]в вашем коде Rust. Если вам нужно линковаться с файлами.o, вы можете использовать-Clink-arg=file.o. - Используйте ваш линковщик другого языка. В этом случае вам сначала нужно сгенерировать цель
staticlibRust и передать ее в вызов вашего линковщика. Если вам нужно слинковать несколько подсистем Rust, вам нужно будет сгенерировать единственныйstaticlib, возможно, используя множество операторовextern crateдля включения несколькихrlibRust. Несколько файловstaticlibRust, скорее всего, будут конфликтовать.
Прямая передача rlib в ваш линковщик другого языка в настоящее время не поддерживается.
Note
Код Rust, скомпилированный или слинкованный с другим экземпляром рантайма Rust, считается “внешним кодом” для целей этого раздела.
Запрещенная линковка и раскрутка стека
Раскрутка стека при панике (unwinding) может быть использована только если бинарный файл построен последовательно в соответствии со следующими правилами.
Артефакт Rust называется потенциально раскручивающим, если выполняется любое из следующих условий:
- Артефакт использует обработчик паники
unwind. - Артефакт содержит крейт, собранный со стратегией паники
unwindpanic strategy, который совершает вызов функции, используя ABI-unwind. - Артефакт совершает вызов ABI
"Rust"в код, выполняющийся в другом артефакте Rust, который имеет отдельную копию рантайма Rust, и этот другой артефакт является потенциально раскручивающим.
Note
Это определение отражает, может ли вызов ABI
"Rust"внутри артефакта Rust когда-либо раскручивать стек.
Если артефакт Rust является потенциально раскручивающим, то все его крейты должны быть собраны со стратегией паники unwind panic strategy. В противном случае раскрутка стека может вызвать неопределенное поведение.
Note
Если вы используете
rustcдля линковки, эти правила применяются автоматически. Если вы не используетеrustcдля линковки, вы должны позаботиться о том, чтобы раскрутка стека обрабатывалась последовательно во всем бинарном файле. Линковка безrustcвключает использованиеdlopenили подобных средств, где линковка выполняется системным рантаймом без участияrustc. Это может произойти только при смешивании кода с разными флагами-C panic, поэтому большинству пользователей не нужно беспокоиться об этом.
Note
Чтобы гарантировать, что библиотека будет корректной (и линкуемой с
rustc) независимо от используемого рантайма паники во время линковки, может использоваться lintffi_unwind_calls. Этот lint помечает любые вызовы-unwindиностранных функций или указателей на функции.
Встроенная ассемблерная вставка (Inline assembly)
Поддержка встроенного ассемблера предоставляется через макросы asm!, naked_asm! и global_asm!.
Она может быть использована для вставки написанного вручную ассемблерного кода в ассемблерный вывод, генерируемый компилятором.
Поддержка встроенного ассемблера стабилизирована на следующих архитектурах:
- x86 и x86-64
- ARM
- AArch64 и Arm64EC
- RISC-V
- LoongArch
- s390x
Компилятор выдаст ошибку, если макрос ассемблера используется на неподдерживаемой цели.
Пример
#![allow(unused)] fn main() { #[cfg(target_arch = "x86_64")] { use std::arch::asm; // Умножаем x на 6, используя сдвиги и сложения let mut x: u64 = 4; unsafe { asm!( "mov {tmp}, {x}", "shl {tmp}, 1", "shl {x}, 2", "add {x}, {tmp}", x = inout(reg) x, tmp = out(reg) _, ); } assert_eq!(x, 4 * 6); } }
Синтаксис
Следующая грамматика определяет аргументы, которые могут быть переданы макросам asm!, global_asm! и naked_asm!.
Syntax
AsmArgs → FormatString ( , FormatString )* ( , AsmOperand )* ,?
FormatString → STRING_LITERAL | RAW_STRING_LITERAL | MacroInvocation
AsmOperand →
ClobberAbi
| AsmOptions
| RegOperand
ClobberAbi → clobber_abi ( Abi ( , Abi )* ,? )
AsmOptions →
options ( ( AsmOption ( , AsmOption )* ,? )? )
AsmOption →
pure
| nomem
| readonly
| preserves_flags
| noreturn
| nostack
| att_syntax
| raw
RegOperand → ( ParamName = )?
(
DirSpec ( RegSpec ) Expression
| DualDirSpec ( RegSpec ) DualDirSpecExpression
| sym PathExpression
| const Expression
| label { Statements? }
)
ParamName → IDENTIFIER_OR_KEYWORD | RAW_IDENTIFIER
DualDirSpecExpression →
Expression
| Expression => Expression
RegSpec → RegisterClass | ExplicitRegister
RegisterClass → IDENTIFIER_OR_KEYWORD
ExplicitRegister → STRING_LITERAL
DirSpec →
in
| out
| lateout
DualDirSpec →
inout
| inlateout
Область видимости
Встроенный ассемблер может быть использован одним из трех способов.
С макросом asm! ассемблерный код излучается в области видимости функции и интегрируется в сгенерированный компилятором ассемблерный код функции.
Этот ассемблерный код должен подчиняться строгим правилам, чтобы избежать неопределенного поведения.
Обратите внимание, что в некоторых случаях компилятор может выбрать излучение ассемблерного кода как отдельной функции и генерацию вызова к ней.
#![allow(unused)] fn main() { #[cfg(target_arch = "x86_64")] { unsafe { core::arch::asm!("/* {} */", in(reg) 0); } } }
С макросом naked_asm! ассемблерный код излучается в области видимости функции и составляет полный ассемблерный код функции. Макрос naked_asm! разрешен только в голых функциях (naked functions).
#![allow(unused)] fn main() { #[cfg(target_arch = "x86_64")] { #[unsafe(naked)] extern "C" fn wrapper() { core::arch::naked_asm!("/* {} */", const 0); } } }
С макросом global_asm! ассемблерный код излучается в глобальной области видимости, вне функции.
Это может быть использовано для написания целых функций с использованием ассемблерного кода и, как правило, предоставляет гораздо больше свободы для использования произвольных регистров и ассемблерных директив.
fn main() {} #[cfg(target_arch = "x86_64")] core::arch::global_asm!("/* {} */", const 0);
Аргументы строки-шаблона
Шаблон ассемблера использует тот же синтаксис, что и строки форматирования (т.е. заполнители указываются фигурными скобками).
Соответствующие аргументы доступны по порядку, по индексу или по имени.
#![allow(unused)] fn main() { #[cfg(target_arch = "x86_64")] { let x: i64; let y: i64; let z: i64; // Это unsafe { core::arch::asm!("mov {}, {}", out(reg) x, in(reg) 5); } // ... это unsafe { core::arch::asm!("mov {0}, {1}", out(reg) y, in(reg) 5); } // ... и это unsafe { core::arch::asm!("mov {out}, {in}", out = out(reg) z, in = in(reg) 5); } // все имеют одинаковое поведение assert_eq!(x, y); assert_eq!(y, z); } }
Однако, неявные именованные аргументы (введенные RFC #2795) не поддерживаются.
#![allow(unused)] fn main() { #[cfg(target_arch = "x86_64")] { let x = 5; // Мы не можем ссылаться на `x` из области видимости напрямую, нам нужен операнд, такой как `in(reg) x` unsafe { core::arch::asm!("/* {x} */"); } // ОШИБКА: нет аргумента с именем x } #[cfg(not(target_arch = "x86_64"))] core::compile_error!("Тест не поддерживается на этой архитектуре"); }
Вызов asm! может иметь один или несколько аргументов строки-шаблона; asm! с несколькими аргументами строки-шаблона обрабатывается так, как если бы все строки были объединены с \n между ними.
Ожидается, что каждый аргумент строки-шаблона соответствует строке ассемблерного кода.
#![allow(unused)] fn main() { #[cfg(target_arch = "x86_64")] { let x: i64; let y: i64; // Мы можем разделить несколько строк, как если бы они были написаны вместе unsafe { core::arch::asm!("mov eax, 5", "mov ecx, eax", out("rax") x, out("rcx") y); } assert_eq!(x, y); } }
Все аргументы строки-шаблона должны появляться перед любыми другими аргументами.
#![allow(unused)] fn main() { let x = 5; #[cfg(target_arch = "x86_64")] { // Строки-шаблоны должны появляться первыми в вызове asm unsafe { core::arch::asm!("/* {x} */", x = const 5, "ud2"); } // ОШИБКА: неожиданная лексема } #[cfg(not(target_arch = "x86_64"))] core::compile_error!("Тест не поддерживается на этой архитектуре"); }
Как и в случае со строками форматирования, позиционные аргументы должны появляться перед именованными аргументами и явными операндами регистров.
#![allow(unused)] fn main() { #[cfg(target_arch = "x86_64")] { // Именованные операнды должны идти после позиционных unsafe { core::arch::asm!("/* {x} {} */", x = const 5, in(reg) 5); } // ОШИБКА: позиционные аргументы не могут следовать за именованными аргументами или явными аргументами регистров } #[cfg(not(target_arch = "x86_64"))] core::compile_error!("Тест не поддерживается на этой архитектуре"); }
#![allow(unused)] fn main() { #[cfg(target_arch = "x86_64")] { // Мы также не можем поместить явные регистры перед позиционными операндами unsafe { core::arch::asm!("/* {} */", in("eax") 0, in(reg) 5); } // ОШИБКА: позиционные аргументы не могут следовать за именованными аргументами или явными аргументами регистров } #[cfg(not(target_arch = "x86_64"))] core::compile_error!("Тест не поддерживается на этой архитектуре"); }
Явные операнды регистров не могут использоваться заполнителями в строке-шаблоне.
#![allow(unused)] fn main() { #[cfg(target_arch = "x86_64")] { // Явные операнды регистров не подставляются, используйте `eax` явно в строке unsafe { core::arch::asm!("/* {} */", in("eax") 5); } // ОШИБКА: неверная ссылка на аргумент по индексу 0 } #[cfg(not(target_arch = "x86_64"))] core::compile_error!("Тест не поддерживается на этой архитектуре"); }
Все остальные именованные и позиционные операнды должны появляться по крайней мере один раз в строке-шаблоне, иначе генерируется ошибка компилятора.
#![allow(unused)] fn main() { #[cfg(target_arch = "x86_64")] { // Мы должны назвать все операнды в строке формата unsafe { core::arch::asm!("", in(reg) 5, x = const 5); } // ОШИБКА: несколько неиспользуемых аргументов asm } #[cfg(not(target_arch = "x86_64"))] core::compile_error!("Тест не поддерживается на этой архитектуре"); }
Точный синтаксис ассемблерного кода зависит от цели и непрозрачен для компилятора, за исключением способа подстановки операндов в строку-шаблон для формирования кода, передаваемого ассемблеру.
В настоящее время все поддерживаемые цели следуют синтаксису ассемблерного кода, используемому внутренним ассемблером LLVM, который обычно соответствует синтаксису ассемблера GNU (GAS).
На x86 по умолчанию используется режим .intel_syntax noprefix GAS.
На ARM используется режим .syntax unified.
Эти цели накладывают дополнительное ограничение на ассемблерный код: любое состояние ассемблера (например, текущая секция, которую можно изменить с помощью .section) должно быть восстановлено до исходного значения в конце строки asm.
Ассемблерный код, не соответствующий синтаксису GAS, приведет к поведению, специфичному для ассемблера.
Дальнейшие ограничения на директивы, используемые встроенным ассемблером, указаны в разделе Поддержка директив.
Тип операнда
Поддерживается несколько типов операндов:
in(<reg>) <expr><reg>может ссылаться на класс регистров или явный регистр. Выделенное имя регистра подставляется в строку-шаблон asm.- Выделенный регистр будет содержать значение
<expr>в начале ассемблерного кода. - Выделенный регистр должен содержать то же значение в конце ассемблерного кода (за исключением случая, когда
lateoutвыделен в тот же регистр).
#![allow(unused)] fn main() { #[cfg(target_arch = "x86_64")] { // ``in` может быть использован для передачи значений во встроенный ассемблер... unsafe { core::arch::asm!("/* {} */", in(reg) 5); } } }
out(<reg>) <expr><reg>может ссылаться на класс регистров или явный регистр. Выделенное имя регистра подставляется в строку-шаблон asm.- Выделенный регистр будет содержать неопределенное значение в начале ассемблерного кода.
<expr>должно быть (возможно, неинициализированным) lvalue выражением, в которое содержимое выделенного регистра записывается в конце ассемблерного кода.- Подчеркивание (
_) может быть указано вместо выражения, что приведет к отбрасыванию содержимого регистра в конце ассемблерного кода (эффективно действуя как clobber).
#![allow(unused)] fn main() { #[cfg(target_arch = "x86_64")] { let x: i64; // и `out` может быть использован для передачи значений обратно в Rust. unsafe { core::arch::asm!("/* {} */", out(reg) x); } } }
lateout(<reg>) <expr>- Идентично
out, за исключением того, что распределитель регистров может повторно использовать регистр, выделенный дляin. - Вы должны писать в регистр только после того, как все входы прочитаны, иначе вы можете испортить вход.
- Идентично
#![allow(unused)] fn main() { #[cfg(target_arch = "x86_64")] { let x: i64; // `lateout` такой же, как `out` // но компилятор знает, что нам не важно значение любых входов к тому времени, // когда мы перезаписываем его. unsafe { core::arch::asm!("mov {}, 5", lateout(reg) x); } assert_eq!(x, 5) } }
inout(<reg>) <expr><reg>может ссылаться на класс регистров или явный регистр. Выделенное имя регистра подставляется в строку-шаблон asm.- Выделенный регистр будет содержать значение
<expr>в начале ассемблерного кода. <expr>должно быть изменяемым инициализированным lvalue выражением, в которое содержимое выделенного регистра записывается в конце ассемблерного кода.
#![allow(unused)] fn main() { #[cfg(target_arch = "x86_64")] { let mut x: i64 = 4; // `inout` может быть использован для модификации значений в регистре unsafe { core::arch::asm!("inc {}", inout(reg) x); } assert_eq!(x, 5); } }
inout(<reg>) <in expr> => <out expr>- То же, что и
inout, за исключением того, что начальное значение регистра берется из значения<in expr>. <out expr>должно быть (возможно, неинициализированным) lvalue выражением, в которое содержимое выделенного регистра записывается в конце ассемблерного кода.- Подчеркивание (
_) может быть указано вместо выражения для<out expr>, что приведет к отбрасыванию содержимого регистра в конце ассемблерного кода (эффективно действуя как clobber). <in expr>и<out expr>могут иметь разные типы.
- То же, что и
#![allow(unused)] fn main() { #[cfg(target_arch = "x86_64")] { let x: i64; // `inout` также может перемещать значения в разные места unsafe { core::arch::asm!("inc {}", inout(reg) 4u64=>x); } assert_eq!(x, 5); } }
inlateout(<reg>) <expr>/inlateout(<reg>) <in expr> => <out expr>- Идентично
inout, за исключением того, что распределитель регистров может повторно использовать регистр, выделенный дляin(это может произойти, если компилятор знает, чтоinимеет то же начальное значение, что иinlateout). - Вы должны писать в регистр только после того, как все входы прочитаны, иначе вы можете испортить вход.
- Идентично
#![allow(unused)] fn main() { #[cfg(target_arch = "x86_64")] { let mut x: i64 = 4; // `inlateout` - это `inout`, использующий `lateout` unsafe { core::arch::asm!("inc {}", inlateout(reg) x); } assert_eq!(x, 5); } }
sym <path><path>должен ссылаться наfnилиstatic.- Мангированное имя символа, ссылающееся на элемент, подставляется в строку-шаблон asm.
- Подставленная строка не включает никакие модификаторы (например, GOT, PLT, перемещения и т.д.).
<path>может указывать на#[thread_local]static, в этом случае ассемблерный код может комбинировать символ с перемещениями (например,@plt,@TPOFF) для чтения данных из thread-local storage.
#![allow(unused)] fn main() { #[cfg(target_arch = "x86_64")] { extern "C" fn foo() { println!("Hello from inline assembly") } // `sym` может быть использован для ссылки на функцию (даже если у нее нет // внешнего имени, которое мы можем написать напрямую) unsafe { core::arch::asm!("call {}", sym foo, clobber_abi("C")); } } }
const <expr><expr>должно быть целочисленным константным выражением. Это выражение следует тем же правилам, что и встроенные блокиconst.- Тип выражения может быть любым целочисленным типом, но по умолчанию используется
i32, как и для целочисленных литералов. - Значение выражения форматируется как строка и подставляется непосредственно в строку-шаблон asm.
#![allow(unused)] fn main() { #[cfg(target_arch = "x86_64")] { // перемешивание [0, 1, 2, 3] => [3, 2, 0, 1] const SHUFFLE: u8 = 0b01_00_10_11; let x: core::arch::x86_64::__m128 = unsafe { core::mem::transmute([0u32, 1u32, 2u32, 3u32]) }; let y: core::arch::x86_64::__m128; // Передаем постоянное значение в инструкцию, которая ожидает непосредственное значение, как `pshufd` unsafe { core::arch::asm!("pshufd {xmm}, {xmm}, {shuffle}", xmm = inlateout(xmm_reg) x=>y, shuffle = const SHUFFLE ); } let y: [u32; 4] = unsafe { core::mem::transmute(y) }; assert_eq!(y, [3, 2, 0, 1]); } }
label <block>- Адрес блока подставляется в строку-шаблон asm. Ассемблерный код может перейти по подставленному адресу.
- Для целей, которые различают прямые и косвенные переходы (например, x86-64 с включенным
cf-protection), ассемблерный код не должен переходить по подставленному адресу косвенно. - После выполнения блока выражение
asm!возвращается. - Тип блока должен быть unit или
!(never). - Блок начинает новый контекст безопасности; небезопасные операции внутри блока
labelдолжны быть обернуты во внутренний блокunsafe, даже если все выражениеasm!уже обернуто вunsafe.
#![allow(unused)] fn main() { #[cfg(target_arch = "x86_64")] unsafe { core::arch::asm!("jmp {}", label { println!("Hello from inline assembly label"); }); } }
Выражения операндов вычисляются слева направо, так же как аргументы вызова функции.
После выполнения asm! выходы записываются в порядке слева направо.
Это важно, если два выхода указывают на одно и то же место: это место будет содержать значение самого правого выхода.
#![allow(unused)] fn main() { #[cfg(target_arch = "x86_64")] { let mut y: i64; // y получает свое значение от второго выхода, а не от первого unsafe { core::arch::asm!("mov {}, 0", "mov {}, 1", out(reg) y, out(reg) y); } assert_eq!(y, 1); } }
Поскольку naked_asm! определяет все тело функции и компилятор не может излучать дополнительный код для обработки операндов, он может использовать только операнды sym и const.
Поскольку global_asm! существует вне функции, он может использовать только операнды sym и const.
fn main() {} // операнды регистров не разрешены, так как мы не в функции #[cfg(target_arch = "x86_64")] core::arch::global_asm!("", in(reg) 5); // ОШИБКА: операнд `in` не может быть использован с `global_asm!` #[cfg(not(target_arch = "x86_64"))] core::compile_error!("Тест не поддерживается на этой архитектуре");
fn main() {} fn foo() {} #[cfg(target_arch = "x86_64")] // `const` и `sym` оба разрешены, однако core::arch::global_asm!("/* {} {} */", const 0, sym foo);
Операнды регистров
Входные и выходные операнды могут быть указаны либо как явный регистр, либо как класс регистров, из которого распределитель регистров может выбрать регистр.
Явные регистры указываются как строковые литералы (например, "eax"), в то время как классы регистров указываются как идентификаторы (например, reg).
#![allow(unused)] fn main() { #[cfg(target_arch = "x86_64")] { let mut y: i64; // Мы можем назвать как `reg`, так и явный регистр, такой как `eax`, чтобы получить // целочисленный регистр unsafe { core::arch::asm!("mov eax, {:e}", in(reg) 5, lateout("eax") y); } assert_eq!(y, 5); } }
Обратите внимание, что явные регистры рассматривают псевдонимы регистров (например, r14 против lr на ARM) и меньшие представления регистра (например, eax против rax) как эквивалентные базовому регистру.
Ошибка времени компиляции возникает, если один и тот же явный регистр используется для двух входных операндов или двух выходных операндов.
#![allow(unused)] fn main() { #[cfg(target_arch = "x86_64")] { // Мы не можем назвать eax дважды unsafe { core::arch::asm!("", in("eax") 5, in("eax") 4); } // ОШИБКА: регистр `eax` конфликтует с регистром `eax` } #[cfg(not(target_arch = "x86_64"))] core::compile_error!("Тест не поддерживается на этой архитектуре"); }
#![allow(unused)] fn main() { #[cfg(target_arch = "x86_64")] { // ... даже используя разные псевдонимы unsafe { core::arch::asm!("", in("ax") 5, in("rax") 4); } // ОШИБКА: регистр `rax` конфликтует с регистром `ax` } #[cfg(not(target_arch = "x86_64"))] core::compile_error!("Тест не поддерживается на этой архитектуре"); }
Кроме того, ошибка времени компиляции также возникает при использовании перекрывающихся регистров (например, ARM VFP) во входных операндах или в выходных операндах.
#![allow(unused)] fn main() { #[cfg(target_arch = "x86_64")] { // al перекрывается с ax, поэтому мы не можем назвать их обоих. unsafe { core::arch::asm!("", in("ax") 5, in("al") 4i8); } // ОШИБКА: регистр `al` конфликтует с регистром `ax` } #[cfg(not(target_arch = "x86_64"))] core::compile_error!("Тест не поддерживается на этой архитектуре"); }
Только следующие типы разрешены в качестве операндов для встроенного ассемблера:
- Целые числа (знаковые и беззнаковые)
- Числа с плавающей точкой
- Указатели (только тонкие)
- Указатели на функции
- SIMD векторы (структуры, определенные с
#[repr(simd)]и которые реализуютCopy). Это включает специфичные для архитектуры векторные типы, определенные вstd::arch, такие как__m128(x86) илиint8x16_t(ARM).
#![allow(unused)] fn main() { #[cfg(target_arch = "x86_64")] { extern "C" fn foo() {} // Целые числа разрешены... let y: i64 = 5; unsafe { core::arch::asm!("/* {} */", in(reg) y); } // и указатели... let py = &raw const y; unsafe { core::arch::asm!("/* {} */", in(reg) py); } // числа с плавающей точкой также... let f = 1.0f32; unsafe { core::arch::asm!("/* {} */", in(xmm_reg) f); } // даже указатели на функции и simd векторы. let func: extern "C" fn() = foo; unsafe { core::arch::asm!("/* {} */", in(reg) func); } let z = unsafe { core::arch::x86_64::_mm_set_epi64x(1, 0) }; unsafe { core::arch::asm!("/* {} */", in(xmm_reg) z); } } }
#![allow(unused)] fn main() { #[cfg(target_arch = "x86_64")] { struct Foo; let x: Foo = Foo; // Сложные типы, такие как структуры, не разрешены unsafe { core::arch::asm!("/* {} */", in(reg) x); } // ОШИБКА: нельзя использовать значение типа `Foo` для встроенного ассемблера } #[cfg(not(target_arch = "x86_64"))] core::compile_error!("Тест не поддерживается на этой архитектуре"); }
Вот список currently поддерживаемых классов регистров:
| Архитектура | Класс регистра | Регистры | Код ограничения LLVM |
|---|---|---|---|
| x86 | reg | ax, bx, cx, dx, si, di, bp, r[8-15] (только x86-64) | r |
| x86 | reg_abcd | ax, bx, cx, dx | Q |
| x86-32 | reg_byte | al, bl, cl, dl, ah, bh, ch, dh | q |
| x86-64 | reg_byte* | al, bl, cl, dl, sil, dil, bpl, r[8-15]b | q |
| x86 | xmm_reg | xmm[0-7] (x86) xmm[0-15] (x86-64) | x |
| x86 | ymm_reg | ymm[0-7] (x86) ymm[0-15] (x86-64) | x |
| x86 | zmm_reg | zmm[0-7] (x86) zmm[0-31] (x86-64) | v |
| x86 | kreg | k[1-7] | Yk |
| x86 | kreg0 | k0 | Только clobbers |
| x86 | x87_reg | st([0-7]) | Только clobbers |
| x86 | mmx_reg | mm[0-7] | Только clobbers |
| x86-64 | tmm_reg | tmm[0-7] | Только clobbers |
| AArch64 | reg | x[0-30] | r |
| AArch64 | vreg | v[0-31] | w |
| AArch64 | vreg_low16 | v[0-15] | x |
| AArch64 | preg | p[0-15], ffr | Только clobbers |
| Arm64EC | reg | x[0-12], x[15-22], x[25-27], x30 | r |
| Arm64EC | vreg | v[0-15] | w |
| Arm64EC | vreg_low16 | v[0-15] | x |
| ARM (ARM/Thumb2) | reg | r[0-12], r14 | r |
| ARM (Thumb1) | reg | r[0-7] | r |
| ARM | sreg | s[0-31] | t |
| ARM | sreg_low16 | s[0-15] | x |
| ARM | dreg | d[0-31] | w |
| ARM | dreg_low16 | d[0-15] | t |
| ARM | dreg_low8 | d[0-8] | x |
| ARM | qreg | q[0-15] | w |
| ARM | qreg_low8 | q[0-7] | t |
| ARM | qreg_low4 | q[0-3] | x |
| RISC-V | reg | x1, x[5-7], x[9-15], x[16-31] (не-RV32E) | r |
| RISC-V | freg | f[0-31] | f |
| RISC-V | vreg | v[0-31] | Только clobbers |
| LoongArch | reg | $r1, $r[4-20], $r[23,30] | r |
| LoongArch | freg | $f[0-31] | f |
| s390x | reg | r[0-10], r[12-14] | r |
| s390x | reg_addr | r[1-10], r[12-14] | a |
| s390x | freg | f[0-15] | f |
| s390x | vreg | v[0-31] | Только clobbers |
| s390x | areg | a[2-15] | Только clobbers |
Note
- На x86 мы обрабатываем
reg_byteиначе, чемreg, потому что компилятор может выделитьalиahотдельно, тогда какregрезервирует весь регистр.- На x86-64 старшие байтовые регистры (например,
ah) недоступны в классе регистровreg_byte.- Некоторые классы регистров помечены как “Только clobbers”, что означает, что регистры в этих классах не могут быть использованы для входов или выходов, только clobbers вида
out(<explicit register>) _илиlateout(<explicit register>) _.
Каждый класс регистров имеет ограничения на то, с какими типами значений они могут быть использованы.
Это необходимо потому, что способ загрузки значения в регистр зависит от его типа.
Например, в big-endian системах загрузка i32x4 и i8x16 в SIMD регистр может привести к разному содержимому регистра, даже если байтовое представление в памяти обоих значений идентично.
Доступность поддерживаемых типов для конкретного класса регистров может зависеть от того, какие целевые функции в настоящее время включены.
| Архитектура | Класс регистра | Целевая функция | Разрешенные типы |
|---|---|---|---|
| x86-32 | reg | None | i16, i32, f32 |
| x86-64 | reg | None | i16, i32, f32, i64, f64 |
| x86 | reg_byte | None | i8 |
| x86 | xmm_reg | sse | i32, f32, i64, f64, i8x16, i16x8, i32x4, i64x2, f32x4, f64x2 |
| x86 | ymm_reg | avx | i32, f32, i64, f64, i8x16, i16x8, i32x4, i64x2, f32x4, f64x2 i8x32, i16x16, i32x8, i64x4, f32x8, f64x4 |
| x86 | zmm_reg | avx512f | i32, f32, i64, f64, i8x16, i16x8, i32x4, i64x2, f32x4, f64x2 i8x32, i16x16, i32x8, i64x4, f32x8, f64x4 i8x64, i16x32, i32x16, i64x8, f32x16, f64x8 |
| x86 | kreg | avx512f | i8, i16 |
| x86 | kreg | avx512bw | i32, i64 |
| x86 | mmx_reg | N/A | Только clobbers |
| x86 | x87_reg | N/A | Только clobbers |
| x86 | tmm_reg | N/A | Только clobbers |
| AArch64 | reg | None | i8, i16, i32, f32, i64, f64 |
| AArch64 | vreg | neon | i8, i16, i32, f32, i64, f64, i8x8, i16x4, i32x2, i64x1, f32x2, f64x1, i8x16, i16x8, i32x4, i64x2, f32x4, f64x2 |
| AArch64 | preg | N/A | Только clobbers |
| Arm64EC | reg | None | i8, i16, i32, f32, i64, f64 |
| Arm64EC | vreg | neon | i8, i16, i32, f32, i64, f64, i8x8, i16x4, i32x2, i64x1, f32x2, f64x1, i8x16, i16x8, i32x4, i64x2, f32x4, f64x2 |
| ARM | reg | None | i8, i16, i32, f32 |
| ARM | sreg | vfp2 | i32, f32 |
| ARM | dreg | vfp2 | i64, f64, i8x8, i16x4, i32x2, i64x1, f32x2 |
| ARM | qreg | neon | i8x16, i16x8, i32x4, i64x2, f32x4 |
| RISC-V32 | reg | None | i8, i16, i32, f32 |
| RISC-V64 | reg | None | i8, i16, i32, f32, i64, f64 |
| RISC-V | freg | f | f32 |
| RISC-V | freg | d | f64 |
| RISC-V | vreg | N/A | Только clobbers |
| LoongArch32 | reg | None | i8, i16, i32, f32 |
| LoongArch64 | reg | None | i8, i16, i32, i64, f32, f64 |
| LoongArch | freg | f | f32 |
| LoongArch | freg | d | f64 |
| s390x | reg, reg_addr | None | i8, i16, i32, i64 |
| s390x | freg | None | f32, f64 |
| s390x | vreg | N/A | Только clobbers |
| s390x | areg | N/A | Только clobbers |
Note
Для целей приведенной выше таблицы указатели, указатели на функции и
isize/usizeрассматриваются как эквивалентный целочисленный тип (i16/i32/i64в зависимости от цели).
#![allow(unused)] fn main() { #[cfg(target_arch = "x86_64")] { let x = 5i32; let y = -1i8; let z = unsafe { core::arch::x86_64::_mm_set_epi64x(1, 0) }; // reg действителен для `i32`, `reg_byte` действителен для `i8`, и xmm_reg действителен для `__m128i` // Мы не можем использовать `tmm0` как вход или выход, но мы можем clobber его. unsafe { core::arch::asm!("/* {} {} {} */", in(reg) x, in(reg_byte) y, in(xmm_reg) z, out("tmm0") _); } } }
#![allow(unused)] fn main() { #[cfg(target_arch = "x86_64")] { let z = unsafe { core::arch::x86_64::_mm_set_epi64x(1, 0) }; // Мы не можем передать `__m128i` в `reg` вход unsafe { core::arch::asm!("/* {} */", in(reg) z); } // ОШИБКА: тип `__m128i` не может быть использован с этим классом регистров } #[cfg(not(target_arch = "x86_64"))] core::compile_error!("Тест не поддерживается на этой архитектуре"); }
Если значение имеет меньший размер, чем регистр, в который оно выделено, то старшие биты этого регистра будут иметь неопределенное значение для входов и будут игнорироваться для выходов.
Единственное исключение - класс регистров freg на RISC-V, где значения f32 являются NaN-boxed в f64, как того требует архитектура RISC-V.
#![allow(unused)] fn main() { #[cfg(target_arch = "x86_64")] { let mut x: i64; // Перемещаем 32-битное значение в 64-битное значение, упс. #[allow(asm_sub_register)] // rustc предупреждает об этом поведении unsafe { core::arch::asm!("mov {}, {}", lateout(reg) x, in(reg) 4i32); } // старшие 32 бита неопределенны assert_eq!(x, 4); // Это утверждение не гарантированно выполнится assert_eq!(x & 0xFFFFFFFF, 4); // Однако это выполнится } }
Когда для операнда inout указаны отдельные входное и выходное выражения, оба выражения должны иметь одинаковый тип.
Единственное исключение - если оба операнда являются указателями или целыми числами, в этом случае от них требуется только иметь одинаковый размер.
Это ограничение существует потому, что распределители регистров в LLVM и GCC иногда не могут обрабатывать связанные операнды с разными типами.
#![allow(unused)] fn main() { #[cfg(target_arch = "x86_64")] { // Указатели и целые числа могут смешиваться (пока они одного размера) let x: isize = 0; let y: *mut (); // Преобразуем `isize` в `*mut ()`, используя магию встроенного ассемблера unsafe { core::arch::asm!("/*{}*/", inout(reg) x=>y); } assert!(y.is_null()); // Очень окольный способ создать нулевой указатель } }
#![allow(unused)] fn main() { #[cfg(target_arch = "x86_64")] { let x: i32 = 0; let y: f32; // Но мы не можем переинтерпретировать `i32` в `f32` таким образом unsafe { core::arch::asm!("/* {} */", inout(reg) x=>y); } // ОШИБКА: несовместимые типы для аргумента asm inout } #[cfg(not(target_arch = "x86_64"))] core::compile_error!("Тест не поддерживается на этой архитектуре"); }
Имена регистров
Некоторые регистры имеют несколько имен. Все они рассматриваются компилятором как идентичные базовому имени регистра. Вот список всех поддерживаемых псевдонимов регистров:
| Архитектура | Базовый регистр | Псевдонимы |
|---|---|---|
| x86 | ax | eax, rax |
| x86 | bx | ebx, rbx |
| x86 | cx | ecx, rcx |
| x86 | dx | edx, rdx |
| x86 | si | esi, rsi |
| x86 | di | edi, rdi |
| x86 | bp | bpl, ebp, rbp |
| x86 | sp | spl, esp, rsp |
| x86 | ip | eip, rip |
| x86 | st(0) | st |
| x86 | r[8-15] | r[8-15]b, r[8-15]w, r[8-15]d |
| x86 | xmm[0-31] | ymm[0-31], zmm[0-31] |
| AArch64 | x[0-30] | w[0-30] |
| AArch64 | x29 | fp |
| AArch64 | x30 | lr |
| AArch64 | sp | wsp |
| AArch64 | xzr | wzr |
| AArch64 | v[0-31] | b[0-31], h[0-31], s[0-31], d[0-31], q[0-31] |
| Arm64EC | x[0-30] | w[0-30] |
| Arm64EC | x29 | fp |
| Arm64EC | x30 | lr |
| Arm64EC | sp | wsp |
| Arm64EC | xzr | wzr |
| Arm64EC | v[0-15] | b[0-15], h[0-15], s[0-15], d[0-15], q[0-15] |
| ARM | r[0-3] | a[1-4] |
| ARM | r[4-9] | v[1-6] |
| ARM | r9 | rfp |
| ARM | r10 | sl |
| ARM | r11 | fp |
| ARM | r12 | ip |
| ARM | r13 | sp |
| ARM | r14 | lr |
| ARM | r15 | pc |
| RISC-V | x0 | zero |
| RISC-V | x1 | ra |
| RISC-V | x2 | sp |
| RISC-V | x3 | gp |
| RISC-V | x4 | tp |
| RISC-V | x[5-7] | t[0-2] |
| RISC-V | x8 | fp, s0 |
| RISC-V | x9 | s1 |
| RISC-V | x[10-17] | a[0-7] |
| RISC-V | x[18-27] | s[2-11] |
| RISC-V | x[28-31] | t[3-6] |
| RISC-V | f[0-7] | ft[0-7] |
| RISC-V | f[8-9] | fs[0-1] |
| RISC-V | f[10-17] | fa[0-7] |
| RISC-V | f[18-27] | fs[2-11] |
| RISC-V | f[28-31] | ft[8-11] |
| LoongArch | $r0 | $zero |
| LoongArch | $r1 | $ra |
| LoongArch | $r2 | $tp |
| LoongArch | $r3 | $sp |
| LoongArch | $r[4-11] | $a[0-7] |
| LoongArch | $r[12-20] | $t[0-8] |
| LoongArch | $r21 | |
| LoongArch | $r22 | $fp, $s9 |
| LoongArch | $r[23-31] | $s[0-8] |
| LoongArch | $f[0-7] | $fa[0-7] |
| LoongArch | $f[8-23] | $ft[0-15] |
| LoongArch | $f[24-31] | $fs[0-7] |
#![allow(unused)] fn main() { #[cfg(target_arch = "x86_64")] { let z = 0i64; // rax - это псевдоним для eax и ax unsafe { core::arch::asm!("", in("rax") z); } } }
Некоторые регистры не могут быть использованы для входных или выходных операндов:
| Архитектура | Неподдерживаемый регистр | Причина |
|---|---|---|
| Все | sp, r15 (s390x) | Указатель стека должен быть восстановлен до своего исходного значения в конце ассемблерного кода или перед переходом к блоку label. |
| Все | bp (x86), x29 (AArch64 и Arm64EC), x8 (RISC-V), $fp (LoongArch), r11 (s390x) | Указатель фрейма не может быть использован как вход или выход. |
| ARM | r7 или r11 | На ARM указатель фрейма может быть либо r7, либо r11 в зависимости от цели. Указатель фрейма не может быть использован как вход или выход. |
| Все | si (x86-32), bx (x86-64), r6 (ARM), x19 (AArch64 и Arm64EC), x9 (RISC-V), $s8 (LoongArch) | Это используется внутри LLVM как “базовый указатель” для функций со сложными фреймами стека. |
| x86 | ip | Это счетчик команд, не настоящий регистр. |
| AArch64 | xzr | Это регистр константного нуля, который не может быть изменен. |
| AArch64 | x18 | Это зарезервированный ОС регистр на некоторых целях AArch64. |
| Arm64EC | xzr | Это регистр константного нуля, который не может быть изменен. |
| Arm64EC | x18 | Это зарезервированный ОС регистр. |
| Arm64EC | x13, x14, x23, x24, x28, v[16-31], p[0-15], ffr | Это регистры AArch64, которые не поддерживаются для Arm64EC. |
| ARM | pc | Это счетчик команд, не настоящий регистр. |
| ARM | r9 | Это зарезервированный ОС регистр на некоторых целях ARM. |
| RISC-V | x0 | Это регистр константного нуля, который не может быть изменен. |
| RISC-V | gp, tp | Эти регистры зарезервированы и не могут быть использованы как входы или выходы. |
| LoongArch | $r0 или $zero | Это регистр константного нуля, который не может быть изменен. |
| LoongArch | $r2 или $tp | Это зарезервировано для TLS. |
| LoongArch | $r21 | Это зарезервировано ABI. |
| s390x | c[0-15] | Зарезервировано ядром. |
| s390x | a[0-1] | Зарезервировано для системного использования. |
#![allow(unused)] fn main() { #[cfg(target_arch = "x86_64")] { // bp зарезервирован unsafe { core::arch::asm!("", in("bp") 5i32); } // ОШИБКА: неверный регистр `bp`: указатель фрейма не может быть использован как операнд для встроенного asm } #[cfg(not(target_arch = "x86_64"))] core::compile_error!("Тест не поддерживается на этой архитектуре"); }
Регистры указателя фрейма и базового указателя зарезервированы для внутреннего использования LLVM. Хотя операторы asm! не могут явно указывать использование зарезервированных регистров, в некоторых случаях LLVM выделит один из этих зарезервированных регистров для операндов reg. Ассемблерный код, использующий зарезервированные регистры, должен быть осторожен, поскольку операнды reg могут использовать те же регистры.
Модификаторы шаблона
Заполнители могут быть дополнены модификаторами, которые указываются после : в фигурных скобках.
Эти модификаторы не влияют на распределение регистров, но изменяют способ форматирования операндов при вставке в строку-шаблон.
Только один модификатор разрешен для каждого заполнителя шаблона.
#![allow(unused)] fn main() { #[cfg(target_arch = "x86_64")] { // Мы не можем указать и `r`, и `e` одновременно. unsafe { core::arch::asm!("/* {:er}", in(reg) 5i32); } // ОШИБКА: модификатор шаблона asm должен быть одним символом } #[cfg(not(target_arch = "x86_64"))] core::compile_error!("Тест не поддерживается на этой архитектуре"); }
Поддерживаемые модификаторы являются подмножеством модификаторов аргументов шаблона asm LLVM (и GCC), но не используют те же буквенные коды.
| Архитектура | Класс регистра | Модификатор | Пример вывода | Модификатор LLVM |
|---|---|---|---|---|
| x86-32 | reg | None | eax | k |
| x86-64 | reg | None | rax | q |
| x86-32 | reg_abcd | l | al | b |
| x86-64 | reg | l | al | b |
| x86 | reg_abcd | h | ah | h |
| x86 | reg | x | ax | w |
| x86 | reg | e | eax | k |
| x86-64 | reg | r | rax | q |
| x86 | reg_byte | None | al / ah | None |
| x86 | xmm_reg | None | xmm0 | x |
| x86 | ymm_reg | None | ymm0 | t |
| x86 | zmm_reg | None | zmm0 | g |
| x86 | *mm_reg | x | xmm0 | x |
| x86 | *mm_reg | y | ymm0 | t |
| x86 | *mm_reg | z | zmm0 | g |
| x86 | kreg | None | k1 | None |
| AArch64/Arm64EC | reg | None | x0 | x |
| AArch64/Arm64EC | reg | w | w0 | w |
| AArch64/Arm64EC | reg | x | x0 | x |
| AArch64/Arm64EC | vreg | None | v0 | None |
| AArch64/Arm64EC | vreg | v | v0 | None |
| AArch64/Arm64EC | vreg | b | b0 | b |
| AArch64/Arm64EC | vreg | h | h0 | h |
| AArch64/Arm64EC | vreg | s | s0 | s |
| AArch64/Arm64EC | vreg | d | d0 | d |
| AArch64/Arm64EC | vreg | q | q0 | q |
| ARM | reg | None | r0 | None |
| ARM | sreg | None | s0 | None |
| ARM | dreg | None | d0 | P |
| ARM | qreg | None | q0 | q |
| ARM | qreg | e / f | d0 / d1 | e / f |
| RISC-V | reg | None | x1 | None |
| RISC-V | freg | None | f0 | None |
| LoongArch | reg | None | $r1 | None |
| LoongArch | freg | None | $f0 | None |
| s390x | reg | None | %r0 | None |
| s390x | reg_addr | None | %r1 | None |
| s390x | freg | None | %f0 | None |
Note
- на ARM
e/f: это выводит имя регистра младшего или старшего двойного слова NEON квада (128-битного) регистра.- на x86: наше поведение для
regбез модификаторов отличается от того, что делает GCC. GCC будет выводить модификатор на основе типа значения операнда, тогда как мы по умолчанию используем полный размер регистра.- на x86
xmm_reg: модификаторы LLVMx,tиgеще не реализованы в LLVM (они поддерживаются только GCC), но это должно быть простым изменением.
#![allow(unused)] fn main() { #[cfg(target_arch = "x86_64")] { let mut x = 0x10u16; // u16::swap_bytes используя `xchg` // младшая половина `{x}` обозначается как `{x:l}`, а старшая половина как `{x:h}` unsafe { core::arch::asm!("xchg {x:l}, {x:h}", x = inout(reg_abcd) x); } assert_eq!(x, 0x1000u16); } }
Как указано в предыдущем разделе, передача входного значения меньшего, чем ширина регистра, приведет к тому, что старшие биты регистра будут содержать неопределенные значения.
Это не проблема, если встроенный asm обращается только к младшим битам регистра, что можно сделать, используя модификатор шаблона для использования имени подрегистра в ассемблерном коде (например, ax вместо rax).
Поскольку это распространенная ошибка, компилятор предложит использовать модификатор шаблона, где это уместно, учитывая тип входа.
Если все ссылки на операнд уже имеют модификаторы, то предупреждение для этого операнда подавляется.
Clobbers ABI
Ключевое слово clobber_abi может быть использовано для применения набора clobbers по умолчанию к ассемблерному коду.
Это автоматически вставит необходимые ограничения clobbers по мере необходимости для вызова функции с определенным соглашением о вызовах: если соглашение о вызовах не полностью сохраняет значение регистра при вызове, то lateout("...") _ неявно добавляется к списку операндов (где ... заменяется на имя регистра).
#![allow(unused)] fn main() { #[cfg(target_arch = "x86_64")] { extern "C" fn foo() -> i32 { 0 } let z: i32; // Чтобы вызвать функцию, мы должны проинформировать компилятор, что мы портим // регистры, сохраняемые вызываемой стороной (callee saved registers) unsafe { core::arch::asm!("call {}", sym foo, out("rax") z, clobber_abi("C")); } assert_eq!(z, 0); } }
clobber_abi может быть указано любое количество раз. Оно вставит clobber для всех уникальных регистров в объединении всех указанных соглашений о вызовах.
#![allow(unused)] fn main() { #[cfg(target_arch = "x86_64")] { extern "sysv64" fn foo() -> i32 { 0 } extern "win64" fn bar(x: i32) -> i32 { x + 1} let z: i32; // Мы можем даже вызывать несколько функций с разными соглашениями и // разными сохраняемыми регистрами unsafe { core::arch::asm!( "call {}", "mov ecx, eax", "call {}", sym foo, sym bar, out("rax") z, clobber_abi("C") ); } assert_eq!(z, 1); } }
Общие выходы классов регистров запрещены компилятором при использовании clobber_abi: все выходы должны указывать явный регистр.
#![allow(unused)] fn main() { #[cfg(target_arch = "x86_64")] { extern "C" fn foo(x: i32) -> i32 { 0 } let z: i32; // должны использоваться явные регистры, чтобы не перекрыть случайно. unsafe { core::arch::asm!( "mov eax, {:e}", "call {}", out(reg) z, sym foo, clobber_abi("C") ); // ОШИБКА: asm с `clobber_abi` должен указывать явные регистры для выходов } assert_eq!(z, 0); } #[cfg(not(target_arch = "x86_64"))] core::compile_error!("Тест не поддерживается на этой архитектуре"); }
Явные выходы регистров имеют приоритет над неявными clobbers, вставленными clobber_abi: clobber будет вставлен для регистра только если этот регистр не используется как выход.
Следующие ABI могут быть использованы с clobber_abi:
| Архитектура | Имя ABI | Испорченные регистры |
|---|---|---|
| x86-32 | "C", "system", "efiapi", "cdecl", "stdcall", "fastcall" | ax, cx, dx, xmm[0-7], mm[0-7], k[0-7], st([0-7]) |
| x86-64 | "C", "system" (на Windows), "efiapi", "win64" | ax, cx, dx, r[8-11], xmm[0-31], mm[0-7], k[0-7], st([0-7]), tmm[0-7] |
| x86-64 | "C", "system" (на не-Windows), "sysv64" | ax, cx, dx, si, di, r[8-11], xmm[0-31], mm[0-7], k[0-7], st([0-7]), tmm[0-7] |
| AArch64 | "C", "system", "efiapi" | x[0-17], x18*, x30, v[0-31], p[0-15], ffr |
| Arm64EC | "C", "system" | x[0-12], x[15-17], x30, v[0-15] |
| ARM | "C", "system", "efiapi", "aapcs" | r[0-3], r12, r14, s[0-15], d[0-7], d[16-31] |
| RISC-V | "C", "system", "efiapi" | x1, x[5-7], x[10-17]*, x[28-31]*, f[0-7], f[10-17], f[28-31], v[0-31] |
| LoongArch | "C", "system" | $r1, $r[4-20], $f[0-23] |
| s390x | "C", "system" | r[0-5], r14, f[0-7], v[0-31], a[2-15] |
Note
- На AArch64
x18включается в список clobbers только если он не считается зарезервированным регистром на цели.- На RISC-V
x[16-17]иx[28-31]включаются в список clobbers только если они не считаются зарезервированными регистрами на цели.
Список испорченных регистров для каждого ABI обновляется в rustc по мере того, как архитектуры получают новые регистры: это гарантирует, что clobbers asm! продолжат быть корректными, когда LLVM начнет использовать эти новые регистры в своем сгенерированном коде.
Опции
Флаги используются для дальнейшего влияния на поведение встроенного ассемблерного кода. В настоящее время определены следующие опции:
pure: Ассемблерный код не имеет побочных эффектов, должен в конечном счете вернуть управление, и его выходы зависят только от его прямых входов (т.е. самих значений, а не того, на что они указывают) или значений, прочитанных из памяти (если опцияnomemтакже не установлена). Это позволяет компилятору выполнять ассемблерный код меньше раз, чем указано в программе (например, вынося его из цикла), или даже полностью устранить его, если выходы не используются. Опцияpureдолжна быть комбинирована либо с опциейnomem, либо сreadonly, иначе выдается ошибка времени компиляции.
#![allow(unused)] fn main() { #[cfg(target_arch = "x86_64")] { let x: i32 = 0; let z: i32; // pure может быть использована для оптимизации, предполагая, что ассемблер не имеет побочных эффектов unsafe { core::arch::asm!("inc {}", inout(reg) x => z, options(pure, nomem)); } assert_eq!(z, 1); } }
#![allow(unused)] fn main() { #[cfg(target_arch = "x86_64")] { let x: i32 = 0; let z: i32; // Либо nomem, либо readonly должны быть удовлетворены, чтобы указать, разрешено ли // чтение памяти unsafe { core::arch::asm!("inc {}", inout(reg) x => z, options(pure)); } // ОШИБКА: опция `pure` должна быть комбинирована либо с `nomem`, либо с `readonly` assert_eq!(z, 0); } #[cfg(not(target_arch = "x86_64"))] core::compile_error!("Тест не поддерживается на этой архитектуре"); }
nomem: Ассемблерный код не читает и не пишет в любую память, доступную вне ассемблерного кода. Это позволяет компилятору кэшировать значения измененных глобальных переменных в регистрах во время выполнения ассемблерного кода, поскольку он знает, что они не читаются и не записываются им. Компилятор также предполагает, что ассемблерный код не выполняет никакой синхронизации с другими потоками, например, через барьеры.
#![allow(unused)] fn main() { #[cfg(target_arch = "x86_64")] { let mut x = 0i32; let z: i32; // Доступ к внешней памяти из ассемблера, когда указан `nomem`, // запрещен unsafe { core::arch::asm!("mov {val:e}, dword ptr [{ptr}]", ptr = in(reg) &mut x, val = lateout(reg) z, options(nomem) ) } // Запись во внешнюю память из ассемблера, когда указан `nomem`, // также является неопределенным поведением unsafe { core::arch::asm!("mov dword ptr [{ptr}], {val:e}", ptr = in(reg) &mut x, val = in(reg) z, options(nomem) ) } } }
#![allow(unused)] fn main() { #[cfg(target_arch = "x86_64")] { let x: i32 = 0; let z: i32; // Если мы выделяем свою собственную память, такую как через `push`, однако, // мы все еще можем использовать ее unsafe { core::arch::asm!("push {x}", "add qword ptr [rsp], 1", "pop {x}", x = inout(reg) x => z, options(nomem) ); } assert_eq!(z, 1); } }
readonly: Ассемблерный код не пишет в любую память, доступную вне ассемблерного кода. Это позволяет компилятору кэшировать значения неизмененных глобальных переменных в регистрах во время выполнения ассемблерного кода, поскольку он знает, что они не записываются им. Компилятор также предполагает, что этот ассемблерный код не выполняет никакой синхронизации с другими потоками, например, через барьеры.
#![allow(unused)] fn main() { #[cfg(target_arch = "x86_64")] { let mut x = 0; // Мы не можем модифицировать внешнюю память, когда указан `readonly` unsafe { core::arch::asm!("mov dword ptr[{}], 1", in(reg) &mut x, options(readonly)) } } }
#![allow(unused)] fn main() { #[cfg(target_arch = "x86_64")] { let x: i64 = 0; let z: i64; // Мы все еще можем читать из нее, однако unsafe { core::arch::asm!("mov {x}, qword ptr [{x}]", x = inout(reg) &x => z, options(readonly) ); } assert_eq!(z, 0); } }
#![allow(unused)] fn main() { #[cfg(target_arch = "x86_64")] { let x: i64 = 0; let z: i64; // То же исключение применяется, как и с nomem. unsafe { core::arch::asm!("push {x}", "add qword ptr [rsp], 1", "pop {x}", x = inout(reg) x => z, options(readonly) ); } assert_eq!(z, 1); } }
preserves_flags: Ассемблерный код не модифицирует регистр флагов (определенный в правилах ниже). Это позволяет компилятору избежать перевычисления флагов условий после выполнения ассемблерного кода.
noreturn: Ассемблерный код не завершается нормально (не проходит дальше); поведение не определено, если это происходит. Он все еще может переходить к блокамlabel. Если любой блокlabelвозвращает unit, блокasm!вернет unit. Иначе он вернет!(never). Как и при вызове функции, которая не возвращает управление, локальные переменные в области видимости не уничтожаются перед выполнением ассемблерного кода.
fn main() -> ! { #[cfg(target_arch = "x86_64")] { // Мы можем использовать инструкцию для прерывания выполнения внутри блока noreturn unsafe { core::arch::asm!("ud2", options(noreturn)); } } #[cfg(not(target_arch = "x86_64"))] panic!("no return"); }
#![allow(unused)] fn main() { #[cfg(target_arch = "x86_64")] { // Вы ответственны за то, чтобы не провалиться за конец блока asm noreturn unsafe { core::arch::asm!("", options(noreturn)); } } }
#![allow(unused)] fn main() { #[cfg(target_arch = "x86_64")] let _: () = unsafe { // Вы все еще можете переходить к блоку `label` core::arch::asm!("jmp {}", label { println!(); }, options(noreturn)); }; }
nostack: Ассемблерный код не помещает данные в стек и не пишет в красную зону стека (если поддерживается целью). Если эта опция не используется, то указатель стека гарантированно будет соответственно выровнен (согласно ABI цели) для вызова функции.
#![allow(unused)] fn main() { #[cfg(target_arch = "x86_64")] { // `push` и `pop` являются UB при использовании с nostack unsafe { core::arch::asm!("push rax", "pop rax", options(nostack)); } } }
att_syntax: Эта опция действительна только на x86 и заставляет ассемблер использовать режим.att_syntax prefixассемблера GNU. Операнды регистров подставляются с ведущим%.
#![allow(unused)] fn main() { #[cfg(target_arch = "x86_64")] { let x: i32; let y = 1i32; // Нам нужно использовать AT&T Syntax здесь. Порядок операндов: src, dest unsafe { core::arch::asm!("mov {y:e}, {x:e}", x = lateout(reg) x, y = in(reg) y, options(att_syntax) ); } assert_eq!(x, y); } }
raw: Это заставляет строку-шаблон разбираться как сырая ассемблерная строка, без специальной обработки{и}. Это в первую очередь полезно при включении сырого ассемблерного кода из внешнего файла с помощьюinclude_str!.
Компилятор выполняет некоторые дополнительные проверки опций:
- Опции
nomemиreadonlyвзаимно исключают друг друга: ошибка времени компиляции возникает, если указаны обе.
#![allow(unused)] fn main() { #[cfg(target_arch = "x86_64")] { // nomem строго сильнее, чем readonly, они не могут быть указаны вместе unsafe { core::arch::asm!("", options(nomem, readonly)); } // ОШИБКА: опции `nomem` и `readonly` взаимно исключают друг друга } #[cfg(not(target_arch = "x86_64"))] core::compile_error!("Тест не поддерживается на этой архитектуре"); }
- Ошибка времени компиляции возникает, если указать
pureна блок asm без выходов или только с отброшенными выходами (_).
#![allow(unused)] fn main() { #[cfg(target_arch = "x86_64")] { // pure блоки нуждаются по крайней мере в одном выходе unsafe { core::arch::asm!("", options(pure)); } // ОШИБКА: asm с опцией `pure` должен иметь по крайней мере один выход } #[cfg(not(target_arch = "x86_64"))] core::compile_error!("Тест не поддерживается на этой архитектуре"); }
- Ошибка времени компиляции возникает, если указать
noreturnна блок asm с выходами и без меток.
#![allow(unused)] fn main() { #[cfg(target_arch = "x86_64")] { let z: i32; // noreturn не может иметь выходы unsafe { core::arch::asm!("mov {:e}, 1", out(reg) z, options(noreturn)); } // ОШИБКА: выходы asm не разрешены с опцией `noreturn` } #[cfg(not(target_arch = "x86_64"))] core::compile_error!("Тест не поддерживается на этой архитектуре"); }
- Ошибка времени компиляции возникает, если есть какие-либо блоки
labelв блоке asm с выходами.
naked_asm! поддерживает только опции att_syntax и raw. Оставшиеся опции не имеют смысла, потому что встроенный ассемблер определяет все тело функции.
global_asm! поддерживает только опции att_syntax и raw. Оставшиеся опции не имеют смысла для встроенного ассемблера в глобальной области видимости.
fn main() {} #[cfg(target_arch = "x86_64")] // nomem бесполезна на global_asm! core::arch::global_asm!("", options(nomem)); #[cfg(not(target_arch = "x86_64"))] core::compile_error!("Тест не поддерживается на этой архитектуре");
Правила для встроенного ассемблера
Чтобы избежать неопределенного поведения, эти правила должны соблюдаться при использовании встроенного ассемблера в области видимости функции (asm!):
- Любые регистры, не указанные как входы, будут содержать неопределенное значение при входе в ассемблерный код.
- “Неопределенное значение” в контексте встроенного ассемблера означает, что регистр может (недетерминированно) иметь любое из возможных значений, разрешенных архитектурой.
В частности, это не то же самое, что LLVM
undef, который может иметь разное значение каждый раз, когда вы его читаете (поскольку такая концепция не существует в ассемблерном коде).
- “Неопределенное значение” в контексте встроенного ассемблера означает, что регистр может (недетерминированно) иметь любое из возможных значений, разрешенных архитектурой.
В частности, это не то же самое, что LLVM
- Любые регистры, не указанные как выходы, должны иметь то же значение при выходе из ассемблерного кода, что и при входе, иначе поведение не определено.
- Это применяется только к регистрам, которые могут быть указаны как вход или выход. Другие регистры следуют целеспецифичным правилам.
- Обратите внимание, что
lateoutможет быть выделен в тот же регистр, что иin, в этом случае это правило не применяется. Однако код не должен полагаться на это, поскольку это зависит от результатов распределения регистров.
- Поведение не определено, если выполнение раскручивается (unwinds) из ассемблерного кода.
- Это также применяется, если ассемблерный код вызывает функцию, которая затем раскручивается.
- Набор мест памяти, которые ассемблерный код может читать и писать, тот же, что и разрешенный для функции FFI.
- Если установлена опция
readonly, то разрешены только чтения из памяти. - Если установлена опция
nomem, то никакие чтения или записи в память не разрешены. - Эти правила не применяются к памяти, которая является приватной для ассемблерного кода, такой как пространство стека, выделенное внутри него.
- Если установлена опция
- Компилятор не может предполагать, что инструкции в ассемблерном коде - это те, которые будут фактически выполнены.
- Это effectively означает, что компилятор должен рассматривать ассемблерный код как черный ящик и принимать во внимание только спецификацию интерфейса, а не сами инструкции.
- Разрешается патчинг кода во время выполнения через целеспецифичные механизмы.
- Однако нет гарантии, что каждый блок ассемблерного кода в исходнике напрямую соответствует единственному экземпляру инструкций в объектном файле; компилятор свободен дублировать или дедуплицировать ассемблерный код в блоках
asm!.
- Если опция
nostackне установлена, ассемблерному коду разрешено использовать пространство стека ниже указателя стека.- При входе в ассемблерный код указатель стека гарантированно будет соответственно выровнен (согласно ABI цели) для вызова функции.
- Вы ответственны за то, чтобы не переполнить стек (например, используйте probing стека, чтобы гарантировать, что вы попадете на guard page).
- Вы должны ajustить указатель стека при выделении памяти в стеке, как того требует ABI цели.
- Указатель стека должен быть восстановлен до своего исходного значения перед выходом из ассемблерного кода.
- Если установлена опция
noreturn, то поведение не определено, если выполнение проходит через конец ассемблерного кода.
- Если установлена опция
pure, то поведение не определено, еслиasm!имеет побочные эффекты, отличные от его прямых выходов. Поведение также не определено, если два выполнения кодаasm!с одинаковыми входами приводят к разным выходам.- При использовании с опцией
nomem, “входы” - это просто прямые входыasm!. - При использовании с опцией
readonly, “входы” включают прямые входы ассемблерного кода и любую память, которую ему разрешено читать.
- При использовании с опцией
- Эти регистры флагов должны быть восстановлены при выходе из ассемблерного кода, если установлена опция
preserves_flags:- x86
- Флаги состояния в
EFLAGS(CF, PF, AF, ZF, SF, OF). - Слово состояния с плавающей точкой (все).
- Флаги исключений с плавающей точкой в
MXCSR(PE, UE, OE, ZE, DE, IE).
- Флаги состояния в
- ARM
- Флаги условий в
CPSR(N, Z, C, V) - Флаг насыщения в
CPSR(Q) - Флаги “больше или равно” в
CPSR(GE). - Флаги условий в
FPSCR(N, Z, C, V) - Флаг насыщения в
FPSCR(QC) - Флаги исключений с плавающей точкой в
FPSCR(IDC, IXC, UFC, OFC, DZC, IOC).
- Флаги условий в
- AArch64 и Arm64EC
- Флаги условий (регистр
NZCV). - Состояние с плавающей точкой (регистр
FPSR).
- Флаги условий (регистр
- RISC-V
- Флаги исключений с плавающей точкой в
fcsr(fflags). - Состояние расширения векторов (
vtype,vl,vxsatиvxrm).
- Флаги исключений с плавающей точкой в
- LoongArch
- Флаги условий с плавающей точкой в
$fcc[0-7].
- Флаги условий с плавающей точкой в
- s390x
- Регистр кода условия
cc.
- Регистр кода условия
- x86
- На x86 флаг направления (DF в
EFLAGS) очищен при входе в ассемблерный код и должен быть очищен при выходе.- Поведение не определено, если флаг направления установлен при выходе из ассемблерного кода.
- На x86 стек регистров с плавающей точкой x87 должен оставаться неизменным, если все регистры
st([0-7])не были помечены как испорченные сout("st(0)") _, out("st(1)") _, ....- Если все регистры x87 испорчены, то стек регистров x87 гарантированно пуст при входе в ассемблерный код. Ассемблерный код должен гарантировать, что стек регистров x87 также пуст при выходе из ассемблерного кода.
#[cfg(target_arch = "x86_64")] pub fn fadd(x: f64, y: f64) -> f64 { let mut out = 0f64; let mut top = 0u16; // мы можем делать сложные вещи с x87, если мы портим весь стек x87 unsafe { core::arch::asm!( "fld qword ptr [{x}]", "fld qword ptr [{y}])", "faddp", "fstp qword ptr [{out}]", "xor eax, eax", "fstsw ax", "shl eax, 11", x = in(reg) &x, y = in(reg) &y, out = in(reg) &mut out, out("st(0)") _, out("st(1)") _, out("st(2)") _, out("st(3)") _, out("st(4)") _, out("st(5)") _, out("st(6)") _, out("st(7)") _, out("eax") top );} assert_eq!(top & 0x7, 0); out } pub fn main() { #[cfg(target_arch = "x86_64")]{ assert_eq!(fadd(1.0, 1.0), 2.0); } }
- На arm64ec, проверки вызовов с соответствующими thunks обязательны при вызове функций.
- Требование восстановления указателя стека и невыходных регистров до их исходного значения применяется только при выходе из ассемблерного кода.
- Это означает, что ассемблерный код, который не завершается нормально и не переходит к каким-либо блокам
label, даже если не помеченnoreturn, не нуждается в сохранении этих регистров. - При возврате в ассемблерный код другого блока
asm!, чем тот, в который вы вошли (например, для переключения контекста), эти регистры должны содержать значение, которое они имели при входе в блокasm!, который вы покидаете.- Вы не можете выйти из ассемблерного кода блока
asm!, который не был введен. Вы также не можете выйти из ассемблерного кода блокаasm!, чей ассемблерный код уже был покинут (без предварительного входа в него снова). - Вы ответственны за переключение любого целеспецифичного состояния (например, thread-local storage, границы стека).
- Вы не можете перейти с адреса в одном блоке
asm!на адрес в другом, даже в пределах той же функции или блока, без обработки их контекстов как потенциально разных и требующих переключения контекста. Вы не можете предполагать, что какое-либо конкретное значение в этих контекстах (например, текущий указатель стека или временные значения ниже указателя стека) останется неизменным между двумя блокамиasm!. - Набор мест памяти, к которым вы можете получить доступ, является пересечением тех, которые разрешены блоками
asm!, которые вы ввели и покинули.
- Вы не можете выйти из ассемблерного кода блока
- Это означает, что ассемблерный код, который не завершается нормально и не переходит к каким-либо блокам
- Вы не можете предполагать, что два блока
asm!, смежные в исходном коде, даже без любого другого кода между ними, окажутся в последовательных адресах в бинарном файле без каких-либо других инструкций между ними.
- Вы не можете предполагать, что блок
asm!появится ровно один раз в выходном бинарном файле. Компилятору разрешено создавать несколько копий блокаasm!, например, когда содержащая его функция встраивается в нескольких местах.
- На x86, встроенный ассемблер не должен заканчиваться префиксом инструкции (таким как
LOCK), который применялся бы к инструкциям, сгенерированным компилятором.- Компилятор в настоящее время не может обнаружить это из-за способа компиляции встроенного ассемблера, но может поймать и отклонить это в будущем.
Note
Как общее правило, флаги, покрываемые
preserves_flags, - это те, которые не сохраняются при выполнении вызова функции.
Правила для голого встроенного ассемблера
Чтобы избежать неопределенного поведения, эти правила должны соблюдаться при использовании встроенного ассемблера в области видимости функции в голых функциях (naked_asm!):
- Любые регистры, не используемые для входов функции согласно соглашению о вызовах и сигнатуре функции, будут содержать неопределенное значение при входе в блок
naked_asm!.- “Неопределенное значение” в контексте встроенного ассемблера означает, что регистр может (недетерминированно) иметь любое из возможных значений, разрешенных архитектурой. В частности, это не то же самое, что LLVM
undef, который может иметь разное значение каждый раз, когда вы его читаете (поскольку такая концепция не существует в ассемблерном коде).
- “Неопределенное значение” в контексте встроенного ассемблера означает, что регистр может (недетерминированно) иметь любое из возможных значений, разрешенных архитектурой. В частности, это не то же самое, что LLVM
- Все регистры, сохраняемые вызываемой стороной (callee-saved), должны иметь то же значение при возврате, что и при входе.
- Регистры, сохраняемые вызывающей стороной (caller-saved), могут использоваться свободно.
- Поведение не определено, если выполнение проходит за конец ассемблерного кода.
- Ожидается, что каждый путь через ассемблерный код завершается инструкцией возврата или расходится.
- Набор мест памяти, которые ассемблерный код может читать и писать, тот же, что и разрешенный для функции FFI.
- Компилятор не может предполагать, что инструкции в блоке
naked_asm!- это те, которые будут фактически выполнены.- Это effectively означает, что компилятор должен рассматривать
naked_asm!как черный ящик и принимать во внимание только спецификацию интерфейса, а не сами инструкции. - Разрешается патчинг кода во время выполнения через целеспецифичные механизмы.
- Это effectively означает, что компилятор должен рассматривать
- Раскрутка (unwinding) из блока
naked_asm!разрешена.- Для корректного поведения должны использоваться соответствующие ассемблерные директивы, которые излучают метаданные раскрутки.
#![allow(unused)] fn main() { #[cfg(target_arch = "x86_64")] { #[unsafe(naked)] extern "sysv64-unwind" fn unwinding_naked() { core::arch::naked_asm!( // "CFI" здесь означает "call frame information". ".cfi_startproc", // CFA (canonical frame address) - это значение `rsp` // до `call`, т.е. до того, как обратный адрес, `rip`, // был помещен в `rsp`, так что оно на восемь байт выше в памяти, // чем `rsp` при входе в функцию (после того, как `rip` был // помещен). // // Это значение по умолчанию, поэтому нам не нужно его писать. //".cfi_def_cfa rsp, 8", // // Традиционно сохранять базовый указатель, // так что мы сделаем это. "push rbp", // Поскольку мы теперь расширили стек вниз на 8 байт в // памяти, нам нужно ajustить смещение до CFA от `rsp` // еще на 8 байт. ".cfi_adjust_cfa_offset 8", // Мы также аннотируем, где мы сохранили значение вызывающей стороны // `rbp`, относительно CFA, так что при раскрутке в // вызывающую сторону мы можем найти его, на случай, если нам нужно вычислить // CFA вызывающей стороны относительно него. // // Здесь мы сохранили `rbp` вызывающей стороны, начиная с 16 байт // ниже CFA. Т.е., начиная с CFA, сначала идет // `rip` (который начинается на 8 байт ниже CFA и продолжается // до него), затем идет `rbp` вызывающей стороны, который мы только что // поместили. ".cfi_offset rbp, -16", // Как традиционно, мы устанавливаем базовый указатель в значение // указателя стека. Таким образом, базовый указатель остается // тем же самым на протяжении тела функции. "mov rbp, rsp", // Теперь мы можем отслеживать смещение до CFA от базового // указателя. Это означает, что нам не нужно делать дальнейшие // ajustments до конца, так как мы не меняем `rbp`. ".cfi_def_cfa_register rbp", // Теперь мы можем вызвать функцию, которая может паниковать. "call {f}", // При возврате мы восстанавливаем `rbp` при подготовке к возврату // сами. "pop rbp", // Теперь, когда мы восстановили `rbp`, мы должны снова указать смещение // до CFA в терминах `rsp`. ".cfi_def_cfa rsp, 8", // Теперь мы можем вернуться. "ret", ".cfi_endproc", f = sym may_panic, ) } extern "sysv64-unwind" fn may_panic() { panic!("unwind"); } } }
Note
Для получения дополнительной информации о директивах ассемблера
cfiвыше см. эти ресурсы:
Корректность и валидность
В дополнение ко всем предыдущим правилам, строковый аргумент asm! должен в конечном счете стать—
после того, как все другие аргументы оценены, форматирование выполнено и операнды переведены—
ассемблерным кодом, который является как синтаксически корректным, так и семантически валидным для целевой архитектуры.
Правила форматирования позволяют компилятору генерировать ассемблерный код с правильным синтаксисом.
Правила, касающиеся операндов, permit валидный перевод операндов Rust в ассемблерный код и из него.
Соблюдение этих правил необходимо, но не достаточно, чтобы окончательный расширенный ассемблерный код был
как корректным, так и валидным. Например:
- аргументы могут быть помещены в позиции, которые синтаксически некорректны после форматирования
- инструкция может быть правильно написана, но иметь архитектурно невалидные операнды
- архитектурно неспецифицированная инструкция может быть ассемблирована в неспецифицированный код
- набор инструкций, каждая корректная и валидная, может вызвать неопределенное поведение, если помещены в непосредственной последовательности
Как результат, эти правила неисчерпывающи. Компилятор не обязан проверять
корректность и валидность исходной строки ни окончательного сгенерированного ассемблерного кода.
Ассемблер может проверять на корректность и валидность, но не обязан делать это.
При использовании asm!, опечатка может быть достаточной, чтобы сделать программу некорректной (unsound),
и правила для ассемблера могут включать тысячи страниц архитектурных справочных руководств.
Программисты должны проявлять соответствующую осторожность, так как вызов этой unsafe возможности сопровождается
принятием на себя ответственности за несоблюдение правил как компилятора, так и архитектуры.
Поддержка директив
Встроенный ассемблер поддерживает подмножество директив, поддерживаемых как GNU AS, так и внутренним ассемблером LLVM, приведенных ниже. Результат использования других директив специфичен для ассемблера (и может вызвать ошибку, или может быть принят как есть).
Если встроенный ассемблер включает любую “stateful” директиву, которая изменяет обработку последующего ассемблера, ассемблерный код должен отменить эффекты любых таких директив до окончания встроенного ассемблера.
Следующие директивы гарантированно поддерживаются ассемблером:
.2byte.4byte.8byte.align.alt_entry.ascii.asciz.balign.balignl.balignw.bss.byte.comm.data.def.double.endef.equ.equiv.eqv.fill.float.global.globl.inst.insn.lcomm.long.octa.option.p2align.popsection.private_extern.pushsection.quad.scl.section.set.short.size.skip.sleb128.space.string.text.type.uleb128.word
#![allow(unused)] fn main() { #[cfg(target_arch = "x86_64")] { let bytes: *const u8; let len: usize; unsafe { core::arch::asm!( "jmp 3f", "2: .ascii \"Hello World!\"", "3: lea {bytes}, [2b+rip]", "mov {len}, 12", bytes = out(reg) bytes, len = out(reg) len ); } let s = unsafe { core::str::from_utf8_unchecked(core::slice::from_raw_parts(bytes, len)) }; assert_eq!(s, "Hello World!"); } }
Поддержка целеспецифичных директив
Раскрутка DWARF
Следующие директивы поддерживаются на целях ELF, которые поддерживают информацию о раскрутке DWARF:
.cfi_adjust_cfa_offset.cfi_def_cfa.cfi_def_cfa_offset.cfi_def_cfa_register.cfi_endproc.cfi_escape.cfi_lsda.cfi_offset.cfi_personality.cfi_register.cfi_rel_offset.cfi_remember_state.cfi_restore.cfi_restore_state.cfi_return_column.cfi_same_value.cfi_sections.cfi_signal_frame.cfi_startproc.cfi_undefined.cfi_window_save
Структурированная обработка исключений
На целях со структурированной обработкой исключений, следующие дополнительные директивы гарантированно поддерживаются:
.seh_endproc.seh_endprologue.seh_proc.seh_pushreg.seh_savereg.seh_setframe.seh_stackalloc
x86 (32-битный и 64-битный)
На целях x86, как 32-битных, так и 64-битных, следующие дополнительные директивы гарантированно поддерживаются:
.nops.code16.code32.code64
Использование директив .code16, .code32 и .code64 поддерживается только если состояние сброшено к значению по умолчанию до выхода из ассемблерного кода.
32-битный x86 использует .code32 по умолчанию, и x86_64 использует .code64 по умолчанию.
ARM (32-битный)
На ARM, следующие дополнительные директивы гарантированно поддерживаются:
.even.fnstart.fnend.save.movsp.code.thumb.thumb_func
Небезопасность (Unsafe)
Небезопасные операции - это те, которые потенциально могут нарушить гарантии безопасности памяти статической семантики Rust.
Следующие особенности языка не могут быть использованы в безопасном подмножестве Rust:
- Разыменование сырого указателя.
- Чтение или запись изменяемой или небезопасной внешней статической переменной.
- Доступ к полю
union, кроме как для присваивания ему.
- Вызов небезопасной функции.
- Вызов безопасной функции, помеченной
target_feature, из функции, которая не имеет атрибутаtarget_feature, включающего те же особенности (см. attributes.codegen.target_feature.safety-restrictions).
- Реализация небезопасного трейта.
- Объявление блока [
extern]1.
- Применение небезопасного атрибута к элементу.
-
До редакции 2024 года блоки extern разрешалось объявлять без
unsafe. ↩
Ключевое слово unsafe
Ключевое слово unsafe используется для создания или снятия обязательства доказывать безопасность чего-либо. Конкретно:
- Оно используется для пометки кода, который определяет дополнительные условия безопасности, которые должны соблюдаться в других местах.
- Это включает
unsafe fn,unsafe staticиunsafe trait.
- Это включает
- Оно используется для пометки кода, для которого программист утверждает, что удовлетворяет условиям безопасности, определенным в других местах.
- Это включает
unsafe {},unsafe impl,unsafe fnбезunsafe_op_in_unsafe_fn,unsafe externи#[unsafe(attr)].
- Это включает
Далее обсуждается каждый из этих случаев. См. документацию по ключевым словам для некоторых иллюстративных примеров.
Ключевое слово unsafe может встречаться в нескольких различных контекстах:
- небезопасные функции (
unsafe fn) - небезопасные блоки (
unsafe {}) - небезопасные трейты (
unsafe trait) - небезопасные реализации трейтов (
unsafe impl) - небезопасные внешние блоки (
unsafe extern) - небезопасные внешние статические переменные (
unsafe static) - небезопасные атрибуты (
#[unsafe(attr)])
Небезопасные функции (unsafe fn)
Небезопасные функции - это функции, которые не являются безопасными во всех контекстах и/или для всех возможных входных данных.
Мы говорим, что они имеют дополнительные условия безопасности, которые являются требованиями, которые должны соблюдаться всеми вызывающими сторонами и которые компилятор не проверяет.
Например, get_unchecked имеет дополнительное условие безопасности, что индекс должен быть в пределах.
Небезопасная функция должна сопровождаться документацией, объясняющей, что это за дополнительные условия безопасности.
Такая функция должна быть предварена ключевым словом unsafe и может быть вызвана только изнутри блока unsafe или изнутри unsafe fn без линты unsafe_op_in_unsafe_fn.
Небезопасные блоки (unsafe {})
Блок кода может быть предварен ключевым словом unsafe, чтобы разрешить использование небезопасных действий, как определено в главе Небезопасность, таких как вызов других небезопасных функций или разыменование сырых указателей.
По умолчанию тело небезопасной функции также считается небезопасным блоком;
это можно изменить, включив линту unsafe_op_in_unsafe_fn.
Помещая операции в небезопасный блок, программист заявляет, что он позаботился об удовлетворении дополнительных условий безопасности всех операций внутри этого блока.
Небезопасные блоки являются логическим двойником небезопасных функций:
где небезопасные функции определяют обязательство доказательства, которое должны выполнять вызывающие стороны, небезопасные блоки утверждают, что все соответствующие обязательства доказательства функций или операций, вызванных внутри блока, были выполнены.
Существует много способов выполнить обязательства доказательства;
например, могут быть проверки во время выполнения или инварианты структур данных, которые гарантируют, что определенные свойства точно истинны, или небезопасный блок может быть внутри unsafe fn, в котором случае блок может использовать обязательства доказательства этой функции для выполнения обязательств доказательства, возникающих внутри блока.
Небезопасные блоки используются для обертывания внешних библиотек, прямого использования оборудования или реализации функций, отсутствующих непосредственно в языке. Например, Rust предоставляет языковые возможности, необходимые для реализации безопасного параллелизма памяти на языке, но реализация потоков и передачи сообщений в стандартной библиотеке использует небезопасные блоки.
Система типов Rust является консервативным приближением динамических требований безопасности, поэтому в некоторых случаях использование безопасного кода имеет производительностные затраты.
Например, двусвязный список не является древовидной структурой и может быть представлен только указателями с подсчетом ссылок в безопасном коде.
Используя unsafe блоки для представления обратных ссылок как сырых указателей, его можно реализовать без подсчета ссылок.
(См. “Learn Rust With Entirely Too Many Linked Lists” для более глубокого изучения этого конкретного примера.)
Небезопасные трейты (unsafe trait)
Небезопасный трейт - это трейт, который поставляется с дополнительными условиями безопасности, которые должны соблюдаться реализациями трейта. Небезопасный трейт должен сопровождаться документацией, объясняющей, что это за дополнительные условия безопасности.
Такой трейт должен быть предварен ключевым словом unsafe и может быть реализован только блоками unsafe impl.
Небезопасные реализации трейтов (unsafe impl)
При реализации небезопасного трейта реализация должна быть предварена ключевым словом unsafe.
Написав unsafe impl, программист заявляет, что он позаботился об удовлетворении дополнительных условий безопасности, требуемых трейтом.
Небезопасные реализации трейтов являются логическим двойником небезопасных трейтов: где небезопасные трейты определяют обязательство доказательства, которое должны выполнять реализации, небезопасные реализации утверждают, что все соответствующие обязательства доказательства были выполнены.
Небезопасные внешние блоки (unsafe extern)
Программист, который объявляет внешний блок, должен гарантировать, что сигнатуры содержащихся в нем элементов корректны. Невыполнение этого может привести к неопределенному поведению. То, что это обязательство было выполнено, указывается написанием unsafe extern.
2024 Edition differences
До редакции 2024 года блоки
externразрешалось использовать без квалификации какunsafe.
Небезопасные атрибуты (#[unsafe(attr)])
Небезопасный атрибут - это атрибут, который имеет дополнительные условия безопасности, которые должны соблюдаться при использовании атрибута. Компилятор не может проверить, были ли эти условия соблюдены. Чтобы утверждать, что они были соблюдены, эти атрибуты должны быть обернуты в unsafe(..), например #[unsafe(no_mangle)].
Поведение, считающееся неопределенным
Код Rust является некорректным, если он демонстрирует любое из поведений в следующем
списке. Это включает код внутри unsafe блоков и unsafe функций.
unsafe только означает, что избежание неопределенного поведения лежит на программисте; это
не меняет ничего в том факте, что программы Rust никогда не должны вызывать
неопределенное поведение.
Это ответственность программиста при написании unsafe кода обеспечить, чтобы
любой безопасный код, взаимодействующий с unsafe кодом, не мог вызвать эти
поведения. unsafe код, который удовлетворяет этому свойству для любого безопасного клиента,
называется корректным (sound); если unsafe код может быть неправильно использован безопасным кодом для демонстрации
неопределенного поведения, он некорректный (unsound).
Warning
Следующий список не является исчерпывающим; он может расти или сокращаться. Не существует формальной модели семантики Rust для того, что разрешено, а что нет в небезопасном коде, поэтому может быть больше поведений, считающихся небезопасными. Мы также оставляем за собой право сделать некоторое поведение в этом списке определенным в будущем. Другими словами, этот список не говорит, что что-то определенно всегда будет неопределенным во всех будущих версиях Rust (но мы можем сделать такие обязательства для некоторых элементов списка в будущем).
Пожалуйста, прочитайте Rustonomicon перед написанием небезопасного кода.
- Гонки данных (Data races).
- Доступ (чтение или запись) к месту, которое является висячим dangling или основанным на невыровненном указателе.
- Выполнение проекции места, которая нарушает требования арифметики указателей в пределах. Проекция места - это выражение поля, выражение индекса кортежа или выражение индекса массива/среза.
-
Нарушение правил псевдонимов указателей. Точные правила псевдонимов еще не определены, но вот общий принцип:
&Tдолжен указывать на память, которая не изменяется, пока они живы (за исключением данных внутриUnsafeCell<U>), и&mut Tдолжен указывать на память, которая не читается и не записывается любым указателем, не производным от ссылки, и на которую не указывает никакая другая ссылка, пока они живы.Box<T>обрабатывается аналогично&'static mut Tдля целей этих правил. Точная продолжительность жизни не указана, но существуют некоторые границы:- Для ссылок продолжительность жизни ограничена сверху синтаксическим временем жизни, назначенным проверщиком заимствований; она не может жить дольше, чем это время жизни.
- Каждый раз, когда ссылка или бокс разыменовывается или перезаимствуется, они считаются живыми.
- Каждый раз, когда ссылка или бокс передаются в функцию или возвращаются из функции, они считаются живыми.
- Когда ссылка (но не
Box!) передается в функцию, она жива по крайней мере так долго, как этот вызов функции, снова за исключением случая, когда&TсодержитUnsafeCell<U>.
Все это также применяется, когда значения этих типов передаются в (вложенном) поле составного типа, но не за указательными индирекциями.
-
Изменение неизменяемых байтов. Все байты, доступные через const-промотированное выражение, являются неизменяемыми, а также байты, доступные через заимствования в инициализаторах
staticиconst, которые были расширены по времени жизни до'static. Байты, принадлежащие неизменяемой привязке или неизменяемомуstatic, являются неизменяемыми, если эти байты не являются частьюUnsafeCell<U>.Более того, байты, указываемые разделяемой ссылкой, включая транзитивно через другие ссылки (как разделяемые, так и изменяемые) и
Boxы, являются неизменяемыми; транзитивность включает те ссылки, которые хранятся в полях составных типов.Изменением является любая запись более чем 0 байтов, которая перекрывается с любыми из соответствующих байтов (даже если эта запись не изменяет содержимое памяти).
- Вызов неопределенного поведения через компиляторные интринсики.
- Выполнение кода, скомпилированного с особенностями платформы, которые текущая платформа
не поддерживает (см.
target_feature), за исключением случаев, когда платформа явно документирует, что это безопасно.
- Вызов функции с неправильным ABI вызова или раскрутка стека за фрейм, который не позволяет раскрутку (например, вызовом функции
"C-unwind", импортированной или преобразованной как функция или указатель на функцию"C").
- Создание недопустимого значения. “Создание” значения происходит всякий раз, когда значение присваивается или читается из места, передается в функцию/примитивную операцию или возвращается из функции/примитивной операции.
- Неправильное использование встроенного ассемблера. Для более подробной информации обратитесь к правилам для следования при написании кода, который использует встроенный ассемблер.
- В контексте const: преобразование или иная реинтерпретация указателя (ссылки, сырого указателя или указателя на функцию) в некоторое выделение как в не-указательный тип (такой как целые числа). ‘Реинтерпретация’ относится к загрузке значения указателя как целочисленного типа без приведения, например, путем приведения сырых указателей или использования объединения (union).
- Нарушение предположений среды выполнения Rust. Большинство предположений среды выполнения Rust в настоящее время не документированы явно.
- Для предположений, специфически связанных с раскруткой стека, см. документацию по панике.
- Среда выполнения предполагает, что фрейм стека Rust не освобождается без выполнения деструкторов для локальных переменных, принадлежащих фрейму стека. Это предположение может быть нарушено функциями C, такими как
longjmp.
Note
Неопределенное поведение влияет на всю программу. Например, вызов функции в C, которая демонстрирует неопределенное поведение C, означает, что вся ваша программа содержит неопределенное поведение, которое также может повлиять на код Rust. И наоборот, неопределенное поведение в Rust может вызвать неблагоприятные эффекты на код, выполняемый любыми вызовами FFI к другим языкам.
Указываемые байты
Диапазон байтов, на которые “указывает” указатель или ссылка, определяется значением указателя и размером типа указываемого объекта (используя size_of_val).
Места, основанные на невыровненных указателях
Говорят, что место “основано на невыровненном указателе”, если последняя * проекция
во время вычисления места была выполнена на указателе, который не был выровнен для его
типа. (Если в выражении места нет * проекции, то это
доступ к полю локальной переменной или static, и rustc гарантирует правильное выравнивание. Если
есть несколько * проекций, то каждая из них влечет загрузку
самого указателя для разыменования из памяти, и каждая из этих загрузок
подвержена ограничению выравнивания. Обратите внимание, что некоторые * проекции могут быть
опущены в поверхностном синтаксисе Rust из-за автоматического разыменования; мы
рассматриваем полностью развернутое выражение места здесь.)
Например, если ptr имеет тип *const S, где S имеет выравнивание 8, то
ptr должен быть выровнен по 8, иначе (*ptr).f “основано на невыровненном указателе”.
Это верно, даже если тип поля f - u8 (т.е. тип с
выравниванием 1). Другими словами, требование выравнивания происходит от типа
указателя, который был разыменован, а не от типа поля, к которому осуществляется доступ.
Обратите внимание, что место, основанное на невыровненном указателе, приводит к Неопределенному Поведению только когда оно загружается или сохраняется.
&raw const/&raw mut на таком месте разрешены.
&/&mut на месте требует выравнивания типа поля (или
иначе программа будет “создавать недопустимое значение”), что обычно является
менее строгим требованием, чем быть основанным на выровненном указателе.
Взятие ссылки приведет к ошибке компилятора в случаях, когда тип поля может быть
более выровненным, чем тип, который его содержит, т.е. repr(packed). Это означает,
что быть основанным на выровненном указателе всегда достаточно, чтобы гарантировать, что
новая ссылка выровнена, но это не всегда необходимо.
Висячие указатели
Ссылка/указатель является “висячим (dangling)”, если не все байты, которые она указывает, являются частью того же живого выделения (так, в частности, они все должны быть частью какого-то выделения).
Если размер равен 0, то указатель тривиально никогда не является “висячим” (даже если это нулевой указатель).
Обратите внимание, что типы с динамическим размером (такие как срезы и строки) указывают на их весь диапазон, поэтому важно, чтобы метаданные длины никогда не были слишком большими.
В частности, динамический размер значения Rust (как определено size_of_val)
никогда не должен превышать isize::MAX, поскольку невозможно, чтобы одно выделение
было больше isize::MAX.
Недопустимые значения
Компилятор Rust предполагает, что все значения, произведенные во время выполнения программы, “допустимы”, и создание недопустимого значения, следовательно, является немедленным НП.
Является ли значение допустимым, зависит от типа:
- Значение
boolдолжно бытьfalse(0) илиtrue(1).
- Значение указателя на функцию
fnдолжно быть ненулевым.
- Значение
charне должно быть суррогатом (т.е. не должно быть в диапазоне0xD800..=0xDFFF) и должно быть равно или меньшеchar::MAX.
- Значение
!никогда не должно существовать.
- Целое число (
i*/u*), значение с плавающей точкой (f*) или сырой указатель должны быть инициализированы, т.е. не должны быть получены из неинициализированной памяти.
- Значение
strобрабатывается как[u8], т.е. оно должно быть инициализировано.
enumдолжен иметь допустимый дискриминант, и все поля варианта, указанного этим дискриминантом, должны быть допустимыми для их соответствующих типов.
struct, кортеж и массив требуют, чтобы все поля/элементы были допустимыми для их соответствующих типов.
- Для
unionточные требования допустимости еще не решены. Очевидно, все значения, которые могут быть созданы entirely в безопасном коде, допустимы. Если объединение имеет поле с нулевым размером, то каждое возможное значение допустимо. Дальнейшие детали все еще обсуждаются.
- Ссылка или
Box<T>должны быть выровнены и ненулевыми, они не могут быть висячими, и они должны указывать на допустимое значение (в случае типов с динамическим размером, используя фактический динамический тип указываемого объекта, определенный метаданными). Обратите внимание, что последний пункт (о указании на допустимое значение) остается предметом некоторых дискуссий.
- Метаданные широкой ссылки,
Box<T>или сырого указателя должны соответствовать типу неразмерного хвоста:- Метаданные
dyn Traitдолжны быть указателем на сгенерированную компилятором таблицу виртуальных функций дляTrait. (Для сырых указателей это требование остается предметом некоторых дискуссий.) - Метаданные среза (
[T]) должны быть допустимымusize. Кроме того, для широких ссылок иBox<T>метаданные среза недопустимы, если они делают общий размер указываемого значения большеisize::MAX.
- Метаданные
-
Если тип имеет пользовательский диапазон допустимых значений, то допустимое значение должно быть в этом диапазоне. В стандартной библиотеке это затрагивает
NonNull<T>иNonZero<T>.Note
rustcдостигает этого с помощью нестабильных атрибутовrustc_layout_scalar_valid_range_*.
Примечание: Неинициализированная память также неявно недопустима для любого типа, который имеет
ограниченный набор допустимых значений. Другими словами, единственные случаи, в которых
чтение неинициализированной памяти разрешено, - это внутри union и в “заполнении (padding)”
(промежутках между полями типа).
Поведение, не считающееся unsafe
Компилятор Rust не считает следующие поведения небезопасными, хотя программист может (должен) находить их нежелательными, неожиданными или ошибочными.
- Взаимные блокировки (Deadlocks)
- Утечки памяти и других ресурсов
- Выход без вызова деструкторов
- Раскрытие рандомизированных базовых адресов через утечки указателей
Переполнение целых чисел
Если программа содержит арифметическое переполнение, программист допустил ошибку. В следующем обсуждении мы сохраняем различие между арифметическим переполнением и оборачивающей арифметикой. Первое ошибочно, в то время как второе является преднамеренным.
Когда программист включил утверждения debug_assert! (например,
включив неоптимизированную сборку), реализации должны
вставлять динамические проверки, которые вызывают panic при переполнении. Другие виды сборок
могут приводить к panic или тихому оборачиванию значений при переполнении, на
усмотрение реализации.
В случае неявно оборачиваемого переполнения реализации должны предоставлять хорошо определенные (даже если все еще считающиеся ошибочными) результаты, используя соглашения о переполнении дополнения до двух.
Целочисленные типы предоставляют встроенные методы, позволяющие программистам
явно выполнять оборачивающую арифметику. Например,
i32::wrapping_add предоставляет сложение с оборачиванием по дополнению до двух.
Стандартная библиотека также предоставляет ньютайп Wrapping<T>, который
гарантирует, что все стандартные арифметические операции для T имеют семантику оборачивания.
См. RFC 560 для условий ошибок, обоснования и более подробной информации о переполнении целых чисел.
Логические ошибки
Безопасный код может налагать дополнительные логические ограничения, которые нельзя проверить ни во время компиляции, ни во время выполнения. Если программа нарушает такие ограничения, поведение может быть неспецифицированным, но не приведет к неопределенному поведению. Это может включать паники, некорректные результаты, аварийное завершение и незавершение. Поведение также может различаться между запусками, сборками или видами сборок.
Например, реализация как Hash, так и Eq требует, чтобы значения,
считающиеся равными, имели равные хеши. Другой пример - структуры данных
такие как BinaryHeap, BTreeMap, BTreeSet, HashMap и HashSet,
которые описывают ограничения на модификацию их ключей, пока
они находятся в структуре данных. Нарушение таких ограничений не
считается небезопасным, однако программа считается ошибочной и
ее поведение непредсказуемым.
Вычисление констант (Constant evaluation)
Вычисление констант - это процесс вычисления результата выражений во время компиляции. Только подмножество всех выражений может быть вычислено во время компиляции.
Выражения констант
Определенные формы выражений, называемые выражениями констант, могут быть вычислены во время компиляции.
В контекстах const это единственные разрешенные выражения, и они всегда вычисляются во время компиляции.
В других местах, таких как let statements, выражения констант могут быть, но не гарантированно, вычислены во время компиляции.
Поведения, такие как выход за границы индексации массива или переполнение, являются ошибками компилятора, если значение должно быть вычислено во время компиляции (т.е. в контекстах const). В противном случае, эти поведения являются предупреждениями, но, вероятно, вызовут панику во время выполнения.
Следующие выражения являются выражениями констант, при условии, что любые операнды также
являются выражениями констант и не вызывают запуск каких-либо вызовов Drop::drop.
- Пути к функциям и константам. Рекурсивное определение констант не разрешено.
-
Пути к статическим переменным с этими ограничениями:
- Запись в
staticэлементы не разрешена в любом контексте вычисления констант. - Чтение из
externстатических переменных не разрешено в любом контексте вычисления констант. - Если вычисление не выполняется в инициализаторе
staticэлемента, то чтение из любой изменяемойstaticне разрешено. Изменяемаяstatic- это элементstatic mutилиstaticэлемент с внутренне-изменяемым типом.
Эти требования проверяются только когда константа вычисляется. Другими словами, синтаксическое наличие таких обращений в контекстах const разрешено, пока они никогда не выполняются.
- Запись в
- Блочные выражения, включая
unsafeиconstблоки.- let statements и, следовательно, неопровержимые patterns, включая изменяемые привязки
- выражения присваивания
- составные выражения присваивания
- выражения-операторы
- Выражения индекса, индексация массива или среза с
usize.
- Выражения замыканий, которые не захватывают переменные из окружения.
- Встроенные операторы отрицания, арифметические, логические, сравнения или ленивые булевы
операторы, используемые на целочисленных и типах с плавающей точкой,
boolиchar.
-
Все формы заимствований, включая сырые заимствования, за исключением заимствований выражений, временные области видимости которых были бы расширены (см. расширение времени жизни временных) до конца программы и которые являются либо:
- Изменяемыми заимствованиями.
- Разделяемыми заимствованиями выражений, которые приводят к значениям с внутренней изменяемостью.
#![allow(unused)] fn main() { // Из-за хвостовой позиции это заимствование расширяет область видимости // временного до конца программы. Поскольку заимствование изменяемое, // это не разрешено в выражении константы. const C: &u8 = &mut 0; // ОШИБКА не разрешено }#![allow(unused)] fn main() { // Const блоки похожи на инициализаторы `const` элементов. let _: &u8 = const { &mut 0 }; // ОШИБКА не разрешено }#![allow(unused)] fn main() { use core::sync::atomic::AtomicU8; // Это не разрешено, так как 1) временная область видимости расширена до // конца программы и 2) временный объект имеет внутреннюю изменяемость. const C: &AtomicU8 = &AtomicU8::new(0); // ОШИБКА не разрешено }#![allow(unused)] fn main() { use core::sync::atomic::AtomicU8; // Как выше. let _: &_ = const { &AtomicU8::new(0) }; // ОШИБКА не разрешено }#![allow(unused)] fn main() { #![allow(static_mut_refs)] // Несмотря на то, что это заимствование изменяемое, оно не временного объекта, так что // это разрешено. const C: &u8 = unsafe { static mut S: u8 = 0; &mut S }; // OK }#![allow(unused)] fn main() { use core::sync::atomic::AtomicU8; // Несмотря на то, что это заимствование значения с внутренней изменяемостью, // оно не временного объекта, так что это разрешено. const C: &AtomicU8 = { static S: AtomicU8 = AtomicU8::new(0); &S // OK }; }#![allow(unused)] fn main() { use core::sync::atomic::AtomicU8; // Это разделяемое заимствование внутренне изменяемого временного объекта разрешено // потому что его область видимости не расширена. const C: () = { _ = &AtomicU8::new(0); }; // OK }#![allow(unused)] fn main() { // Несмотря на то, что заимствование изменяемое и временный объект живет до // конца программы из-за промоута, это разрешено, потому что // заимствование не в хвостовой позиции и поэтому область видимости временного // не расширена через расширение времени жизни временных. const C: () = { let _: &'static mut [u8] = &mut []; }; // OK // ~~ // Промоутированный временный объект. }Note
Другими словами — чтобы сосредоточиться на том, что разрешено, а не на том, что запрещено — разделяемые заимствования внутренне изменяемых данных и изменяемые заимствования разрешены в контексте const только когда заимствованное выражение места является транзитным, косвенным или статическим.
Выражение места является транзитным, если это переменная, локальная для текущего контекста const, или выражение, временная область видимости которого содержится внутри текущего контекста const.
#![allow(unused)] fn main() { // Заимствование переменной, локальной для инициализатора, следовательно // это выражение места транзитное. const C: () = { let mut x = 0; _ = &mut x; }; }#![allow(unused)] fn main() { // Заимствование временного объекта, область видимости которого не была расширена, // следовательно это выражение места транзитное. const C: () = { _ = &mut 0u8; }; }#![allow(unused)] fn main() { // Когда временный объект промоутирован, но не расширен по времени жизни, его // выражение места все еще рассматривается как транзитное. const C: () = { let _: &'static mut [u8] = &mut []; }; }Выражение места является косвенным, если это выражение разыменования.
#![allow(unused)] fn main() { const C: () = { _ = &mut *(&mut 0); }; }Выражение места является статическим, если это
staticэлемент.#![allow(unused)] fn main() { #![allow(static_mut_refs)] const C: &u8 = unsafe { static mut S: u8 = 0; &mut S }; }Note
Одним неожиданным следствием этих правил является то, что мы разрешаем это,
#![allow(unused)] fn main() { const C: &[u8] = { let x: &mut [u8] = &mut []; x }; // OK // ~~~~~~~ // Пустые массивы промоутируются даже за изменяемыми заимствованиями. }но мы запрещаем этот похожий код:
#![allow(unused)] fn main() { const C: &[u8] = &mut []; // ERROR // ~~~~~~~ // Хвостовое выражение. }Разница между ними в том, что в первом пустой массив промоутирован, но его область видимости не подвергается расширению времени жизни временных, поэтому мы считаем выражение места транзитным (даже если после промоута место действительно живет до конца программы). Во втором область видимости временного пустого массива подвергается расширению времени жизни, и поэтому он отвергается из-за того, что является изменяемым заимствованием временного с расширенным временем жизни (и, следовательно, заимствует нетранзитное выражение места).
Эффект неожиданный, потому что расширение времени жизни временных в этом случае приводит к тому, что меньше кода компилируется, чем без него.
См. issue #143129 для более подробной информации.
- Оператор разыменования, за исключением сырых указателей.
- Группированные выражения.
- Выражения приведения, за исключением
- приведения указателя к адресу и
- приведения указателя на функцию к адресу.
- Вызовы const функций и const методов.
Контекст const
Контекст const - это один из следующих:
- Инициализатор
Контексты const, которые используются как части типов (выражения длины типа массива и повторения, а также аргументы const обобщений), могут только ограниченно использовать окружающие обобщенные параметры: такое выражение должно быть либо единственным голым const обобщенным параметром, либо произвольным выражением, не использующим никаких обобщений.
Const функции
Const функция - это функция, которая может быть вызвана из контекста const. Она определяется с квалификатором const и также включает конструкторы кортежных структур и вариантов кортежных перечислений.
Example
#![allow(unused)] fn main() { const fn square(x: i32) -> i32 { x * x } const VALUE: i32 = square(12); }
При вызове из контекста const, const функция интерпретируется компилятором во время компиляции. Интерпретация происходит в среде цели компиляции, а не хоста. Так что usize - это 32 бита, если вы компилируете для 32 битной системы, независимо от того, собираете ли вы на 64 битной или 32 битной системе.
Когда const функция вызывается извне контекста const, она ведет себя так же, как если бы у нее не было квалификатора const.
Тело const функции может использовать только выражения констант.
Const функции не могут быть async.
Типы параметров const функции и тип возвращаемого значения ограничены теми, которые совместимы с контекстом const.
Двоичный интерфейс приложения (Application binary interface, ABI)
Этот раздел документирует особенности, которые влияют на ABI скомпилированного вывода крейта.
См. внешние функции для информации о указании ABI для экспорта функций. См. внешние блоки для информации о указании ABI для линковки внешних библиотек.
Атрибут used
Атрибут used может быть применен только к static элементам. Этот атрибут принуждает
компилятор сохранять переменную в выходном объектном файле (.o, .rlib, и т.д., исключая финальные бинарные файлы)
даже если переменная не используется или не ссылается никаким другим элементом в крейте.
Однако, линковщик все еще свободен удалить такой элемент.
Ниже приведен пример, который показывает, при каких условиях компилятор сохраняет static элемент в
выходном объектном файле.
#![allow(unused)] fn main() { // foo.rs // Это сохраняется из-за `#[used]`: #[used] static FOO: u32 = 0; // Это удаляемо, потому что не используется: #[allow(dead_code)] static BAR: u32 = 0; // Это сохраняется, потому что публично достижимо: pub static BAZ: u32 = 0; // Это сохраняется, потому что на него ссылается публичная, достижимая функция: static QUUX: u32 = 0; pub fn quux() -> &'static u32 { &QUUX } // Это удаляемо, потому что на него ссылается приватная, неиспользуемая (мертвая) функция: static CORGE: u32 = 0; #[allow(dead_code)] fn corge() -> &'static u32 { &CORGE } }
$ rustc -O --emit=obj --crate-type=rlib foo.rs
$ nm -C foo.o
0000000000000000 R foo::BAZ
0000000000000000 r foo::FOO
0000000000000000 R foo::QUUX
0000000000000000 T foo::quux
Атрибут no_mangle
Атрибут no_mangle может быть использован на любом элементе для отключения стандартного
искажения имен символов (name mangling). Символ для элемента будет идентификатором
имени элемента.
Дополнительно, элемент будет публично экспортирован из произведенной библиотеки или
объектного файла, аналогично атрибуту used.
Этот атрибут небезопасен, так как немангированный символ может столкнуться с другим символом с тем же именем (или с хорошо известным символом), приводя к неопределенному поведению.
#![allow(unused)] fn main() { #[unsafe(no_mangle)] extern "C" fn foo() {} }
2024 Edition differences
До редакции 2024 года разрешалось использовать атрибут
no_mangleбез квалификацииunsafe.
Атрибут link_section
Атрибут link_section определяет секцию объектного файла, в которую будет помещено
содержимое функции или статической переменной.
Атрибут link_section использует синтаксис MetaNameValueStr для указания имени секции.
#![allow(unused)] fn main() { #[unsafe(no_mangle)] #[unsafe(link_section = ".example_section")] pub static VAR1: u32 = 1; }
Этот атрибут небезопасен, так как позволяет пользователям помещать данные и код в секции памяти, не ожидающие их, такие как изменяемые данные в области только для чтения.
2024 Edition differences
До редакции 2024 года разрешалось использовать атрибут
link_sectionбез квалификацииunsafe.
Атрибут export_name
Атрибут export_name определяет имя символа, который будет
экспортирован для функции или статической переменной.
Атрибут export_name использует синтаксис MetaNameValueStr для указания имени символа.
#![allow(unused)] fn main() { #[unsafe(export_name = "exported_symbol_name")] pub fn name_in_rust() { } }
Этот атрибут небезопасен, так как символ с пользовательским именем может столкнуться с другим символом с тем же именем (или с хорошо известным символом), приводя к неопределенному поведению.
2024 Edition differences
До редакции 2024 года разрешалось использовать атрибут
export_nameбез квалификацииunsafe.
Среда выполнения Rust (The Rust runtime)
Этот раздел документирует особенности, которые определяют некоторые аспекты среды выполнения Rust.
Атрибут global_allocator
Атрибут global_allocator attributes выбирает распределитель памяти.
Example
#![allow(unused)] fn main() { use core::alloc::{GlobalAlloc, Layout}; use std::alloc::System; struct MyAllocator; unsafe impl GlobalAlloc for MyAllocator { unsafe fn alloc(&self, layout: Layout) -> *mut u8 { unsafe { System.alloc(layout) } } unsafe fn dealloc(&self, ptr: *mut u8, layout: Layout) { unsafe { System.dealloc(ptr, layout) } } } #[global_allocator] static GLOBAL: MyAllocator = MyAllocator; }
Атрибут global_allocator использует синтаксис MetaWord.
Атрибут global_allocator может быть применен только к статическому элементу, тип которого реализует трейт GlobalAlloc.
Атрибут global_allocator может быть использован только один раз на элементе.
Атрибут global_allocator может быть использован только один раз в графе крейтов.
Атрибут global_allocator экспортируется из прелюдии стандартной библиотеки.
Атрибут windows_subsystem
Атрибут windows_subsystem attributes устанавливает подсистему при линковке на цели Windows.
Example
#![allow(unused)] #![windows_subsystem = "windows"] fn main() { }
Атрибут windows_subsystem использует синтаксис MetaNameValueStr. Принимаемые значения: "console" и "windows".
Атрибут windows_subsystem может быть применен только к корню крейта.
Только первое использование windows_subsystem имеет эффект.
Note
rustcвыдает предупреждения против любого использования после первого. Это может стать ошибкой в будущем.
Атрибут windows_subsystem игнорируется на не-Windows целях и не-bin типах крейтов.
Подсистема "console" является подсистемой по умолчанию. Если консольный процесс запускается из существующей консоли, то он будет присоединен к этой консоли; в противном случае будет создано новое консольное окно.
Подсистема "windows" будет запущена отдельно от любой существующей консоли.
Note
Подсистема
"windows"обычно используется GUI приложениями, которые не хотят отображать консольное окно при запуске.
Приложения
Сводная таблица
Ниже приведено краткое изложение правил порождения грамматики. Подробности синтаксиса этой грамматики см. в notation.grammar.syntax.
Lexer summary
Lexer
CHAR → <a Unicode scalar value>
NUL → U+0000
IDENTIFIER_OR_KEYWORD → ( XID_Start | _ ) XID_Continue*
XID_Start → <XID_Start определено в Unicode>
XID_Continue → <XID_Continue определено в Unicode>
RAW_IDENTIFIER → r# IDENTIFIER_OR_KEYWORD
NON_KEYWORD_IDENTIFIER → IDENTIFIER_OR_KEYWORDкроме строгих или зарезервированных ключевых слов
IDENTIFIER → NON_KEYWORD_IDENTIFIER | RAW_IDENTIFIER
RESERVED_RAW_IDENTIFIER → r# ( _ | crate | self | Self | super )
LINE_COMMENT →
// ( ~[/ ! LF] | // ) ~LF*
| //
BLOCK_COMMENT →
/*
( ~[* !] | ** | BLOCK_COMMENT_OR_DOC )
( BLOCK_COMMENT_OR_DOC | ~*/ )*
*/
| /**/
| /***/
INNER_LINE_DOC →
//! ~[LF CR]*
INNER_BLOCK_DOC →
/*! ( BLOCK_COMMENT_OR_DOC | ~[*/ CR] )* */
OUTER_LINE_DOC →
/// ( ~/ ~[LF CR]* )?
OUTER_BLOCK_DOC →
/**
( ~* | BLOCK_COMMENT_OR_DOC )
( BLOCK_COMMENT_OR_DOC | ~[*/ CR] )*
*/
BLOCK_COMMENT_OR_DOC →
BLOCK_COMMENT
| OUTER_BLOCK_DOC
| INNER_BLOCK_DOC
WHITESPACE →
U+0009 // Горизонтальная табуляция, '\t'
| U+000A // Перевод строки (Line feed), '\n'
| U+000B // Вертикальная табуляция
| U+000C // Прогон страницы (Form feed)
| U+000D // Возврат каретки (Carriage return), '\r'
| U+0020 // Пробел, ' '
| U+0085 // Следующая строка (Next line)
| U+200E // Слева направо (Left-to-right mark)
| U+200F // Справа налево (Right-to-left mark)
| U+2028 // Разделитель строк (Line separator)
| U+2029 // Разделитель абзацев (Paragraph separator)
TAB → U+0009 // Горизонтальная табуляция, '\t'
LF → U+000A // Перевод строки (Line feed), '\n'
CR → U+000D // Возврат каретки (Carriage return), '\r'
Token →
RESERVED_TOKEN
| RAW_IDENTIFIER
| CHAR_LITERAL
| STRING_LITERAL
| RAW_STRING_LITERAL
| BYTE_LITERAL
| BYTE_STRING_LITERAL
| RAW_BYTE_STRING_LITERAL
| C_STRING_LITERAL
| RAW_C_STRING_LITERAL
| INTEGER_LITERAL
| FLOAT_LITERAL
| LIFETIME_TOKEN
| PUNCTUATION
| IDENTIFIER_OR_KEYWORD
SUFFIX → IDENTIFIER_OR_KEYWORDкроме _
SUFFIX_NO_E → SUFFIXне начинающийся с e или E
CHAR_LITERAL →
'
( ~[' \ LF CR TAB] | QUOTE_ESCAPE | ASCII_ESCAPE | UNICODE_ESCAPE )
' SUFFIX?
QUOTE_ESCAPE → \' | \"
ASCII_ESCAPE →
\x OCT_DIGIT HEX_DIGIT
| \n | \r | \t | \\ | \0
UNICODE_ESCAPE →
\u{ ( HEX_DIGIT _* )1..6 }
STRING_LITERAL →
" (
~[" \ CR]
| QUOTE_ESCAPE
| ASCII_ESCAPE
| UNICODE_ESCAPE
| STRING_CONTINUE
)* " SUFFIX?
STRING_CONTINUE → \ LF
RAW_STRING_LITERAL → r RAW_STRING_CONTENT SUFFIX?
RAW_STRING_CONTENT →
" ( ~CR )* (non-greedy) "
| # RAW_STRING_CONTENT #
BYTE_LITERAL →
b' ( ASCII_FOR_CHAR | BYTE_ESCAPE ) ' SUFFIX?
ASCII_FOR_CHAR →
<любой ASCII (т.е. 0x00 до 0x7F) кроме ', \, LF, CR или TAB>
BYTE_ESCAPE →
\x HEX_DIGIT HEX_DIGIT
| \n | \r | \t | \\ | \0 | \' | \"
BYTE_STRING_LITERAL →
b" ( ASCII_FOR_STRING | BYTE_ESCAPE | STRING_CONTINUE )* " SUFFIX?
ASCII_FOR_STRING →
<любой ASCII (т.е 0x00 до 0x7F) кроме ", \ или CR>
RAW_BYTE_STRING_LITERAL →
br RAW_BYTE_STRING_CONTENT SUFFIX?
RAW_BYTE_STRING_CONTENT →
" ASCII_FOR_RAW* (non-greedy) "
| # RAW_BYTE_STRING_CONTENT #
ASCII_FOR_RAW →
<любой ASCII (т.е. 0x00 до 0x7F) кроме CR>
C_STRING_LITERAL →
c" (
~[" \ CR NUL]
| BYTE_ESCAPEкроме \0 или \x00
| UNICODE_ESCAPEкроме \u{0}, \u{00}, …, \u{000000}
| STRING_CONTINUE
)* " SUFFIX?
RAW_C_STRING_LITERAL →
cr RAW_C_STRING_CONTENT SUFFIX?
RAW_C_STRING_CONTENT →
" ( ~[CR NUL] )* (non-greedy) "
| # RAW_C_STRING_CONTENT #
INTEGER_LITERAL →
( DEC_LITERAL | BIN_LITERAL | OCT_LITERAL | HEX_LITERAL ) SUFFIX_NO_E?
DEC_LITERAL → DEC_DIGIT ( DEC_DIGIT | _ )*
BIN_LITERAL → 0b ( BIN_DIGIT | _ )* BIN_DIGIT ( BIN_DIGIT | _ )*
OCT_LITERAL → 0o ( OCT_DIGIT | _ )* OCT_DIGIT ( OCT_DIGIT | _ )*
HEX_LITERAL → 0x ( HEX_DIGIT | _ )* HEX_DIGIT ( HEX_DIGIT | _ )*
BIN_DIGIT → [0-1]
OCT_DIGIT → [0-7]
DEC_DIGIT → [0-9]
HEX_DIGIT → [0-9 a-f A-F]
TUPLE_INDEX → DEC_LITERAL | BIN_LITERAL | OCT_LITERAL | HEX_LITERAL
FLOAT_LITERAL →
DEC_LITERAL .not immediately followed by ., _ or an XID_Start character
| DEC_LITERAL . DEC_LITERAL SUFFIX_NO_E?
| DEC_LITERAL ( . DEC_LITERAL )? FLOAT_EXPONENT SUFFIX?
FLOAT_EXPONENT →
( e | E ) ( + | - )? ( DEC_DIGIT | _ )* DEC_DIGIT ( DEC_DIGIT | _ )*
RESERVED_NUMBER →
BIN_LITERAL [2-9]
| OCT_LITERAL [8-9]
| ( BIN_LITERAL | OCT_LITERAL | HEX_LITERAL ) .not immediately followed by ., _ or an XID_Start character
| ( BIN_LITERAL | OCT_LITERAL ) ( e | E )
| 0b _* <end of input or not BIN_DIGIT>
| 0o _* <end of input or not OCT_DIGIT>
| 0x _* <end of input or not HEX_DIGIT>
| DEC_LITERAL ( . DEC_LITERAL )? ( e | E ) ( + | - )? <end of input or not DEC_DIGIT>
LIFETIME_TOKEN →
' IDENTIFIER_OR_KEYWORDnot immediately followed by '
| RAW_LIFETIME
LIFETIME_OR_LABEL →
' NON_KEYWORD_IDENTIFIERnot immediately followed by '
| RAW_LIFETIME
RAW_LIFETIME →
'r# IDENTIFIER_OR_KEYWORDnot immediately followed by '
RESERVED_RAW_LIFETIME → 'r# ( _ | crate | self | Self | super )not immediately followed by '
PUNCTUATION →
=
| <
| <=
| ==
| !=
| >=
| >
| &&
| ||
| !
| ~
| +
| -
| *
| /
| %
| ^
| &
| |
| <<
| >>
| +=
| -=
| *=
| /=
| %=
| ^=
| &=
| |=
| <<=
| >>=
| @
| .
| ..
| ...
| ..=
| ,
| ;
| :
| ::
| ->
| <-
| =>
| #
| $
| ?
| {
| }
| [
| ]
| (
| )
RESERVED_TOKEN →
RESERVED_GUARDED_STRING_LITERAL
| RESERVED_NUMBER
| RESERVED_POUNDS
| RESERVED_RAW_IDENTIFIER
| RESERVED_RAW_LIFETIME
| RESERVED_TOKEN_DOUBLE_QUOTE
| RESERVED_TOKEN_LIFETIME
| RESERVED_TOKEN_POUND
| RESERVED_TOKEN_SINGLE_QUOTE
RESERVED_TOKEN_DOUBLE_QUOTE →
IDENTIFIER_OR_KEYWORDкроме b или c или r или br или cr "
RESERVED_TOKEN_SINGLE_QUOTE →
IDENTIFIER_OR_KEYWORDкроме b '
RESERVED_TOKEN_POUND →
IDENTIFIER_OR_KEYWORDкроме r или br или cr #
RESERVED_TOKEN_LIFETIME →
' IDENTIFIER_OR_KEYWORDкроме r #
RESERVED_GUARDED_STRING_LITERAL → #+ STRING_LITERAL
RESERVED_POUNDS → #2..
Macros summary
Syntax
MacroInvocation →
SimplePath ! DelimTokenTree
DelimTokenTree →
( TokenTree* )
| [ TokenTree* ]
| { TokenTree* }
TokenTree →
Tokenкроме разделителей | DelimTokenTree
MacroInvocationSemi →
SimplePath ! ( TokenTree* ) ;
| SimplePath ! [ TokenTree* ] ;
| SimplePath ! { TokenTree* }
MacroRulesDefinition →
macro_rules ! IDENTIFIER MacroRulesDef
MacroRulesDef →
( MacroRules ) ;
| [ MacroRules ] ;
| { MacroRules }
MacroRules →
MacroRule ( ; MacroRule )* ;?
MacroRule →
MacroMatcher => MacroTranscriber
MacroMatcher →
( MacroMatch* )
| [ MacroMatch* ]
| { MacroMatch* }
MacroMatch →
Tokenкроме $ и разделителей
| MacroMatcher
| $ ( IDENTIFIER_OR_KEYWORDкроме crate | RAW_IDENTIFIER ) : MacroFragSpec
| $ ( MacroMatch+ ) MacroRepSep? MacroRepOp
MacroFragSpec →
block | expr | expr_2021 | ident | item | lifetime | literal
| meta | pat | pat_param | path | stmt | tt | ty | vis
MacroRepSep → Tokenкроме разделителей и MacroRepOp
MacroRepOp → * | + | ?
Attributes summary
Syntax
ProcMacroDeriveAttribute →
proc_macro_derive ( DeriveMacroName ( , DeriveMacroAttributes )? ,? )
DeriveMacroAttributes →
attributes ( ( IDENTIFIER ( , IDENTIFIER )* ,? )? )
InnerAttribute → # ! [ Attr ]
OuterAttribute → # [ Attr ]
Attr →
SimplePath AttrInput?
| unsafe ( SimplePath AttrInput? )
AttrInput →
DelimTokenTree
| = Expression
MetaItem →
SimplePath
| SimplePath = Expression
| SimplePath ( MetaSeq? )
MetaSeq →
MetaItemInner ( , MetaItemInner )* ,?
MetaItemInner →
MetaItem
| Expression
MetaNameValueStr →
IDENTIFIER = ( STRING_LITERAL | RAW_STRING_LITERAL )
MetaListPaths →
IDENTIFIER ( ( SimplePath ( , SimplePath )* ,? )? )
MetaListIdents →
IDENTIFIER ( ( IDENTIFIER ( , IDENTIFIER )* ,? )? )
MetaListNameValueStr →
IDENTIFIER ( ( MetaNameValueStr ( , MetaNameValueStr )* ,? )? )
InlineAttribute →
inline ( always )
| inline ( never )
| inline
CollapseDebuginfoAttribute → collapse_debuginfo ( CollapseDebuginfoOption )
CollapseDebuginfoOption →
yes
| no
| external
Items summary
Syntax
Crate →
InnerAttribute*
Item*
Item →
OuterAttribute* ( VisItem | MacroItem )
VisItem →
Visibility?
(
Module
| ExternCrate
| UseDeclaration
| Function
| TypeAlias
| Struct
| Enumeration
| Union
| ConstantItem
| StaticItem
| Trait
| Implementation
| ExternBlock
)
MacroItem →
MacroInvocationSemi
| MacroRulesDefinition
Module →
unsafe? mod IDENTIFIER ;
| unsafe? mod IDENTIFIER {
InnerAttribute*
Item*
}
ExternCrate → extern crate CrateRef AsClause? ;
CrateRef → IDENTIFIER | self
AsClause → as ( IDENTIFIER | _ )
UseDeclaration → use UseTree ;
UseTree →
( SimplePath? :: )? *
| ( SimplePath? :: )? { ( UseTree ( , UseTree )* ,? )? }
| SimplePath ( as ( IDENTIFIER | _ ) )?
Function →
FunctionQualifiers fn IDENTIFIER GenericParams?
( FunctionParameters? )
FunctionReturnType? WhereClause?
( BlockExpression | ; )
FunctionQualifiers → const? async? ItemSafety? ( extern Abi? )?
ItemSafety → safe | unsafe
Abi → STRING_LITERAL | RAW_STRING_LITERAL
FunctionParameters →
SelfParam ,?
| ( SelfParam , )? FunctionParam ( , FunctionParam )* ,?
SelfParam → OuterAttribute* ( ShorthandSelf | TypedSelf )
ShorthandSelf → ( & | & Lifetime )? mut? self
FunctionParam → OuterAttribute* ( FunctionParamPattern | ... | Type )
FunctionParamPattern → PatternNoTopAlt : ( Type | ... )
FunctionReturnType → -> Type
TypeAlias →
type IDENTIFIER GenericParams? ( : TypeParamBounds )?
WhereClause?
( = Type WhereClause? )? ;
Struct →
StructStruct
| TupleStruct
StructStruct →
struct IDENTIFIER GenericParams? WhereClause? ( { StructFields? } | ; )
TupleStruct →
struct IDENTIFIER GenericParams? ( TupleFields? ) WhereClause? ;
StructFields → StructField ( , StructField )* ,?
StructField → OuterAttribute* Visibility? IDENTIFIER : Type
TupleFields → TupleField ( , TupleField )* ,?
TupleField → OuterAttribute* Visibility? Type
Union →
union IDENTIFIER GenericParams? WhereClause? { StructFields? }
ConstantItem →
const ( IDENTIFIER | _ ) : Type ( = Expression )? ;
StaticItem →
ItemSafety? static mut? IDENTIFIER : Type ( = Expression )? ;
Trait →
unsafe? trait IDENTIFIER GenericParams? ( : TypeParamBounds? )? WhereClause?
{
InnerAttribute*
AssociatedItem*
}
Implementation → InherentImpl | TraitImpl
InherentImpl →
impl GenericParams? Type WhereClause? {
InnerAttribute*
AssociatedItem*
}
TraitImpl →
unsafe? impl GenericParams? !? TypePath for Type
WhereClause?
{
InnerAttribute*
AssociatedItem*
}
ExternBlock →
unsafe? extern Abi? {
InnerAttribute*
ExternalItem*
}
ExternalItem →
OuterAttribute* (
MacroInvocationSemi
| Visibility? StaticItem
| Visibility? Function
)
GenericParams → < ( GenericParam ( , GenericParam )* ,? )? >
GenericParam → OuterAttribute* ( LifetimeParam | TypeParam | ConstParam )
LifetimeParam → Lifetime ( : LifetimeBounds )?
TypeParam → IDENTIFIER ( : TypeParamBounds? )? ( = Type )?
ConstParam →
const IDENTIFIER : Type
( = ( BlockExpression | IDENTIFIER | -? LiteralExpression ) )?
WhereClause → where ( WhereClauseItem , )* WhereClauseItem?
WhereClauseItem →
LifetimeWhereClauseItem
| TypeBoundWhereClauseItem
LifetimeWhereClauseItem → Lifetime : LifetimeBounds
TypeBoundWhereClauseItem → ForLifetimes? Type : TypeParamBounds?
AssociatedItem →
OuterAttribute* (
MacroInvocationSemi
| ( Visibility? ( TypeAlias | ConstantItem | Function ) )
)
Visibility →
pub
| pub ( crate )
| pub ( self )
| pub ( super )
| pub ( in SimplePath )
Configuration summary
Syntax
ConfigurationPredicate →
ConfigurationOption
| ConfigurationAll
| ConfigurationAny
| ConfigurationNot
| true
| false
ConfigurationOption →
IDENTIFIER ( = ( STRING_LITERAL | RAW_STRING_LITERAL ) )?
ConfigurationAll →
all ( ConfigurationPredicateList? )
ConfigurationAny →
any ( ConfigurationPredicateList? )
ConfigurationNot →
not ( ConfigurationPredicate )
ConfigurationPredicateList →
ConfigurationPredicate ( , ConfigurationPredicate )* ,?
CfgAttribute → cfg ( ConfigurationPredicate )
CfgAttrAttribute → cfg_attr ( ConfigurationPredicate , CfgAttrs? )
Itemsвызовного выражения summary
Syntax
Enumeration →
enum IDENTIFIER GenericParams? WhereClause? { EnumVariants? }
EnumVariants → EnumVariant ( , EnumVariant )* ,?
EnumVariant →
OuterAttribute* Visibility?
IDENTIFIER ( EnumVariantTuple | EnumVariantStruct )? EnumVariantDiscriminant?
EnumVariantTuple → ( TupleFields? )
EnumVariantStruct → { StructFields? }
Statements summary
Syntax
Statement →
;
| Item
| LetStatement
| ExpressionStatement
| OuterAttribute* MacroInvocationSemi
LetStatement →
OuterAttribute* let PatternNoTopAlt ( : Type )?
(
= Expression
| = Expressionexcept LazyBooleanExpression or end with a } else BlockExpression
)? ;
ExpressionStatement →
ExpressionWithoutBlock ;
| ExpressionWithBlock ;?
Expressions summary
Syntax
Expression →
ExpressionWithoutBlock
| ExpressionWithBlock
ExpressionWithoutBlock →
OuterAttribute*
(
LiteralExpression
| PathExpression
| OperatorExpression
| GroupedExpression
| ArrayExpression
| AwaitExpression
| IndexExpression
| TupleExpression
| TupleIndexingExpression
| StructExpression
| CallExpression
| MethodCallExpression
| FieldExpression
| ClosureExpression
| AsyncBlockExpression
| ContinueExpression
| BreakExpression
| RangeExpression
| ReturnExpression
| UnderscoreExpression
| MacroInvocation
)
ExpressionWithBlock →
OuterAttribute*
(
BlockExpression
| ConstBlockExpression
| UnsafeBlockExpression
| LoopExpression
| IfExpression
| MatchExpression
)
LiteralExpression →
CHAR_LITERAL
| STRING_LITERAL
| RAW_STRING_LITERAL
| BYTE_LITERAL
| BYTE_STRING_LITERAL
| RAW_BYTE_STRING_LITERAL
| C_STRING_LITERAL
| RAW_C_STRING_LITERAL
| INTEGER_LITERAL
| FLOAT_LITERAL
| true
| false
PathExpression →
PathInExpression
| QualifiedPathInExpression
BlockExpression →
{
InnerAttribute*
Statements?
}
Statements →
Statement+
| Statement+ ExpressionWithoutBlock
| ExpressionWithoutBlock
AsyncBlockExpression → async move? BlockExpression
ConstBlockExpression → const BlockExpression
UnsafeBlockExpression → unsafe BlockExpression
OperatorExpression →
BorrowExpression
| DereferenceExpression
| TryPropagationExpression
| NegationExpression
| ArithmeticOrLogicalExpression
| ComparisonExpression
| LazyBooleanExpression
| TypeCastExpression
| AssignmentExpression
| CompoundAssignmentExpression
BorrowExpression →
( & | && ) Expression
| ( & | && ) mut Expression
| ( & | && ) raw const Expression
| ( & | && ) raw mut Expression
DereferenceExpression → * Expression
TryPropagationExpression → Expression ?
NegationExpression →
- Expression
| ! Expression
ArithmeticOrLogicalExpression →
Expression + Expression
| Expression - Expression
| Expression * Expression
| Expression / Expression
| Expression % Expression
| Expression & Expression
| Expression | Expression
| Expression ^ Expression
| Expression << Expression
| Expression >> Expression
ComparisonExpression →
Expression == Expression
| Expression != Expression
| Expression > Expression
| Expression < Expression
| Expression >= Expression
| Expression <= Expression
LazyBooleanExpression →
Expression || Expression
| Expression && Expression
TypeCastExpression → Expression as TypeNoBounds
AssignmentExpression → Expression = Expression
CompoundAssignmentExpression →
Expression += Expression
| Expression -= Expression
| Expression *= Expression
| Expression /= Expression
| Expression %= Expression
| Expression &= Expression
| Expression |= Expression
| Expression ^= Expression
| Expression <<= Expression
| Expression >>= Expression
GroupedExpression → ( Expression )
ArrayExpression → [ ArrayElements? ]
ArrayElements →
Expression ( , Expression )* ,?
| Expression ; Expression
IndexExpression → Expression [ Expression ]
TupleExpression → ( TupleElements? )
TupleElements → ( Expression , )+ Expression?
TupleIndexingExpression → Expression . TUPLE_INDEX
StructExpression →
PathInExpression { ( StructExprFields | StructBase )? }
StructExprFields →
StructExprField ( , StructExprField )* ( , StructBase | ,? )
StructExprField →
OuterAttribute*
(
IDENTIFIER
| ( IDENTIFIER | TUPLE_INDEX ) : Expression
)
StructBase → .. Expression
CallExpression → Expression ( CallParams? )
CallParams → Expression ( , Expression )* ,?
MethodCallExpression → Expression . PathExprSegment ( CallParams? )
FieldExpression → Expression . IDENTIFIER
ClosureExpression →
async?
move?
( || | | ClosureParameters? | )
( Expression | -> TypeNoBounds BlockExpression )
ClosureParameters → ClosureParam ( , ClosureParam )* ,?
ClosureParam → OuterAttribute* PatternNoTopAlt ( : Type )?
LoopExpression →
LoopLabel? (
InfiniteLoopExpression
| PredicateLoopExpression
| IteratorLoopExpression
| LabelBlockExpression
)
InfiniteLoopExpression → loop BlockExpression
PredicateLoopExpression → while Conditions BlockExpression
IteratorLoopExpression →
for Pattern in Expressionexcept StructExpression BlockExpression
LoopLabel → LIFETIME_OR_LABEL :
BreakExpression → break LIFETIME_OR_LABEL? Expression?
LabelBlockExpression → BlockExpression
ContinueExpression → continue LIFETIME_OR_LABEL?
RangeExpression →
RangeExpr
| RangeFromExpr
| RangeToExpr
| RangeFullExpr
| RangeInclusiveExpr
| RangeToInclusiveExpr
RangeExpr → Expression .. Expression
RangeFromExpr → Expression ..
RangeToExpr → .. Expression
RangeFullExpr → ..
RangeInclusiveExpr → Expression ..= Expression
RangeToInclusiveExpr → ..= Expression
IfExpression →
if Conditions BlockExpression
( else ( BlockExpression | IfExpression ) )?
Conditions →
Expressionexcept StructExpression
| LetChain
LetChain → LetChainCondition ( && LetChainCondition )*
LetChainCondition →
Expressionexcept ExcludedConditions
| OuterAttribute* let Pattern = Scrutineeexcept ExcludedConditions
ExcludedConditions →
StructExpression
| LazyBooleanExpression
| RangeExpr
| RangeFromExpr
| RangeInclusiveExpr
| AssignmentExpression
| CompoundAssignmentExpression
MatchExpression →
match Scrutinee {
InnerAttribute*
MatchArms?
}
Scrutinee → Expressionexcept StructExpression
MatchArms →
( MatchArm => ( ExpressionWithoutBlock , | ExpressionWithBlock ,? ) )*
MatchArm => Expression ,?
MatchArm → OuterAttribute* Pattern MatchArmGuard?
MatchArmGuard → if Expression
ReturnExpression → return Expression?
AwaitExpression → Expression . await
Patterns summary
Syntax
Pattern → |? PatternNoTopAlt ( | PatternNoTopAlt )*
PatternNoTopAlt →
PatternWithoutRange
| RangePattern
PatternWithoutRange →
LiteralPattern
| IdentifierPattern
| WildcardPattern
| RestPattern
| ReferencePattern
| StructPattern
| TupleStructPattern
| TuplePattern
| GroupedPattern
| SlicePattern
| PathPattern
| MacroInvocation
LiteralPattern → -? LiteralExpression
IdentifierPattern → ref? mut? IDENTIFIER ( @ PatternNoTopAlt )?
WildcardPattern → _
RestPattern → ..
RangePattern →
RangeExclusivePattern
| RangeInclusivePattern
| RangeFromPattern
| RangeToExclusivePattern
| RangeToInclusivePattern
| ObsoleteRangePattern
RangeExclusivePattern →
RangePatternBound .. RangePatternBound
RangeInclusivePattern →
RangePatternBound ..= RangePatternBound
RangeFromPattern →
RangePatternBound ..
RangeToExclusivePattern →
.. RangePatternBound
RangeToInclusivePattern →
..= RangePatternBound
ObsoleteRangePattern →
RangePatternBound ... RangePatternBound
RangePatternBound →
LiteralPattern
| PathExpression
ReferencePattern → ( & | && ) mut? PatternWithoutRange
StructPattern →
PathInExpression {
StructPatternElements?
}
StructPatternElements →
StructPatternFields ( , | , StructPatternEtCetera )?
| StructPatternEtCetera
StructPatternFields →
StructPatternField ( , StructPatternField )*
StructPatternField →
OuterAttribute*
(
TUPLE_INDEX : Pattern
| IDENTIFIER : Pattern
| ref? mut? IDENTIFIER
)
TupleStructPattern → PathInExpression ( TupleStructItems? )
TupleStructItems → Pattern ( , Pattern )* ,?
TuplePattern → ( TuplePatternItems? )
TuplePatternItems →
Pattern ,
| RestPattern
| Pattern ( , Pattern )+ ,?
GroupedPattern → ( Pattern )
SlicePattern → [ SlicePatternItems? ]
SlicePatternItems → Pattern ( , Pattern )* ,?
Types summary
Syntax
Type →
TypeNoBounds
| ImplTraitType
| TraitObjectType
TypeNoBounds →
ParenthesizedType
| ImplTraitTypeOneBound
| TraitObjectTypeOneBound
| TypePath
| TupleType
| NeverType
| RawPointerType
| ReferenceType
| ArrayType
| SliceType
| InferredType
| QualifiedPathInType
| BareFunctionType
| MacroInvocation
ParenthesizedType → ( Type )
NeverType → !
TupleType →
( )
| ( ( Type , )+ Type? )
ArrayType → [ Type ; Expression ]
ReferenceType → & Lifetime? mut? TypeNoBounds
RawPointerType → * ( mut | const ) TypeNoBounds
BareFunctionType →
ForLifetimes? FunctionTypeQualifiers fn
( FunctionParametersMaybeNamedVariadic? ) BareFunctionReturnType?
FunctionTypeQualifiers → unsafe? ( extern Abi? )?
BareFunctionReturnType → -> TypeNoBounds
FunctionParametersMaybeNamedVariadic →
MaybeNamedFunctionParameters | MaybeNamedFunctionParametersVariadic
MaybeNamedFunctionParameters →
MaybeNamedParam ( , MaybeNamedParam )* ,?
MaybeNamedParam →
OuterAttribute* ( ( IDENTIFIER | _ ) : )? Type
MaybeNamedFunctionParametersVariadic →
( MaybeNamedParam , )* MaybeNamedParam , OuterAttribute* ...
TraitObjectType → dyn? TypeParamBounds
TraitObjectTypeOneBound → dyn? TraitBound
ImplTraitType → impl TypeParamBounds
ImplTraitTypeOneBound → impl TraitBound
InferredType → _
Miscellaneous summary
Syntax
TypeParamBounds → TypeParamBound ( + TypeParamBound )* +?
TypeParamBound → Lifetime | TraitBound | UseBound
TraitBound →
( ? | ForLifetimes )? TypePath
| ( ( ? | ForLifetimes )? TypePath )
LifetimeBounds → ( Lifetime + )* Lifetime?
Lifetime →
LIFETIME_OR_LABEL
| 'static
| '_
UseBound → use UseBoundGenericArgs
UseBoundGenericArgs →
< >
| < ( UseBoundGenericArg , )* UseBoundGenericArg ,? >
UseBoundGenericArg →
Lifetime
| IDENTIFIER
| Self
ForLifetimes → for GenericParams
Paths summary
Syntax
SimplePath →
::? SimplePathSegment ( :: SimplePathSegment )*
SimplePathSegment →
IDENTIFIER | super | self | crate | $crate
PathInExpression →
::? PathExprSegment ( :: PathExprSegment )*
PathExprSegment →
PathIdentSegment ( :: GenericArgs )?
PathIdentSegment →
IDENTIFIER | super | self | Self | crate | $crate
GenericArgs →
< >
| < ( GenericArg , )* GenericArg ,? >
GenericArg →
Lifetime | Type | GenericArgsConst | GenericArgsBinding | GenericArgsBounds
GenericArgsConst →
BlockExpression
| LiteralExpression
| - LiteralExpression
| SimplePathSegment
GenericArgsBinding →
IDENTIFIER GenericArgs? = Type
GenericArgsBounds →
IDENTIFIER GenericArgs? : TypeParamBounds
QualifiedPathInExpression → QualifiedPathType ( :: PathExprSegment )+
QualifiedPathType → < Type ( as TypePath )? >
QualifiedPathInType → QualifiedPathType ( :: TypePathSegment )+
TypePath → ::? TypePathSegment ( :: TypePathSegment )*
TypePathSegment → PathIdentSegment ( ::? ( GenericArgs | TypePathFn ) )?
TypePathFn → ( TypePathFnInputs? ) ( -> TypeNoBounds )?
TypePathFnInputs → Type ( , Type )* ,?
Assembly summary
Syntax
AsmArgs → FormatString ( , FormatString )* ( , AsmOperand )* ,?
FormatString → STRING_LITERAL | RAW_STRING_LITERAL | MacroInvocation
AsmOperand →
ClobberAbi
| AsmOptions
| RegOperand
ClobberAbi → clobber_abi ( Abi ( , Abi )* ,? )
AsmOptions →
options ( ( AsmOption ( , AsmOption )* ,? )? )
AsmOption →
pure
| nomem
| readonly
| preserves_flags
| noreturn
| nostack
| att_syntax
| raw
RegOperand → ( ParamName = )?
(
DirSpec ( RegSpec ) Expression
| DualDirSpec ( RegSpec ) DualDirSpecExpression
| sym PathExpression
| const Expression
| label { Statements? }
)
ParamName → IDENTIFIER_OR_KEYWORD | RAW_IDENTIFIER
DualDirSpecExpression →
Expression
| Expression => Expression
RegSpec → RegisterClass | ExplicitRegister
RegisterClass → IDENTIFIER_OR_KEYWORD
ExplicitRegister → STRING_LITERAL
DirSpec →
in
| out
| lateout
DualDirSpec →
inout
| inlateout
Syntax index
This appendix provides an index of tokens and common forms with links to where those elements are defined.
Keywords
Operators and punctuation
Comments
| Comment | Use |
|---|---|
// | line comment |
//! | inner line comment |
/// | outer line doc comment |
/*…*/ | block comment |
/*!…*/ | inner block doc comment |
/**…*/ | outer block doc comment |
Other tokens
| Token | Use |
|---|---|
ident | identifiers |
r#ident | raw identifiers |
'ident | lifetimes and loop labels |
'r#ident | raw lifetimes and loop labels |
…u8, …i32, …f64, …usize, … | number literals |
"…" | string literals |
r"…", r#"…"#, r##"…"##, … | raw string literals |
b"…" | byte string literals |
br"…", br#"…"#, br##"…"##, … | raw byte string literals |
'…' | character literals |
b'…' | byte literals |
c"…" | C string literals |
cr"…", cr#"…"#, cr##"…"##, … | raw C string literals |
Macros
| Syntax | Use |
|---|---|
ident!(…)ident! {…}ident![…] | macro invocations |
$ident | macro metavariable |
$ident:kind | macro matcher fragment specifier |
$(…)… | macro repetition |
Attributes
| Syntax | Use |
|---|---|
#[meta] | outer attribute |
#![meta] | inner attribute |
Expressions
Items
Items are the components of a crate.
| Item | Use |
|---|---|
mod ident;mod ident {…} | modules |
use path; | use declarations |
fn ident(…) {…} | functions |
type Type = Type; | type aliases |
struct ident {…} | structs |
enum ident {…} | enumerations |
union ident {…} | unions |
trait ident {…} | traits |
impl Type {…}impl Type for Trait {…} | implementations |
const ident = expr; | constant items |
static ident = expr; | static items |
extern "C" {…} | external blocks |
fn ident<…>(…) …struct ident<…> {…}enum ident<…> {…}impl<…> Type<…> {…} | generic definitions |
Type expressions
Type expressions are used to refer to types.
| Type | Use |
|---|---|
bool, u8, f64, str, … | primitive types |
for<…> | higher-ranked trait bounds |
T: TraitA + TraitB | trait bounds |
T: 'a + 'b | lifetime bounds |
T: TraitA + 'a | trait and lifetime bounds |
T: ?Sized | relaxed trait bounds |
[Type; len] | array types |
(Type, …) | tuple types |
[Type] | slice types |
(Type) | parenthesized types |
impl Trait | impl trait types, anonymous type parameters |
dyn Trait | trait object types |
identident::… | type paths (can refer to structs, enumerations, unions, type aliases, traits, generics, etc.) |
Type<…>Trait<…> | generic arguments (e.g. Vec<u8>) |
Trait<ident = Type> | associated type bindings (e.g. Iterator<Item = T>) |
Trait<ident: …> | associated type bounds (e.g. Iterator<Item: Send>) |
&Type&mut Type | reference types |
*mut Type*const Type | raw pointer types |
fn(…) -> Type | function pointer types |
_ | inferred type, inferred const |
'_ | placeholder lifetime |
! | never type |
Patterns
Patterns are used to match values.
| Pattern | Use |
|---|---|
"foo", 'a', 123, 2.4, … | literal patterns |
ident | identifier patterns |
_ | wildcard pattern |
.. | rest pattern |
a.., ..b, a..b, a..=b, ..=b | range patterns |
&pattern&mut pattern | reference patterns |
path {…} | struct patterns |
path(…) | tuple struct patterns |
(pattern, …) | tuple patterns |
(pattern) | grouped patterns |
[pattern, …] | slice patterns |
CONST, Enum::Variant, … | path patterns |
Приложение: Формальная спецификация неоднозначности множества следования макросов
Эта страница документирует формальную спецификацию правил следования для Макросов по примеру. Они были первоначально определены в RFC 550, из которого скопирована основная часть этого текста, и расширены в последующих RFC.
rmacro.ambiguity.convention: https://github.com/rust-lang/rust/issues/56575
Определения и соглашения
macro: всё, что может быть вызвано какfoo!(...)в исходном коде.MBE: макрос по примеру (macro-by-example), макрос, определенный с помощьюmacro_rules.matcher: левая часть правила в вызовеmacro_rulesили её подчасть.macro parser: часть кода в парсере Rust, которая будет разбирать ввод с помощью грамматики, производной от всех сопоставителей.fragment: класс синтаксиса Rust, который данный сопоставитель будет принимать (или “сопоставлять”).repetition: фрагмент, следующий регулярному повторяющемуся шаблону.NT: нетерминал, различные “метапеременные” или сопоставители повторов, которые могут появляться в сопоставителе, указанные в синтаксисе MBE с ведущим символом$.simple NT: “метапеременный” нетерминал (дальнейшее обсуждение ниже).complex NT: сопоставляющий повтор нетерминал, заданный с помощью операторов повтора (*,+,?).token: атомарный элемент сопоставителя; т.е. идентификаторы, операторы, открывающие/закрывающие разделители, и простые NT.token tree: древовидная структура, образованная из токенов (листья), сложных NT и конечных последовательностей деревьев токенов.delimiter token: токен, предназначенный для разделения конца одного фрагмента и начала следующего.separator token: опциональный токен-разделитель в сложном NT, который разделяет каждую пару элементов в сопоставленном повторе.separated complex NT: сложный NT, который имеет свой собственный токен-сепаратор.delimited sequence: последовательность деревьев токенов с соответствующими открывающими и закрывающими разделителями в начале и конце последовательности.empty fragment: класс невидимого синтаксиса Rust, который разделяет токены, т.е. пробелы или (в некоторых лексических контекстах) пустая последовательность токенов.fragment specifier: идентификатор в простом NT, который указывает, какой фрагмент принимает NT.language: контекстно-свободный язык.
Пример:
#![allow(unused)] fn main() { macro_rules! i_am_an_mbe { (start $foo:expr $($i:ident),* end) => ($foo) } }
(start $foo:expr $($i:ident),* end) - это сопоставитель. Весь сопоставитель является разделенной последовательностью (с открывающим и закрывающим разделителями ( и )), а $foo и $i являются простыми NT с expr и ident в качестве соответствующих спецификаторов фрагментов.
$(i:ident),* также является NT; это сложный NT, который сопоставляет разделенный запятыми повтор идентификаторов. , - это токен-сепаратор для сложного NT; он встречается между каждой парой элементов (если есть) сопоставленного фрагмента.
Другой пример сложного NT - $(hi $e:expr ;)+, который сопоставляет любой фрагмент вида hi <expr>; hi <expr>; ..., где hi <expr>; встречается хотя бы один раз. Обратите внимание, что этот сложный NT не имеет выделенного токена-сепаратора.
(Обратите внимание, что парсер Rust гарантирует, что разделенные последовательности всегда встречаются с правильной вложенностью структуры деревьев токенов и корректным соответствием открывающих и закрывающих разделителей.)
Мы будем использовать переменную “M” для обозначения сопоставителя, переменные “t” и “u” для произвольных отдельных токенов и переменные “tt” и “uu” для произвольных деревьев токенов. (Использование “tt” представляет потенциальную неоднозначность с его дополнительной ролью как спецификатора фрагмента; но из контекста будет ясно, какая интерпретация имеется в виду.)
“SEP” будет обозначать токены-сепараторы, “OP” - операторы повтора *, + и ?, “OPEN”/“CLOSE” - соответствующие пары токенов, окружающие разделенную последовательность (например, [ и ]).
Греческие буквы “α” “β” “γ” “δ” обозначают потенциально пустые последовательности деревьев токенов. (Однако греческая буква “ε” (эпсилон) играет особую роль в изложении и не обозначает последовательность деревьев токенов.)
- Это соглашение с греческими буквами обычно используется, когда наличие последовательности является технической деталью; в частности, когда мы хотим подчеркнуть, что мы работаем с последовательностью деревьев токенов, мы будем использовать обозначение “tt …” для последовательности, а не греческую букву.
Обратите внимание, что сопоставитель - это просто дерево токенов. “Простой NT”, как упомянуто выше, является метапеременным NT; таким образом, это не повтор. Например, $foo:ty - это простой NT, но $($foo:ty)+ - сложный NT.
Также обратите внимание, что в контексте этого формализма термин “токен” обычно включает простые NT.
Наконец, полезно иметь в виду, что согласно определениям этого формализма, ни один простой NT не сопоставляется с пустым фрагментом, и аналогично ни один токен не сопоставляется с пустым фрагментом синтаксиса Rust. (Таким образом, единственный NT, который может сопоставляться с пустым фрагментом, - это сложный NT.) На самом деле это не так, потому что сопоставитель vis может сопоставляться с пустым фрагментом. Таким образом, для целей формализма мы будем рассматривать $v:vis как фактически $($v:vis)? с требованием, чтобы сопоставитель соответствовал пустому фрагменту.
Инварианты сопоставителя
Чтобы быть действительным, сопоставитель должен удовлетворять следующим трем инвариантам. Определения FIRST и FOLLOW описаны позже.
- Для любых двух последовательных последовательностей деревьев токенов в сопоставителе
M(т.е.M = ... tt uu ...) с непустымuu ...мы должны иметь FOLLOW(... tt) ∪ {ε} ⊇ FIRST(uu ...). - Для любого разделяемого сложного NT в сопоставителе,
M = ... $(tt ...) SEP OP ..., мы должны иметьSEP∈ FOLLOW(tt ...). - Для неразделяемого сложного NT в сопоставителе,
M = ... $(tt ...) OP ..., если OP =*или+, мы должны иметь FOLLOW(tt ...) ⊇ FIRST(tt ...).
Первый инвариант говорит, что любой фактический токен, который идет после сопоставителя, если таковой имеется, должен быть somewhere в predetermined множестве следования. Это гарантирует, что legal определение макроса будет продолжать присваивать то же определение тому, где заканчивается ... tt и начинается uu ..., даже при добавлении новых синтаксических форм в язык.
Второй инвариант говорит, что разделяемый сложный NT должен использовать токен-сепаратор, который является частью predetermined множества следования для внутреннего содержимого NT. Это гарантирует, что legal определение макроса будет продолжать разбирать входной фрагмент в ту же разделенную последовательность tt ..., даже при добавлении новых синтаксических форм в язык.
Третий инвариант говорит, что когда у нас есть сложный NT, который может сопоставлять две или более копии одной и той же вещи без разделения между ними, должно быть допустимо размещать их рядом друг с другом в соответствии с первым инвариантом. Этот инвариант также требует, чтобы они были непустыми, что устраняет возможную неоднозначность.
ПРИМЕЧАНИЕ: Третий инвариант в настоящее время не применяется из-за исторического упущения и значительной зависимости от поведения. В настоящее время не решено, что делать с этим в будущем. Макросы, которые не соблюдают поведение, могут стать недействительными в будущей редакции Rust. См. отслеживаемую проблему.
FIRST и FOLLOW, неформально
Данный сопоставитель M отображается в три множества: FIRST(M), LAST(M) и FOLLOW(M).
Каждое из трех множеств состоит из токенов. FIRST(M) и LAST(M) также могут содержать выделенный нетокенный элемент ε (“эпсилон”), который указывает, что M может сопоставляться с пустым фрагментом. (Но FOLLOW(M) всегда просто множество токенов.)
Неформально:
- FIRST(M): собирает токены, потенциально используемые первыми при сопоставлении фрагмента с M.
- LAST(M): собирает токены, потенциально используемые последними при сопоставлении фрагмента с M.
-
FOLLOW(M): множество токенов, разрешенных следовать immediately после некоторого фрагмента, сопоставленного M.
Другими словами: t ∈ FOLLOW(M) если и только если существует (потенциально пустая) последовательность токенов α, β, γ, δ, где:
-
M сопоставляется с β,
-
t сопоставляется с γ, и
-
Конкатенация α β γ δ является разбираемой программой на Rust.
-
Мы используем сокращение ANYTOKEN для обозначения множества всех токенов (включая простые NT). Например, если любой токен разрешен после сопоставителя M, то FOLLOW(M) = ANYTOKEN.
(Чтобы проверить свое понимание вышеприведенных неформальных описаний, читателю на этом этапе может захотеться перейти к примерам FIRST/LAST перед чтением их формальных определений.)
FIRST, LAST
Ниже приведены формальные индуктивные определения для FIRST и LAST.
“A ∪ B” обозначает объединение множеств, “A ∩ B” обозначает пересечение множеств, а “A \ B” обозначает разность множеств (т.е. все элементы A, которые не присутствуют в B).
FIRST
FIRST(M) определяется анализом случаев последовательности M и структуры ее первого дерева токенов (если есть):
- если M - пустая последовательность, то FIRST(M) = { ε },
-
если M начинается с токена t, то FIRST(M) = { t },
(Примечание: это покрывает случай, когда M начинается с разделенной последовательности деревьев токенов,
M = OPEN tt ... CLOSE ..., в этом случаеt = OPENи thus FIRST(M) = {OPEN}.)(Примечание: это критически зависит от свойства, что никакой простой NT не сопоставляется с пустым фрагментом.)
-
В противном случае, M - это последовательность деревьев токенов, начинающаяся со сложного NT:
M = $( tt ... ) OP α, илиM = $( tt ... ) SEP OP α, (гдеα- (потенциально пустая) последовательность деревьев токенов для остальной части сопоставителя).- Пусть SEP_SET(M) = { SEP }, если SEP присутствует и ε ∈ FIRST(
tt ...); иначе SEP_SET(M) = {}.
- Пусть SEP_SET(M) = { SEP }, если SEP присутствует и ε ∈ FIRST(
-
Пусть ALPHA_SET(M) = FIRST(
α), если OP =*или?, и ALPHA_SET(M) = {}, если OP =+. -
FIRST(M) = (FIRST(
tt ...) \ {ε}) ∪ SEP_SET(M) ∪ ALPHA_SET(M).
Определение для сложных NT заслуживает некоторого обоснования. SEP_SET(M) определяет возможность того, что сепаратор может быть допустимым первым токеном для M, что происходит, когда сепаратор определен и повторяющийся фрагмент может быть пустым. ALPHA_SET(M) определяет возможность того, что сложный NT может быть пустым, означая, что допустимые первые токены M - это те, что из следующих последовательностей деревьев токенов α. Это происходит, когда используется * или ?, и в этом случае может быть ноль повторений. Теоретически, это также может произойти, если + используется с потенциально пустым повторяющимся фрагментом, но это запрещено третьим инвариантом.
Отсюда ясно, что FIRST(M) может включать любой токен из SEP_SET(M) или ALPHA_SET(M), и если сопоставление сложного NT непусто, то любой токен, начинающий FIRST(tt ...), также может работать. Последняя часть для рассмотрения - это ε. SEP_SET(M) и FIRST(tt ...) \ {ε} не могут содержать ε, но ALPHA_SET(M) может. Следовательно, это определение позволяет M принимать ε тогда и только тогда, когда ε ∈ ALPHA_SET(M). Это правильно, потому что для того, чтобы M принял ε в случае сложного NT, и сложный NT, и α должны принять его. Если OP = +, означая, что сложный NT не может быть пустым, то по определению ε ∉ ALPHA_SET(M). В противном случае сложный NT может принимать ноль повторений, и тогда ALPHA_SET(M) = FOLLOW(α). Так что это определение также корректно относительно ε.
LAST
LAST(M), определяется анализом случаев самой M (последовательности деревьев токенов):
- если M - пустая последовательность, то LAST(M) = { ε }
- если M - одиночный токен t, то LAST(M) = { t }
-
если M - одиночный сложный NT, повторяющийся ноль или более раз,
M = $( tt ... ) *, илиM = $( tt ... ) SEP *-
Пусть sep_set = { SEP }, если SEP присутствует; иначе sep_set = {}.
-
если ε ∈ LAST(
tt ...) тогда LAST(M) = LAST(tt ...) ∪ sep_set -
иначе последовательность
tt ...должна быть непустой; LAST(M) = LAST(tt ...) ∪ {ε}.
-
-
если M - одиночный сложный NT, повторяющийся один или более раз,
M = $( tt ... ) +, илиM = $( tt ... ) SEP +-
Пусть sep_set = { SEP }, если SEP присутствует; иначе sep_set = {}.
-
если ε ∈ LAST(
tt ...) тогда LAST(M) = LAST(tt ...) ∪ sep_set -
иначе последовательность
tt ...должна быть непустой; LAST(M) = LAST(tt ...)
-
- если M - одиночный сложный NT, повторяющийся ноль или один раз,
M = $( tt ...) ?, тогда LAST(M) = LAST(tt ...) ∪ {ε}.
- если M - разделенная последовательность деревьев токенов
OPEN tt ... CLOSE, тогда LAST(M) = {CLOSE}.
-
если M - непустая последовательность деревьев токенов
tt uu ...,-
Если ε ∈ LAST(
uu ...), тогда LAST(M) = LAST(tt) ∪ (LAST(uu ...) \ { ε }). -
Иначе последовательность
uu ...должна быть непустой; тогда LAST(M) = LAST(uu ...).
-
Примеры FIRST и LAST
Ниже приведены некоторые примеры FIRST и LAST. (Обратите внимание, в частности, как вводится и устраняется специальный элемент ε на основе взаимодействия между частями ввода.)
Наш первый пример представлен в древовидной структуре, чтобы подробно описать, как компилируется анализ сопоставителя. (Некоторые из более простых поддеревьев были опущены.)
ВВОД: $( $d:ident $e:expr );* $( $( h )* );* $( f ; )+ g
~~~~~~~~ ~~~~~~~ ~
| | |
FIRST: { $d:ident } { $e:expr } { h }
ВВОД: $( $d:ident $e:expr );* $( $( h )* );* $( f ; )+
~~~~~~~~~~~~~~~~~~ ~~~~~~~ ~~~
| | |
FIRST: { $d:ident } { h, ε } { f }
ВВОД: $( $d:ident $e:expr );* $( $( h )* );* $( f ; )+ g
~~~~~~~~~~~~~~~~~~~~~~~~~~~~ ~~~~~~~~~~~~~~ ~~~~~~~~~ ~
| | | |
FIRST: { $d:ident, ε } { h, ε, ; } { f } { g }
ВВОД: $( $d:ident $e:expr );* $( $( h )* );* $( f ; )+ g
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
FIRST: { $d:ident, h, ;, f }
Таким образом:
- FIRST(
$($d:ident $e:expr );* $( $(h)* );* $( f ;)+ g) = {$d:ident,h,;,f}
Однако обратите внимание, что:
- FIRST(
$($d:ident $e:expr );* $( $(h)* );* $($( f ;)+ g)*) = {$d:ident,h,;,f, ε }
Вот похожие примеры, но теперь для LAST.
- LAST(
$d:ident $e:expr) = {$e:expr} - LAST(
$( $d:ident $e:expr );*) = {$e:expr, ε } - LAST(
$( $d:ident $e:expr );* $(h)*) = {$e:expr, ε,h} - LAST(
$( $d:ident $e:expr );* $(h)* $( f ;)+) = {;} - LAST(
$( $d:ident $e:expr );* $(h)* $( f ;)+ g) = {g}
FOLLOW(M)
Наконец, определение для FOLLOW(M) строится следующим образом. pat, expr и т.д. представляют простые нетерминалы с данным спецификатором фрагмента.
- FOLLOW(pat) = {
=>,,,=,|,if,in}.
- FOLLOW(expr) = FOLLOW(expr_2021) = FOLLOW(stmt) = {
=>,,,;}.
- FOLLOW(ty) = FOLLOW(path) = {
{,[,,,=>,:,=,>,>>,;,|,as,where, block nonterminals}.
- FOLLOW(vis) = {
,, любой ключевое слово или идентификатор, кроме не-сырогоpriv; любой токен, который может начинать тип; ident, ty и path нетерминалы}.
- FOLLOW(t) = ANYTOKEN для любого другого простого токена, включая block, ident, tt, item, lifetime, literal и meta простые нетерминалы, и все терминалы.
- FOLLOW(M), для любого другого M, определяется как пересечение, когда t пробегает по (LAST(M) \ {ε}), из FOLLOW(t).
Токены, которые могут начинать тип, на момент написания: {(, [, !, *, &, &&, ?, lifetimes, >, >>, ::, любой неключевой идентификатор, super, self, Self, extern, crate, $crate, _, for, impl, fn, unsafe, typeof, dyn}, хотя этот список может быть неполным, потому что люди не всегда помнят обновлять приложение, когда добавляются новые.
Примеры FOLLOW для сложных M:
- FOLLOW(
$( $d:ident $e:expr )*) = FOLLOW($e:expr) - FOLLOW(
$( $d:ident $e:expr )* $(;)*) = FOLLOW($e:expr) ∩ ANYTOKEN = FOLLOW($e:expr) - FOLLOW(
$( $d:ident $e:expr )* $(;)* $( f |)+) = ANYTOKEN
Примеры допустимых и недопустимых сопоставителей
Имея вышеуказанную спецификацию, мы можем представить аргументы, почему определенные сопоставители являются законными, а другие - нет.
-
($ty:ty < foo ,): недопустимо, потому что FIRST(< foo ,) = {<} ⊈ FOLLOW(ty) -
($ty:ty , foo <): допустимо, потому что FIRST(, foo <) = {,} is ⊆ FOLLOW(ty). -
($pa:pat $pb:pat $ty:ty ,): недопустимо, потому что FIRST($pb:pat $ty:ty ,) = {$pb:pat} ⊈ FOLLOW(pat), и также FIRST($ty:ty ,) = {$ty:ty} ⊈ FOLLOW(pat). -
( $($a:tt $b:tt)* ; ): допустимо, потому что FIRST($b:tt) = {$b:tt} is ⊆ FOLLOW(tt) = ANYTOKEN, как и FIRST(;) = {;}. -
( $($t:tt),* , $(t:tt),* ): допустимо, (хотя любая попытка фактически использовать этот макрос вызовет ошибку локальной неоднозначности во время раскрытия). -
($ty:ty $(; not sep)* -): недопустимо, потому что FIRST($(; not sep)* -) = {;,-} не в FOLLOW(ty). -
($($ty:ty)-+): недопустимо, потому что сепаратор-не в FOLLOW(ty). -
($($e:expr)*): недопустимо, потому что expr NT не в FOLLOW(expr NT).
Влияния
Rust не является особенно оригинальным языком, с элементами дизайна, пришедшими из широкого спектра источников. Некоторые из них перечислены ниже (включая элементы, которые с тех пор были удалены):
- SML, OCaml: алгебраические типы данных, сопоставление с образцом (pattern matching), вывод типов, разделение операторов точкой с запятой
- C++: ссылки, RAII, умные указатели, семантика перемещения (move semantics), мономорфизация, модель памяти
- ML Kit, Cyclone: управление памятью на основе регионов (region based memory management)
- Haskell (GHC): трейты (typeclasses), ассоциированные типы (type families)
- Newsqueak, Alef, Limbo: каналы, конкурентность
- Erlang: передача сообщений, отказ потоков,
отказ связанных потоков,облегченная конкурентность - Swift: опциональные привязки (optional bindings)
- Scheme: гигиеничные макросы (hygienic macros)
- C#: атрибуты
- Ruby: синтаксис замыканий,
синтаксис блоков - NIL, Hermes:
состояние типа (typestate) - Unicode Annex #31: синтаксис идентификаторов и образцов (pattern syntax)
Сводка тестов
Ниже приведена сводка общих тестов, которые связаны с отдельными идентификаторами правил в справочнике.
Глоссарий
Абстрактное синтаксическое дерево (Abstract syntax tree)
‘Абстрактное синтаксическое дерево’ или ‘AST’ - это промежуточное представление структуры программы, когда компилятор компилирует ее.
Выравнивание (Alignment)
Выравнивание значения определяет, по каким адресам предпочтительно начинаются значения. Всегда является степенью двойки. Ссылки на значение должны быть выровнены. Подробнее.
Двоичный интерфейс приложения (Application binary interface, ABI)
Двоичный интерфейс приложения (ABI) определяет, как скомпилированный код взаимодействует с другим скомпилированным кодом. С помощью extern блоков и extern fn, строки ABI влияют на:
- Соглашение о вызовах (Calling convention): Как передаются аргументы функции, возвращаются значения (например, в регистрах или в стеке) и кто отвечает за очистку стека.
- Раскрутка стека (Unwinding): Разрешена ли раскрутка стека. Например, ABI
"C-unwind"разрешает раскрутку через границу FFI, в то время как ABI"C"- нет.
Арность (Arity)
Арность относится к количеству аргументов, которые принимает функция или оператор.
Например, f(2, 3) и g(4, 6) имеют арность 2, а h(8, 2, 6)
имеет арность 3. Оператор ! имеет арность 1.
Массив (Array)
Массив, иногда также называемый массивом фиксированного размера или встроенным массивом, - это значение, описывающее коллекцию элементов, каждый из которых выбирается по индексу, который может быть вычислен во время выполнения программы. Он занимает непрерывную область памяти.
Ассоциированный элемент (Associated item)
Ассоциированный элемент - это элемент, который ассоциирован с другим элементом. Ассоциированные элементы определяются в реализациях и объявляются в трейтах. Только функции, константы и псевдонимы типов могут быть ассоциированными. Противопоставляется свободному элементу.
Покрывающая реализация (Blanket implementation)
Любая реализация, где тип появляется непокрытым. impl<T> Foo for T, impl<T> Bar<T> for T, impl<T> Bar<Vec<T>> for T и impl<T> Bar<T> for Vec<T> считаются покрывающими реализациями. Однако impl<T> Bar<Vec<T>> for Vec<T> не является покрывающей реализацией, так как все экземпляры T, которые появляются в этой impl,
покрыты Vec.
Ограничение (Bound)
Ограничения - это ограничения на тип или трейт. Например, если ограничение наложено на аргумент, который принимает функция, типы, передаваемые в эту функцию, должны соблюдать это ограничение.
Комбинатор (Combinator)
Комбинаторы - это функции высшего порядка, которые применяют только функции и ранее определенные комбинаторы для предоставления результата из своих аргументов. Они могут быть использованы для управления потоком управления модульным образом.
Крейт (Crate)
Крейт - это единица компиляции и линковки. Существуют разные типы крейтов, такие как библиотеки или исполняемые файлы. Крейты могут ссылаться и линковаться с другими библиотечными крейтами, называемыми внешними крейтами. Крейт имеет самодостаточное дерево модулей, начиная с неназванного корневого модуля, называемого корнем крейта. Элементы могут быть сделаны видимыми для других крейтов, помечая их как публичные в корне крейта, включая через пути публичных модулей. Подробнее.
Диспетчеризация (Dispatch)
Диспетчеризация - это механизм определения того, какая конкретная версия кода фактически выполняется, когда это связано с полиморфизмом. Две основные формы диспетчеризации - статическая диспетчеризация и динамическая диспетчеризация. Rust поддерживает динамическую диспетчеризацию через использование трейт-объектов.
Тип с динамическим размером (Dynamically sized type)
Тип с динамическим размером (DST) - это тип без статически известного размера или выравнивания.
Сущность (Entity)
Сущность - это языковая конструкция, на которую можно ссылаться каким-либо образом в исходной программе, обычно через путь. Сущности включают типы, элементы, обобщенные параметры, переменные привязки, метки циклов, времена жизни, поля, атрибуты и линты.
Выражение (Expression)
Выражение - это комбинация значений, констант, переменных, операторов и функций, которая вычисляется в единственное значение, с побочными эффектами или без них.
Например, 2 + (3 * 4) - это выражение, которое возвращает значение 14.
Свободный элемент (Free item)
Элемент, который не является членом реализации, такой как свободная функция или свободная константа. Противопоставляется ассоциированному элементу.
Фундаментальные трейты (Fundamental traits)
Фундаментальный трейт - это трейт, добавление реализации которого для существующего типа является критическим изменением.
Трейты Fn и Sized являются фундаментальными.
Фундаментальные конструкторы типов (Fundamental type constructors)
Фундаментальный конструктор типа - это тип, для которого реализация покрывающей реализации над ним
является критическим изменением. &, &mut, Box и Pin являются фундаментальными.
Всякий раз, когда тип T считается локальным, &T, &mut T, Box<T> и Pin<T>
также считаются локальными. Фундаментальные конструкторы типов не могут покрывать другие типы.
Всякий раз, когда используется термин “покрытый тип”,
T в &T, &mut T, Box<T> и Pin<T> не считается покрытым.
Обитаемый тип (Inhabited)
Тип является обитаемым, если у него есть конструкторы и, следовательно, он может быть инстанциирован. Обитаемый тип не “пуст” в том смысле, что могут существовать значения этого типа. Противоположность Необитаемому.
Собственная реализация (Inherent implementation)
Реализация, которая применяется к номинальному типу, а не к паре трейт-тип. Подробнее.
Собственный метод (Inherent method)
Метод, определенный в собственной реализации, а не в реализации трейта.
Инициализированный (Initialized)
Переменная инициализирована, если ей было присвоено значение и с тех пор из нее не было перемещено. Все остальные места памяти считаются неинициализированными. Только небезопасный Rust может создать место памяти без его инициализации.
Локальный трейт (Local trait)
trait, который был определен в текущем крейте. Определение трейта является локальным
или нет независимо от примененных аргументов типа. Учитывая trait Foo<T, U>,
Foo всегда локальный, независимо от типов, подставленных для T и U.
Локальный тип (Local type)
struct, enum или union, которые были определены в текущем крейте.
На это не влияют примененные аргументы типа. struct Foo считается локальным, но
Vec<Foo> - нет. LocalType<ForeignType> является локальным. Псевдонимы типов не
влияют на локальность.
Модуль (Module)
Модуль - это контейнер для нуля или более элементов. Модули организованы в дерево, начиная с неназванного модуля в корне, называемого корнем крейта или корневым модулем. Пути могут быть использованы для ссылки на элементы из других модулей, что может быть ограничено правилами видимости. Подробнее
Имя (Name)
Имя - это идентификатор или метка времени жизни или цикла, которая ссылается на сущность. Привязка имени - это когда объявление сущности вводит идентификатор или метку, связанную с этой сущностью. Пути, идентификаторы и метки используются для ссылки на сущность.
Разрешение имен (Name resolution)
Разрешение имен - это процесс во время компиляции связывания путей, идентификаторов и меток с объявлениями сущностей.
Пространство имен (Namespace)
Пространство имен - это логическая группировка объявленных имен на основе вида сущности, на которую ссылается имя. Пространства имен позволяют появлению имени в одном пространстве имен не конфликтовать с тем же именем в другом пространстве имен.
Внутри пространства имен имена организованы в иерархию, где каждый уровень иерархии имеет свою собственную коллекцию именованных сущностей.
Номинальные типы (Nominal types)
Типы, на которые можно ссылаться путем напрямую. В частности перечисления, структуры, объединения и типы трейт-объектов.
Dyn-совместимые трейты (Dyn-compatible traits)
Трейты, которые могут быть использованы в типах трейт-объектов (dyn Trait).
Только трейты, которые следуют определенным правилам, являются dyn-совместимыми.
Ранее они были известны как объектно-безопасные трейты.
Путь (Path)
Путь - это последовательность из одного или более сегментов пути, используемая для ссылки на сущность в текущей области видимости или других уровнях иерархии пространств имен.
Прелюдия (Prelude)
Прелюдия, или The Rust Prelude, - это небольшая коллекция элементов - в основном трейтов - которые импортируются в каждый модуль каждого крейта. Трейты в прелюдии являются повсеместными.
Область видимости (Scope)
Область видимости - это область исходного текста, где именованная сущность может быть упомянута этим именем.
Проверяемое выражение (Scrutinee)
Проверяемое выражение - это выражение, которое сопоставляется в выражениях match и
похожих конструкциях сопоставления с образцом. Например, в match x { A => 1, B => 2 },
выражение x является проверяемым выражением.
Размер (Size)
Размер значения имеет два определения.
Первое - это то, сколько памяти должно быть выделено для хранения этого значения.
Второе - это смещение в байтах между последовательными элементами в массиве с этим типом элемента.
Это кратное выравниванию, включая ноль. Размер может меняться
в зависимости от версии компилятора (по мере внесения новых оптимизаций) и целевой
платформы (аналогично тому, как usize варьируется в зависимости от платформы).
Срез (Slice)
Срез - это представление с динамическим размером в непрерывную последовательность, записываемое как [T].
Его часто видят в его заимствованных формах, либо изменяемых, либо разделяемых. Тип разделяемого
среза - &[T], в то время как тип изменяемого среза - &mut [T], где T представляет
тип элемента.
Оператор (Statement)
Оператор - это наименьший самостоятельный элемент языка программирования, который командует компьютеру выполнить действие.
Строковый литерал (String literal)
Строковый литерал - это строка, хранящаяся непосредственно в конечном бинарном файле, и поэтому будет
действительна для продолжительности 'static.
Его тип - заимствованный строковый срез с продолжительностью 'static, &'static str.
Строковый срез (String slice)
Строковый срез - это самый примитивный строковый тип в Rust, записываемый как str. Его
часто видят в его заимствованных формах, либо изменяемых, либо разделяемых. Тип разделяемого
строкового среза - &str, в то время как тип изменяемого строкового среза - &mut str.
Строковые срезы всегда являются действительным UTF-8.
Трейт (Trait)
Трейт - это языковой элемент, который используется для описания функциональностей, которые тип должен предоставлять. Он позволяет типу давать определенные обещания о своем поведении.
Обобщенные функции и обобщенные структуры могут использовать трейты для ограничения типов, которые они принимают.
Турбо-рыба (Turbofish)
Пути с обобщенными параметрами в выражениях должны префиксировать открывающие скобки с ::.
В сочетании с угловыми скобками для обобщений это выглядит как рыба ::<>.
Как таковой, этот синтаксис в разговорной речи называется синтаксисом турбо-рыбы.
Примеры:
#![allow(unused)] fn main() { let ok_num = Ok::<_, ()>(5); let vec = [1, 2, 3].iter().map(|n| n * 2).collect::<Vec<_>>(); }
Этот префикс :: требуется для устранения неоднозначности обобщенных путей с множественными сравнениями в списке, разделенном запятыми.
См. бастион турбо-рыбы для примера, где отсутствие префикса было бы неоднозначным.
Непокрытый тип (Uncovered type)
Тип, который не появляется как аргумент другого типа. Например,
T непокрыт, но T в Vec<T> покрыт. Это актуально только для
аргументов типа.
Неопределенное поведение (Undefined behavior)
Поведение во время компиляции или выполнения, которое не специфицировано. Это может привести к, но не ограничивается: завершению или повреждению процесса; неправильным, некорректным или непреднамеренным вычислениям; или платформенно-специфичным результатам. Подробнее.
Необитаемый тип (Uninhabited)
Тип является необитаемым, если у него нет конструкторов и, следовательно, он никогда не может быть инстанциирован.
Необитаемый тип “пуст” в том смысле, что нет значений этого типа. Канонический
пример необитаемого типа - тип never !, или перечисление без вариантов
enum Never { }. Противоположность Обитаемому.