Code review в Rust: что проверять и как давать обратную связь

Онлайн-тренажер Python для начинающих

Изучайте Python легко и без перегрузки теорией. Решайте практические задачи с автоматической проверкой, получайте подсказки на русском языке и пишите код прямо в браузере — без необходимости что-либо устанавливать.

Начать курс

Введение

Code review — неотъемлемая часть профессиональной разработки. В экосистеме Rust, где на первом месте стоит безопасность памяти и производительность, ревью кода приобретает особое значение. Неправильное использование unsafe, игнорирование правил владения (ownership) или неявные паники могут свести на нет все преимущества языка.

В этой статье мы разберем, на что обращать внимание при code review в Rust, как структурировать обратную связь и какие инструменты помогут автоматизировать проверки. Материал будет полезен как новичкам, так и опытным разработчикам, желающим упорядочить процесс ревью.

1. Безопасность: проверка unsafe кода и неявных паник

Rust гарантирует безопасность памяти, но unsafe — это отказ от этих гарантий. Каждый блок unsafe должен быть тщательно проверен.

Что проверять в unsafe коде?

  • Корректность указателей: нет ли разыменования нулевого или висячего указателя.
  • Выравнивание данных: правильное ли выравнивание при приведении сырых указателей.
  • Алиасинг: нет ли нарушений правил алиасинга (например, одновременное изменяемое и неизменяемое заимствование).
  • Инварианты: соблюдены ли инварианты структур данных (например, бинарная куча остается кучей).
// Плохой пример: небезопасное приведение без проверки выравнивания
fn bad_unsafe(data: &[u8]) -> &[u32] {
    unsafe {
        std::slice::from_raw_parts(
            data.as_ptr() as *const u32,
            data.len() / 4
        )
    }
}

// Хороший пример: с проверкой выравнивания
fn good_unsafe(data: &[u8]) -> Option<&[u32]> {
    if data.len() % 4 != 0 || data.as_ptr() as usize % 4 != 0 {
        return None;
    }
    Some(unsafe {
        std::slice::from_raw_parts(
            data.as_ptr() as *const u32,
            data.len() / 4
        )
    })
}

Неявные паники

В Rust много операций, которые могут вызвать панику: unwrap(), expect(), panic!(), деление на ноль, выход за границы массива. На code review стоит обратить внимание:

  • Используйте unwrap() только в тестах или когда ошибка действительно невозможна.
  • Предпочитайте unwrap_or(), unwrap_or_else() или обработку через match.
  • Проверьте, что все as преобразования не теряют данные (например, u64 as u32).
// Плохо: неявная паника
let value = some_option.unwrap();

// Хорошо: явная обработка
let value = some_option.unwrap_or_else(|| {
    log::error!("Missing expected value");
    default_value()
});

2. Владение и заимствование: типичные ошибки

Система владения — главная особенность Rust. Ошибки компилятора здесь — лучшие друзья, но code review помогает найти логические ошибки, которые компилятор не ловит.

Избыточные клонирования

Частая проблема — клонирование там, где можно передать ссылку. Это снижает производительность и увеличивает использование памяти.

// Плохо: клонирование всей строки
fn process(data: Vec<String>) {
    for item in data {
        let cloned = item.clone();
        do_something(&cloned);
    }
}

// Хорошо: передача ссылки
fn process(data: &[String]) {
    for item in data {
        do_something(item);
    }
}

Ненужные заимствования и lifetime

Проверьте, не слишком ли длинное время жизни у ссылок. Иногда можно упростить сигнатуру функции, убрав лишние аннотации lifetime.

// Сложно: избыточные lifetime
fn longest<'a>(x: &'a str, y: &'a str) -> &'a str {
    if x.len() > y.len() { x } else { y }
}

// Если входные данные не связаны, можно использовать разные lifetime
fn longest_improved<'a, 'b>(x: &'a str, y: &'b str) -> &'a str
where 'b: 'a {
    if x.len() > y.len() { x } else { y }
}

3. Идиоматичность кода и использование инструментов

Rust имеет свой уникальный стиль. На code review проверяйте соответствие Rust Style Guide и использование стандартных идиом.

Идиомы Rust

  • Использование Option и Result: избегайте булевых флагов, используйте Option для опциональных значений.
  • Конструкции if let и while let: они делают код более читаемым.
  • Цепочки итераторов: предпочитайте filter().map().collect() вместо циклов с временными переменными.
// Неидиоматично: ручной цикл
let mut evens = Vec::new();
for x in numbers {
    if x % 2 == 0 {
        evens.push(x * 2);
    }
}

// Идиоматично: цепочка итераторов
let evens: Vec<i32> = numbers
    .iter()
    .filter(|&&x| x % 2 == 0)
    .map(|x| x * 2)
    .collect();

Инструменты для автоматизации

Включите в CI следующие проверки — они сэкономят время на ревью:

  • Clippy: линтер для Rust, ловит неидиоматичный код и потенциальные ошибки.
  • Rustfmt: автоматическое форматирование кода.
  • cargo deny: проверка лицензий и уязвимостей зависимостей.
  • cargo audit: поиск известных уязвимостей.
# Пример конфигурации CI для GitHub Actions
- name: Run                

Блоги

Рекомендаций по книгам