Как стать автором
Обновить
794.35
OTUS
Цифровые навыки от ведущих экспертов

Создаем собственные линтеры в Rust с DyLint

Уровень сложностиСредний
Время на прочтение5 мин
Количество просмотров1.2K

Привет, Хабр!

Rust совсем не прощает ошибок, и каждый разработчик, выбравший путь настоящего Растера, знает о строгой типизации и управлению памяти в этом прекрасном ЯПе. Однако, как и в любом серьёзном деле, всегда есть простор для улучшения. Один из инструментов для улучшения вашего кода - DyLint. Он полезен для создания собственных линтеров, способных находить тонкие ошибки и несоответствия.

Линтеры — это инструменты статического анализа, которые автоматически проверяют исходный код на наличие ошибок, стилевых несоответствий, подозрительных конструкций и других потенциальных проблем, задолго до того, как код попадет в продакшн.

Установим

DyLint устанавливается через Cargo:

cargo install cargo-dylint dylint-link

Чтобы использовать DyLint, добавляем конфигурационный файл dylint.toml в корень проекта. В файле можно указать, какие библиотеки линтеров должны использоваться. Например:

curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh

Для проверки проекта на соответствие линтам юзаем команду:

source $HOME/.cargo/env

Процесс создания линтеров

Создание собственных линтеров в DyLint в Rust представляет собой написание динамических библиотек, которые можно подключать к проекту для проверки кода.

Для начала создается новая библиотека с помощью Cargo:

cargo new --lib my_linter

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

Линтер должен реализовывать трейт LintPass из rustc_lint. В этом трейте определяется, какие проверки должны выполняться:

impl LintPass for MyLinter {
    fn check_expr(&mut self, cx: &EarlyContext, expr: &Expr) {
        // код проверки
    }
}

Для того чтобы Rust компилятор мог использовать линтер, необходимо зарегать его в системе:

#[plugin_registrar]
pub fn registrar(reg: &mut Registry) {
    reg.register_late_lint_pass(Box::new(MyLinter));
}

Кстати, функции проверки могут взаимодействовать с AST для анализа кода.

После создания линтера его можно тестировать, добавив его в список зависимостей и вызвав через Cargo:

cargo dylint my_linter --path path/to/your/project

Примеры создания собственных лиентеров

В качестве первого примера, создадим линтер, который проверяет, соответствуют ли комментарии в коде установленному стандарту, например, начинаются ли они с заглавной буквы и заканчиваются точкой:

impl EarlyLintPass for CommentStyleLint {
    fn check_comment(&mut self, cx: &EarlyContext, comment: &Comment) {
        if !(comment.text.starts_with(char::is_uppercase) && comment.text.ends_with('.')) {
            cx.span_lint(COMMENT_STYLE, comment.span, "Комментарий должен начинаться с заглавной буквы и заканчиваться точкой.");
        }
    }
}

Теперь создадим линтер, который выявляет использование чисел напрямую в коде:

impl LateLintPass for MagicNumberLint {
    fn check_expr(&mut self, cx: &LateContext, expr: &Expr) {
        if let ExprKind::Lit(lit) = &expr.kind {
            if let LitKind::Int(_, _) = lit.node {
                cx.span_lint(MAGIC_NUMBER, expr.span, "Использование 'магического числа' в выражении.");
            }
        }
    }
}

Ненужное использование unwrap() может привести к панике программы. Cоздадим линтер, который помогает их отслеживать:

impl LateLintPass for UnwrapUsageLint {
    fn check_expr(&mut self, cx: &LateContext, expr: &Expr) {
        if let ExprKind::MethodCall(path, _, args) = &expr.kind {
            if path.ident.name == "unwrap" {
                cx.span_lint(UNNECESSARY_UNWRAP, expr.span, "Использование `unwrap()` может привести к панике.");
            }
        }
    }
}

Cоздадим линтер, который выявляет потенциально тяжелые вычисления, выполняемые внутри циклов:

impl LateLintPass for HeavyComputationLint {
    fn check_block(&mut self, cx: &LateContext, block: &Block) {
        for stmt in &block.stmts {
            if is_heavy_computation(stmt) {
                cx.span_lint(HEAVY_COMPUTATION, stmt.span, "Тяжелое вычисление внутри цикла может замедлить выполнение.");
            }
        }
    }
}

А теперь создадим линтер помогает идентифицировать вызовы функций, которые были помечены как устаревшие:

impl LateLintPass for DeprecatedFunctionLint {
    fn check_expr(&mut self, cx: &LateContext, expr: &Expr) {
        if let ExprKind::Call(func, _) = &expr.kind {
            if let Some(deprecated) = get_deprecated_attr(func) {
                cx.span_lint(DEPRECATED_FUNCTION, expr.span, &format!("Вызов устаревшей функции: {}", deprecated.message));
            }
        }
    }
}

Различные советы

Для оптимизации производительности DyLint, важно использовать профилировщики, вроде perf, criterion и flamegraph. Они помогают выявлять узкие места и горячие точки в коде. Например, perf предоставляет информацию о производительности CPU, criterion используется для микробенчмарков, а flamegraph визуализирует вызовы функций, показывая, где тратится больше всего времени.

Правильная настройка параметров сборки может существенно повлиять на производительность. Например, использование LTO (Link Time Optimization) и замена стандартного аллокатора на альтернативные, такие как jemalloc или mimalloc, могут повысить скорость выполнения и уменьшить использование памяти. Настройка компилятора на использование специфических инструкций CPU также может дать прирост производительности.

Нужно настроить линтеры так, чтобы они фокусировались только на значимых для проекта аспектах кода. Это поможет избежать лишних предупреждений и ошибок. В файле конфигурации dylint.toml можно задать параметры для каждого линтера, чтобы точно контролировать их поведение.

Можно также использовать условия компиляции для включения или отключения определенных линтеров в зависимости от контекста. Это поможет сократить количество ложных срабатываний в специфических частях кода или в определенных средах выполнения.

Включите DyLint в CI/CD процесс, чтобы автоматически проверять код на соответствие стандартам при каждом коммите. Это позволит вовремя обнаруживать и исправлять проблемы, обеспечивая постоянное высокое качество кода. Настройте CI/CD систему так, чтобы она выполняла cargo dylint при каждом сборке проекта.

Пример конфигурации для GitHub Actions:

name: Rust project CI

on:
  push:
    branches:
      - main
  pull_request:

jobs:
  build:
    runs-on: ubuntu-latest

    steps:
    - uses: actions/checkout@v2

    - name: Set up Rust
      uses: actions-rs/toolchain@v1
      with:
        toolchain: stable
        override: true

    - name: Install DyLint
      run: cargo install cargo-dylint dylint-link

    - name: Check with DyLint
      run: cargo dylint --all

    - name: Build
      run: cargo build --verbose

    - name: Run tests
      run: cargo test --verbose

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

В Visual Studio Code можно использовать расширение Rust Analyzer и настроить его для запуска DyLint. Так можно получать предупреждения и ошибки непосредственно в редакторе кода.


В завершение хочу порекомендовать бесплатный вебинар про "Собеседование Rust-разработчика". Узнать подробнее и зарегистрироваться можно по ссылке.

Теги:
Хабы:
+12
Комментарии0

Публикации

Информация

Сайт
otus.ru
Дата регистрации
Дата основания
Численность
101–200 человек
Местоположение
Россия
Представитель
OTUS