Типичные ошибки новичков в Rust и как их исправить
Наткнулся на , которая разбирает по косточкам код начинающего Rust-разработчика. В ней автор не просто критикует, а проводит живой рефакторинг: шаг за шагом превращает работающий, но странный код в чистый Rust. Это не скучная теория, а практический разбор, как если бы опытный коллега сел рядом и начал объяснять: «Вот тут можно лучше, а здесь совсем по-другому».
Особенно зацепило, что разбор идет от типичных ошибок новичков:
Множественное повторение одно и того же кода.
Возврат кортежей (Student, bool) вместо понятного Result.
// Было: что означает этот bool? true - ок? false - ошибка?
fn add_student() -> (Student, bool) { ... }
// Стало: ясная семантика успеха или ошибки
fn add_student() -> Result { ... }
Странные циклы с ручным управлением счетчиком.
// Было: зачем нам mutable счетчик тут?
let mut i: i8 = 1;
loop { i += 1; ... }
// Стало: чисто и понятно
for i in 1usize.. { ... }
Кроме того, в статье есть решения, которые не только делают код лучше, но и учат думать как опытный Rust-разработчик, правильно использовать систему типов, инкапсуляцию через модули и писать тестируемый код с первого раза.
Ключевые шаги рефакторинга:
Инкапсуляция данных: вместо голого Vec завели структуру StudentDatabase. Это не просто обертка - это способ сказать: «Вот ваш интерфейс для работы со студентами, а внутреннее устройство - моя забота».
// Было: прямой доступ к вектору, можно сделать что угодно
let mut db: Vec = Vec::new();
db.push(student);
// Стало: данные защищены, интерфейс четкий
let mut db = StudentDatabase::new();
db.add(student);
Юнит-тестирование как дизайн-инструмент: хотите написать тестируемый код? Спросите себя: «Как я буду это тестировать?». Это заставит вас делать правильные абстракции. Например, чтобы протестировать вывод в консоль, пришлось изменить метод display(). Вместо прямого вызова println!() он теперь принимает любой объект, реализующий std::io::Write.
// Старая версия, которую не протестировать
pub fn display(&self) {
for student in &self.db {
println!("{}", student);
}
}
// Новая версия: тестируема и гибка
pub fn display_on(&self, output: &mut impl std::io::Write) -> io::Result<()> {
writeln!(output, "Студенты:")?;
for student in &self.db {
write!(output, " - {}\n", student)?;
}
Ok(())
}
// В продакшене: вывод в консоль
db.display_on(&mut std::io::stdout())?;
// В тесте: вывод в буфер
let mut buffer = Vec::new();
db.display_on(&mut buffer)?;
assert!(buffer.len() > 0);
Использование силы системы типов:Rust не ООП-язык. Его сила в системе типов и трейтах. Вместо того чтобы создавать глубокие иерархии классов, думайте о том, что может делать ваша структура (через трейты) и какие данные она хранит.
Вывод:
Статья отлично показывает, что рефакторинг - это не про «сделать красиво», а про то, чтобы код стал предсказуемым, поддерживаемым и самодокументируемым.