Привет, Хабр!
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-разработчика". Узнать подробнее и зарегистрироваться можно по ссылке.