Option в Rust
В Rust нет null. Если значения может не быть, это выражается прямо в типе с помощью Option. Поначалу кажется, что это лишние церемонии, но так закрывается целый класс ошибок — «нулевые ссылки», из-за которых в других языках случилось бесчисленное множество падений.
Как выглядит Option
Заголовок раздела «Как выглядит Option»Option — это перечисление с двумя вариантами:
enum Option<T> { Some(T), // значение есть None, // значения нет}На самом деле ты его уже видел: .get() у вектора и .get() у HashMap возвращают Option.
Создаём Option
Заголовок раздела «Создаём Option»fn main() { let has_value: Option<i32> = Some(5); let no_value: Option<i32> = None;
println!("{:?}", has_value); println!("{:?}", no_value);}Вывод:
Some(5)NoneОбрабатываем Option через match
Заголовок раздела «Обрабатываем Option через match»Безопасный способ достать значение — match. Rust заставляет обработать оба случая, так что случайно забыть про вариант «значения нет» не получится:
fn main() { let age: Option<i32> = Some(30);
match age { Some(value) => println!("Возраст: {value}"), None => println!("Возраст не указан"), }}Пример из жизни
Заголовок раздела «Пример из жизни»fn find_user(id: u32) -> Option<&'static str> { if id == 1 { Some("Алиса") } else { None }}
fn main() { match find_user(1) { Some(name) => println!("Найден: {name}"), None => println!("Пользователь не найден"), }
match find_user(99) { Some(name) => println!("Найден: {name}"), None => println!("Пользователь не найден"), }}Вывод:
Найден: АлисаПользователь не найденВместо того чтобы вернуть null или выбросить исключение, когда пользователь не найден, функция самим своим типом сообщает каждому, кто её вызывает: «я могу ничего не найти». И компилятор не даст проигнорировать такую возможность.
Сокращения: unwrap_or и unwrap
Заголовок раздела «Сокращения: unwrap_or и unwrap»В простых случаях полный match может показаться перебором. Есть пара сокращений:
fn main() { let age: Option<i32> = None;
let value = age.unwrap_or(0); // если значения нет, берём 0 println!("{value}");}Есть ещё .unwrap(), который сразу отдаёт значение, но паникует (аварийно завершает программу), если там None:
let age: Option<i32> = Some(30);let value = age.unwrap(); // здесь всё в порядке, ведь age — это SomeПрактика
Заголовок раздела «Практика»1. Первое чётное число
Заголовок раздела «1. Первое чётное число»Напиши fn first_even(numbers: &[i32]) -> Option<i32>, которая возвращает первое чётное число в срезе или None, если такого нет. Выведи результат для [3, 7, 8, 10] через match, а для [1, 3, 5] — через .unwrap_or(0). Подсказка: перебирай срез в цикле и делай return Some(n), как только n % 2 == 0; после цикла верни None.
Показать решение
fn first_even(numbers: &[i32]) -> Option<i32> { for &n in numbers { if n % 2 == 0 { return Some(n); } } None}
fn main() { match first_even(&[3, 7, 8, 10]) { Some(n) => println!("Первое чётное: {n}"), None => println!("Чётных чисел нет"), }
let fallback = first_even(&[1, 3, 5]).unwrap_or(0); println!("Со значением по умолчанию: {fallback}");}Вывод:
Первое чётное: 8Со значением по умолчанию: 02. Ищем настройку
Заголовок раздела «2. Ищем настройку»Создай HashMap с настройками, где есть только "громкость" со значением 80. Затем перебери ключи "громкость" и "яркость" и выведи значение, если оно есть, или сообщение «не задана», если его нет. Подсказка: .get(key) у HashMap возвращает Option, так что по нему можно сразу сделать match.
Показать решение
use std::collections::HashMap;
fn main() { let mut settings = HashMap::new(); settings.insert("громкость", 80);
for key in ["громкость", "яркость"] { match settings.get(key) { Some(value) => println!("{key} = {value}"), None => println!("{key} не задана"), } }}Вывод:
громкость = 80яркость не заданаOption<T>описывает значение, которое может быть, а может и не быть, —nullв Rust нет.Some(value)что-то содержит;Noneзначит, что там ничего нет.match— безопасный способ обработать оба случая; компилятор не даст забыть ни один..unwrap_or(default)даёт запасное значение;.unwrap()паникует, если значениеNone, — пользуйся им пореже.
Проверь себя
Заголовок раздела «Проверь себя»Проверь себя
1. Что Rust использует вместо null для значения, которого может не быть?
2. Что происходит, если вызвать .unwrap() у значения None?
3. Что делает age.unwrap_or(0), когда age — это None?
4. Почему match считается безопасным способом достать значение из Option?
5. Какие из упомянутых на этой странице методов уже возвращают Option?
Результат: 0 / 5
Что почитать
Заголовок раздела «Что почитать»- Перечисление Option и его преимущества перед null-значениями — книга «Язык программирования Rust»: официальное объяснение, почему в Rust
Optionвместоnull(перевод на русский). std::option— документация стандартной библиотеки (на английском): обзор всего, что можно делать сOption, включая удобные методы вродеmap,and_thenиis_some.- Option и unwrap — Rust на примерах: короткие запускаемые примеры
match,unwrapи того, когда случается паника (перевод на русский). Option<T>— документация std (на английском): полный список методов, у каждого — крошечный пример, который можно скопировать.