Атрибуты системы типов
Следующие атрибуты используются для изменения того, как тип может использоваться.
Атрибут 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) в нижестоящих крейтах.