Обеспечивайте инварианты при конструировании
Паттерн newtype можно использовать для обеспечения инвариантов.
// Copyright 2025 Google LLC
// SPDX-License-Identifier: Apache-2.0
pub struct Username(String);
impl Username {
pub fn new(username: String) -> Result<Self, InvalidUsername> {
if username.is_empty() {
return Err(InvalidUsername::CannotBeEmpty)
}
if username.len() > 32 {
return Err(InvalidUsername::TooLong { len: username.len() })
}
Ok(Self(username))
}
pub fn as_str(&self) -> &str {
&self.0
}
}
pub enum InvalidUsername {
CannotBeEmpty,
TooLong { len: usize },
}
-
Паттерн newtype в сочетании с системой модулей и видимости Rust можно использовать, чтобы гарантировать, что экземпляры заданного типа удовлетворяют набору инвариантов.
В примере выше «сырой»
String, хранящийся внутри структурыUsername, нельзя получить напрямую из других модулей или крейтов, поскольку он не помечен какpubилиpub(in ...). Потребители типаUsernameвынуждены использовать методnew, чтобы создавать экземпляры. В свою очередьnewвыполняет проверку, гарантируя, что все экземплярыUsernameудовлетворяют этим проверкам. -
Метод
as_strпозволяет потребителям получить сырое строковое представление (например, чтобы сохранить его в базе данных). Однако потребители не могут изменить базовое значение, поскольку возвращаемый тип&strограничивает их доступом только для чтения. -
Инварианты на уровне типов дают вторичные преимущества.
Входные данные проверяются один раз, на границе, а остальная часть программы может полагаться на выполнение инвариантов. Можно избежать избыточной проверки и проверок «оборонительного программирования» по всей программе, уменьшив шум и улучшив производительность.