Parse
Link:
- Parse, don’t validate: https://lexi-lambda.github.io/blog/2019/11/05/parse-don-t-validate/
Parse - don’t validate
Эта философия превращает подверженные ошибкам проверки в runtime в гарантии во время компиляции. То есть, входные данные в программе нельзя тащить дальше в код. Нужно их на входе проверять и отсеивать. Т.е. вопросы: “а вдруг там ничего нет void/none, а вдруг пользователь ввёл некорректные данные?” - надо решать сразу на входной функции чтения данных, и не тащить это по всей программе, везде делая реверанс в стиле “а если там в начале ничего не было, то… "
В Rust это можно и нужно зашивать в типы данных, которые гарантируют наличие контента.
Пример: непустой массив
Допустим, я создаю тип для дома, в котором будет массив комнат. Массив комнат в доме априори не может быть пустым - хотя бы одна комната должны быть!
Вариант 1 - валидация в конструкторе
pub struct House {
rooms: Vec<Room>,
}
impl House {
/// Конструктор, возвращающий Result - не может создать пустой дом
pub fn new(rooms: Vec<Room>) -> Result<Self, &'static str> {
if rooms.is_empty() {
Err("House must have at least one room")
} else {
Ok(House { rooms })
}
}Вариант 2 - NonEmpty
Добавляем зависимость cargo add nonempty спец-тип:
use nonempty::NonEmpty;
pub struct House {
rooms: NonEmpty<Room>, // Гарантированно не пустой список
}
impl House {
/// Конструктор, принимающий как минимум одну комнату
pub fn new(first_room: Room, rest_rooms: Vec<Room>) -> Self {
let mut rooms = NonEmpty::new(first_room);
rooms.extend(rest_rooms);
House { rooms }
}Вариант 3 - собственный тип
Создаём собственный тип данных, аналог NonEmpty:
use std::ops::{Index, IndexMut};
/// Вектор, который гарантированно содержит хотя бы один элемент
#[derive(Debug, Clone)]
pub struct NonEmptyVec<T> {
first: T,
rest: Vec<T>,
}
impl<T> NonEmptyVec<T> {
/// Создает новый NonEmptyVec с одним элементом
pub fn new(first: T) -> Self {
NonEmptyVec {
first,
rest: Vec::new(),
}
}
/// Создает NonEmptyVec из Vec, возвращая None если вектор пуст
pub fn from_vec(mut vec: Vec<T>) -> Option<Self> {
if vec.is_empty() {
None
} else {
let first = vec.remove(0);
Some(NonEmptyVec {
first,
rest: vec,
})
}
}
/// Создает NonEmptyVec из Vec, паникуя если вектор пуст
pub fn from_vec_unchecked(vec: Vec<T>) -> Self {
assert!(!vec.is_empty(), "Cannot create NonEmptyVec from empty vector");
let mut vec = vec;
let first = vec.remove(0);
NonEmptyVec {
first,
rest: vec,
}
}
/// Создает NonEmptyVec из итератора, возвращая None если итератор пуст
pub fn from_iter<I: IntoIterator<Item = T>>(iter: I) -> Option<Self> {
let mut iter = iter.into_iter();
let first = iter.next()?;
let rest: Vec<T> = iter.collect();
Some(NonEmptyVec { first, rest })
}
/// Возвращает количество элементов
pub fn len(&self) -> usize {
1 + self.rest.len()
}
/// Всегда возвращает false, так как NonEmptyVec никогда не пуст
pub fn is_empty(&self) -> bool {
false
}
/// Получает ссылку на элемент по индексу
pub fn get(&self, index: usize) -> Option<&T> {
if index == 0 {
Some(&self.first)
} else {
self.rest.get(index - 1)
}
}
/// Получает мутабельную ссылку на элемент по индексу
pub fn get_mut(&mut self, index: usize) -> Option<&mut T> {
if index == 0 {
Some(&mut self.first)
} else {
self.rest.get_mut(index - 1)
}
}
/// Возвращает ссылку на первый элемент
pub fn first(&self) -> &T {
&self.first
}
/// Возвращает мутабельную ссылку на первый элемент
pub fn first_mut(&mut self) -> &mut T {
&mut self.first
}
/// Возвращает ссылку на последний элемент
pub fn last(&self) -> &T {
self.rest.last().unwrap_or(&self.first)
}
/// Возвращает мутабельную ссылку на последний элемент
pub fn last_mut(&mut self) -> &mut T {
if self.rest.is_empty() {
&mut self.first
} else {
self.rest.last_mut().unwrap()
}
}
/// Добавляет элемент в конец
pub fn push(&mut self, value: T) {
self.rest.push(value);
}
/// Удаляет последний элемент, возвращая его
/// Гарантированно возвращает Some, так как всегда есть хотя бы один элемент
pub fn pop(&mut self) -> Option<T> {
self.rest.pop().or_else(|| {
// Не можем удалить последний элемент, так как это сделает коллекцию пустой
// Вместо этого возвращаем None, сигнализируя, что удаление невозможно
None
})
}
/// Удаляет последний элемент, паникуя если это был последний элемент
pub fn pop_unchecked(&mut self) -> T {
self.rest.pop().expect("Cannot pop the last element of NonEmptyVec")
}
/// Вставляет элемент на указанную позицию
pub fn insert(&mut self, index: usize, value: T) {
if index == 0 {
let old_first = std::mem::replace(&mut self.first, value);
self.rest.insert(0, old_first);
} else {
self.rest.insert(index - 1, value);
}
}
/// Удаляет элемент по индексу, возвращая его
pub fn remove(&mut self, index: usize) -> Option<T> {
if index == 0 {
if self.rest.is_empty() {
// Не можем удалить последний элемент
None
} else {
let old_first = std::mem::replace(&mut self.first, self.rest.remove(0));
Some(old_first)
}
} else {
self.rest.remove(index - 1).into()
}
}
/// Создает итератор
pub fn iter(&self) -> impl Iterator<Item = &T> {
std::iter::once(&self.first).chain(self.rest.iter())
}
/// Создает мутабельный итератор
pub fn iter_mut(&mut self) -> impl Iterator<Item = &mut T> {
std::iter::once(&mut self.first).chain(self.rest.iter_mut())
}
/// Преобразует в Vec
pub fn into_vec(mut self) -> Vec<T> {
let mut vec = Vec::with_capacity(self.len());
vec.push(self.first);
vec.append(&mut self.rest);
vec
}
/// Применяет функцию ко всем элементам
pub fn map<U, F>(self, mut f: F) -> NonEmptyVec<U>
where
F: FnMut(T) -> U,
{
NonEmptyVec {
first: f(self.first),
rest: self.rest.into_iter().map(f).collect(),
}
}
}
// Реализация Index для удобного доступа по индексу
impl<T> Index<usize> for NonEmptyVec<T> {
type Output = T;
fn index(&self, index: usize) -> &Self::Output {
self.get(index).expect("Index out of bounds")
}
}
// Реализация IndexMut для мутабельного доступа по индексу
impl<T> IndexMut<usize> for NonEmptyVec<T> {
fn index_mut(&mut self, index: usize) -> &mut Self::Output {
self.get_mut(index).expect("Index out of bounds")
}
}
// Реализация IntoIterator для использования в циклах
impl<T> IntoIterator for NonEmptyVec<T> {
type Item = T;
type IntoIter = std::vec::IntoIter<T>;
fn into_iter(self) -> Self::IntoIter {
self.into_vec().into_iter()
}
}
// Реализация FromIterator для создания из итератора
impl<T> FromIterator<T> for NonEmptyVec<T> {
fn from_iter<I: IntoIterator<Item = T>>(iter: I) -> Self {
NonEmptyVec::from_iter(iter).expect("Cannot create NonEmptyVec from empty iterator")
}
}