Как получить доступ к значениям enum в Rust? Разбираем match, if let и другие способы
Основные способы доступа к значениям enum в Rust
В Rust enum — это не просто набор констант, а алгебраический тип данных. Каждый вариант может содержать свои данные, и доступ к ним возможен только через сопоставление с образцом. Это сделано для безопасности: компилятор заставляет вас обработать все варианты, предотвращая ошибки во время выполнения.
1. Полный match (самый безопасный способ)
Обязательный для обработки всех вариантов. Компилятор проверит, что вы ничего не пропустили:
enum OrderStatus {
Pending,
Processing,
Shipped(String),
Delivered { date: String, signature: bool },
}
fn describe(status: &OrderStatus) {
match status {
OrderStatus::Pending => println!("Ожидает обработки"),
OrderStatus::Processing => println!("В обработке"),
OrderStatus::Shipped(tracking) => {
println!("Отправлено, трек-номер: {}", tracking);
}
OrderStatus::Delivered { date, signature } => {
println!("Доставлено {} (подпись: {})", date, signature);
}
}
}
Обратите внимание: в варианте Shipped(String) мы используем (tracking) — это деструктуризация кортежа. Для Delivered { date, signature } — деструктуризация структуры с именованными полями.
2. if let — когда нужен только один вариант
Идеально, если вас интересует только конкретный случай, а остальные можно проигнорировать:
let status = OrderStatus::Shipped("TRACK123".to_string());
if let OrderStatus::Shipped(tracking) = &status {
println!("Трек: {}", tracking);
} else {
println!("Ещё не отправлено");
}
Можно использовать без else, если остальное не важно:
if let OrderStatus::Delivered { date, .. } = status {
println!("Доставлено: {}", date);
}
3. while let — для итерации по enum
Полезно, когда вы обрабатываете поток значений и хотите извлекать только определённые:
let mut events = vec![
OrderStatus::Processing,
OrderStatus::Shipped("A123".into()),
OrderStatus::Delivered { date: "2024-01-01".into(), signature: true },
].into_iter();
while let Some(OrderStatus::Shipped(tracking)) = events.next() {
println!("Обрабатываем отправку: {}", tracking);
}
4. Методы на enum (рекомендуемый подход для сложной логики)
Лучшая практика — добавлять методы к самому enum:
impl OrderStatus {
fn tracking_number(&self) -> Option<&str> {
match self {
OrderStatus::Shipped(tracking) => Some(tracking),
_ => None,
}
}
fn is_delivered(&self) -> bool {
matches!(self, OrderStatus::Delivered { .. })
}
}
// Использование
let status = OrderStatus::Shipped("ABC".into());
if let Some(track) = status.tracking_number() {
println!("{}", track);
}
5. unwrap / expect — только когда уверены на 100%
Можно использовать, если вы точно знаете, что enum содержит нужный вариант. Но это опасно — при ошибке будет паника:
// НЕ рекомендуется для production
let status = OrderStatus::Shipped("TEST".into());
let tracking = match status {
OrderStatus::Shipped(t) => t,
_ => panic!("Ожидался Shipped!"),
};
Вместо этого используйте unwrap_or_else или unwrap_or_default:
let tracking = match &status {
OrderStatus::Shipped(t) => t.clone(),
_ => String::from("unknown"),
};
Важные моменты
- При сопоставлении со ссылкой (
&status) вы получаете ссылки на данные внутри enum - Используйте
matches!макрос, если нужно только проверить вариант без извлечения данных - Для enum с большим количеством вариантов используйте
_ => {}в match, чтобы игнорировать остальные - Данные внутри enum можно заимствовать, перемещать или клонировать — как с обычными переменными
Альтернативный подход: библиотека enum-as-inner и сырые указатели
Если вам нужно часто извлекать значения из enum и вы устали писать match, можно использовать крейт enum-as-inner. Он генерирует методы as_*() для каждого варианта:
use enum_as_inner::EnumAsInner;
#[derive(EnumAsInner)]
enum OrderStatus {
Pending,
Processing,
Shipped(String),
Delivered { date: String, signature: bool },
}
fn main() {
let status = OrderStatus::Shipped("TRACK".into());
// Генерируется автоматически
if let Some(tracking) = status.as_shipped() {
println!("{}", tracking);
}
// Для именованных полей
if let Some((date, signature)) = status.as_delivered() {
println!("{} {}", date, signature);
}
}
Ещё один низкоуровневый способ — использовать unsafe и приведение указателей, но это крайне не рекомендуется для новичков. Rust гарантирует безопасность через match, и попытки обойти это обычно приводят к неопределённому поведению.
Советую привыкать к match и if let — это идиоматичный Rust. Со временем вы оцените, как такой подход предотвращает баги, особенно при рефакторинге: компилятор сразу укажет все места, где нужно обновить код при добавлении нового варианта enum.