Введение: Почему Rust так строг, и как code review помогает не сойти с ума
Вы написали код на Rust. Он скомпилировался — ура! Но вот беда: senior-разработчик, открывая ваш Pull Request, вздыхает и начинает писать комментарии. Знакомо? Rust — язык с уникальной философией: он доверяет вам, но проверяет всё. Однако компилятор не видит того, что видит опытный глаз. Он не скажет: «Эй, это можно сделать элегантнее» или «Твой код течёт — не памятью, так производительностью».
В этой статье мы проведём ревью кода на Rust и разберём типичные замечания senior-ов. Вы узнаете, как превратить «просто рабочий код» в идиоматичный и надёжный. Поехали!
1. Игнорирование системы типов: «А давай-ка я всё в String заверну»
Самая частая ошибка новичков — использовать String или общие типы там, где нужны конкретные. Senior скажет: «Твой тип должен отражать бизнес-логику, а не просто быть строкой».
Проблема: потеря семантики
Представьте, что вы пишете функцию для работы с email-адресами:
fn send_email(to: String, subject: String, body: String) {
// ...
}
Что здесь не так? Вы можете перепутать to и subject — компилятор не заметит. Senior предложит использовать newtype-паттерн:
struct Email(String);
struct Subject(String);
struct Body(String);
fn send_email(to: Email, subject: Subject, body: Body) {
// ...
}
Это кажется избыточным? На code review Rust такой подход — золотой стандарт. Он делает код самодокументируемым и исключает целый класс ошибок.
2. Злоупотребление clone(): копировать — не значит владеть
Junior-разработчики часто используют .clone() везде, где компилятор ругается на заимствования. Senior скажет: «Ты просто сдаёшься перед borrow checker-ом. Думай иначе».
Пример: лишний clone
fn process(data: Vec<i32>) {
let first = data[0].clone();
let second = data[1].clone();
// ...
}
i32 реализует трейт Copy — clone() здесь абсолютно лишний. Но проблема глубже: если вы клонируете большие структуры (например, Vec или String), то получаете удар по производительности.
Правильный подход — работать с ссылками:
fn process(data: &[i32]) {
let first = &data[0];
let second = &data[1];
// ...
}
На ревью кода на Rust вы обязаны объяснить, почему вы клонируете. Если ответ «чтобы компилятор не ругался» — готовьтесь к разгромному комментарию.
3. Плохая обработка ошибок: .unwrap() — это не финальное решение
Senior-ы ненавидят unwrap() и expect() в production-коде. Почему? Потому что они превращают потенциально восстанавливаемую ошибку в панику. А паника в Rust — это аварийное завершение.
Что не так с этим кодом?
let file_content = std::fs::read_to_string("config.txt").unwrap();
Если файла нет — программа упадёт. Senior предложит:
let file_content = std::fs::read_to_string("config.txt")
.map_err(|e| format!("Failed to read config: {}", e))?;
Используйте ? оператор и собственные типы ошибок. Это основа идиоматичного Rust. На code review Rust это одно из первых замечаний.
4. Забытая производительность: аллокации и итераторы
Rust — системный язык. Senior будет смотреть не только на безопасность, но и на скорость. Одна из типичных проблем — создание лишних коллекций.
Пример: коллекционирование без нужды
let numbers: Vec<i32> = (0..100).collect();
let doubled: Vec<i32> = numbers.iter().map(|x| x * 2).collect();
Зачем создавать Vec дважды? Можно использовать ленивые итераторы:
let doubled: Vec<i32> = (0..100).map(|x| x * 2).collect();
Или ещё лучше — передавать итератор в функцию, чтобы не аллоцировать память вообще:
fn process(iter: impl Iterator<Item = i32>) {
for item in iter {
// ...
}
}
Такой подход — признак высокого уровня владения Rust. Senior обязательно отметит это на ревью кода.
5. Неправильная работа с временем жизни: «Я просто добавлю 'static»
Когда borrow checker начинает ругаться на времена жизни, многие разработчики ставят 'static как «волшебную таблетку». Senior скажет: «Ты только что запретил передавать сюда что-либо, кроме глобальных констант».
Проблема: излишний 'static
fn process(data: &'static str) {
println!("{}", data);
}
Эта функция не примет строку, созданную на стеке. Нужно использовать обобщённое время жизни:
fn process<'a>(data: &'a str) {
println!("{}", data);
}
Или, если время жизни не важно, используйте &str — компилятор сам выведет аннотации. На обучении Rust это одна из самых сложных тем, но senior видит эти ошибки мгновенно.
Вывод: 5 шагов к чистому коду на Rust
Итак, подведём итоги. Чтобы пройти code review Rust без красных флагов, запомните эти action items:
- Используйте newtype-паттерн — не прячьте семантику в примитивные типы.
- Избегайте лишних clone() — учитесь работать с ссылками и заимствованиями.
- Не вызывайте unwrap() — обрабатывайте ошибки через
Resultи?. - Оптимизируйте итераторы — не создавайте промежуточные коллекции без нужды.
- Не злоупотребляйте 'static — используйте обобщённые времена жизни.
Rust — это язык, который учит думать. Каждое замечание senior-а — не критика, а шанс стать лучше. С