command - команда и параметры
watch - отслеживаемая папка
need_stdout - stdout вывод кода показывать к терминале
Далее запуск секции:
bacon check-examples
Интерактивный перезапуск компиляции
This will compile+build the code in examples folder, file “variables.rs”. Very convenient to try test different stuff. For live development do:
bacon run -- -q # сборка и запуск текущего проектbacon run -- -q --example <файл> # сборка и запуск файла в папке examplesbacon test# запуск unit-тестов (например, определённых для lib.rs)
cargo new test_project // create project with binary file src/main.rs
// OR
cargo new test_project --lib // create project with library file src/lib.rs
cd test_project
Source code is in src folder.
cargo build # build project for debug, do not runcargo run # build & run projectcargo check # fast compilingcargo build --release # slow build with optimizations for speed of release version
Documentation of methods & traits used in code can be compiled an opened in browser with Cargo command:
cargo doc --open
Зависимости кода в cargo.toml
[dependencies] — Package library dependencies - видно отовсюду, из тестов;
[dev-dependencies] — Dependencies for examples, tests, and benchmarks. При сборке ПО видны только для тестов (в рамках #[cfg(test)] mod tests {});
[build-dependencies] — Dependencies for build scripts.
Сборка бинарных файлов
Можно в src папке создать подпапку bin, и там сложить вариации бинарных файлов. После чего указывать при сборке cargo run --bin <название файла в папке bin>
Сборка Examples
Create “examples” folder beside the “src” folder. Create a file named, for example, “variables.rs” and place some test code in the folder. Then in the project root folder run:
cargo run --example variables
❗Примеры в Examples папке наследуют features и прочие параметры из общего Cargo.toml. В случае условной компиляции (см, далее) команда будет выглядеть: cargo run --example variables --features mock
Условная компиляция
Позволяет исключить часть кода в зависимости от условий.
#[cfg(feature = "debug")]// код сработает только с флагом debug
fnget_data()-> String{"мулька".to_string()}#[cfg(feature = "mock", feature = "debug")]// код сработает только с флагами mock И debug
fnget_data()-> String{"хитрая мулька".to_string()}#[cfg(not(feature = "debug"))]// код сработает без флага debug
fnget_data()-> String{"реальные данные".to_string()}fnmain(){println!("{}",get_data());}
Features нужно специально включать при сборке командой cargo run --features debug. Либо можно прописать флаг включения default = ["debug"] в файле Cargo.toml:
[features]default=["debug"]# по-умолчанию флаг = ВКЛdebug=[]
Отключить работу флага default можно командой cargo run --no-default-features.
Условная компиляция, встроенная в Cargo, позволяет подключать зависимости опционально. Можно проверять фичи в коде:
Пример с подключением библиотеки rand только для создания случайного значения в “mock-варианте” кода:
[features]default=["mock"]# включить флаг mock по-умолчаниюmock=["rand"]# использовать рандимозатор только при включенном флагеmock-persistent=["rand"][dependencies]rand={version="0.10.0",optional=true}# опциональный рандомизатор
Диагностика сборки
cargo-bloat для поиска проблем
cargo install cargo-bloat
cargo bloat --release
Определяет, какие зависимости задерживают сборку.
cargo build –timings
Создаёт отчёт в HTML с указанием трат времени:
cargo build --timings
# далее открыть target/cargo-timings/cargo-timing.html
cargo-llvm-lines
Показывает, какие generic-функции создают больше всего нагрузки для LLVM:
Generics создают мономорфизм - каждый новый тип создаёт новый код при сборке.
Ускорение сборки
Профили Cargo
Можно управлять ходом сборки проекта с помощью профилей. В том числе это сильно виляет на скорость сборки. Примеры профилей, которые можно создать в cargo.toml файле:
[profile.dev]opt-level=0# No optimization (fastest compilation LLVM)debug=1# Line info only (not full debug symbols)codegen-units=256# More parallelism (max is 256)incremental=true# Recompile only changed code[profile.release]opt-level=3# Maximum runtime performancelto="thin"# Faster than "fat" LTO, still good optimizationcodegen-units=1# Better optimization at cost of compile time
Для debug сборок, opt-level = 0 и высокое число codegen-units даю самую высокую скорость сборки (разница в скорости в 108 раз!).
Кеширование сборки зависимостей
С помощью sccache можно кешировать собранные артефакты, в том числе таскать кеш между системами.
Использовать все ядра, кроме 2 (чтобы ОС не повисла):
cargo build -j $(nproc --ignore=2)
Либо указать в .cargo/config.toml:
[build]jobs=8
Panic Response
В ответ на панику, по умолчанию программа разматывает стек (unwinding) - проходит по стеку и вычищает данные всех функций. Для уменьшения размера можно просто отключать программу без очистки - abort. Для этого в файле Cargo.toml надо добавить в разделы [profile]:
[profile.release]panic='abort'
Mold (только для Linux)
Проект для Linux (в macOS есть коммерческий аналог Sold, который ненамного лучше сборщику в XCode 15+, потому использование не оправдано).
Настройка
Ubuntu: sudo apt-get install mold clang
Fedora: sudo dnf install mold clang
Arch Linux: sudo pacman -S mold clang
Создать в проекте папку и файл .cargo/config.toml (либо для всех проектов сразу - папка и файл ~/.cargo/config.toml) и вписать:
Далее, в код бинарной программы включить функции из библиотеки:
useexamplelib::function01;// фукнкция должна быть публичной (pub fn)
Единая инициализация и сборка библиотек зависимостей в Workspace
Создаём проект типа workspace и прописываем в его файле Cargo.toml верхнего уровня все библиотеки с версиями (в примере anyhow) в спец разделе [workspace.dependencies]:
При этом в отдельном разделе [dependencies] указываем, что библиотеки будут распространяться на весь проект. Далее, создаём модуль внутри workspace (в примере = greeter) и в его файле Cargo.toml прописываем, что библиотека берётся из зависимостей workspace:
[dependencies]anyhow={workspace=true}
Можно добавлять features к библиотеке из workspace на этапе описания вложенных модулей:
Cargo audit - это инструмент, который проверяет зависимости проекта на наличие известных уязвимостей. Он не запускает код, не генерирует тесты и не следит за памятью. Вместо этого он сверяет версии всех библиотек (crates) с базой данных уязвимостей (RustSec Advisory Database).
Установка и запуск
cargo install cargo-audit # установить один разcargo audit # проверить проект
Пример вывода:
Crate:regexVersion:1.4.0Title:Regex panic on crafted inputSeverity:highSolution:upgrade to >= 1.5.0
Cargo audit находит только те проблемы, о которых кто-то сообщил и добавил в базу. Он не ищет новые, неизвестные уязвимости.
Какие проблемы находит Cargo Audit?
Тип уязвимости
Пример
Паника при специальном вводе
Библиотека крашится на строке вида “aaaaaaaa…”
Утечка данных
Библиотека отправляет данные на левый сервер
Проблемы с безопасностью
В версии 1.2.3 веб-сервера есть дыра для DoS-атаки
name:Rust CI/CD Pipelineon:push:branches:["main"]pull_request:branches:["main"]env:CARGO_TERM_COLOR:alwaysRUST_BACKTRACE:1jobs:# Job 1: Format and Lintformat-lint:runs-on:ubuntu-lateststeps:- uses:actions/checkout@v4- uses:actions-rs/toolchain@v1with:toolchain:nightlycomponents:rustfmt, clippyoverride:true- name:Format checkrun:cargo +nightly fmt -- --check- name:Clippyrun:>- cargo clippy --all-targets --all-features --workspace
-- -D warnings# Job 2: Build (original)build:runs-on:ubuntu-lateststeps:- uses:actions/checkout@v4- uses:actions-rs/toolchain@v1with:toolchain:stableoverride:true- name:Build releaserun:cargo build --release- name:Upload artifactsuses:actions/upload-artifact@v4with:name:binarypath:target/release/smarthouse# NEW JOB 3: Security Audit (Cargo Audit)# Checks if any dependencies have known vulnerabilitiessecurity-audit:runs-on:ubuntu-lateststeps:- uses:actions/checkout@v4# Install and run cargo-audit- name:Install cargo-auditrun:cargo install cargo-audit- name:Run security auditrun:cargo audit# This will fail the build if any vulnerabilities found# Exit code 1 if vulnerabilities detected# NEW JOB 4: Property-based Testing (Proptest)# Runs extended tests with randomly generated dataproptest:runs-on:ubuntu-lateststeps:- uses:actions/checkout@v4- uses:actions-rs/toolchain@v1with:toolchain:stableoverride:true# Run tests including proptest (slower than regular tests)- name:Run property-based testsrun:cargo test --release --lib -- --include-ignored proptest# --include-ignored runs proptest tests marked with #[ignore]# Proptest generates thousands of random inputs to find edge cases# Alternative: Run all tests with proptest enabled (no ignore)- name:Run all tests including proptest (alternative)run:cargo test --release -- --nocapture# This runs ALL tests, including proptest tests# Remove this if you prefer the filtered version above# NEW JOB 5: Unsafe Code Verification (Miri)# Checks for Undefined Behavior in unsafe Rust code# WARNING: Miri is VERY slow (100-1000x) but catches critical bugsmiri-check:runs-on:ubuntu-latest# Only run if you actually have unsafe code# Add a condition to avoid slow checks on simple projectssteps:- uses:actions/checkout@v4- uses:actions-rs/toolchain@v1with:toolchain:nightly # Miri requires nightly Rustcomponents:mirioverride:true# Install Miri component- name:Install Mirirun:| rustup +nightly component add miri
cargo +nightly miri setup# Run Miri on tests (catches UB in unsafe code)- name:Run Miri on testsrun:cargo +nightly miri test# This will find:# - Invalid memory access# - Data races in unsafe code# - Violations of Rust's aliasing rules# - Using uninitialized memory# Optional: Run Miri on the main binary- name:Run Miri on binary (optional)run:cargo +nightly miri run# Check if the main program has UBcontinue-on-error:true# Don't fail the build if this fails# Miri is extremely slow, so this might timeout in CI# NEW JOB 6: Memory Analysis (Valgrind)# Finds memory leaks and invalid memory access in compiled binary# NOTE: Only useful if you have C/C++ dependencies or complex unsafe codevalgrind-check:runs-on:ubuntu-latest# Only run on main branch (Valgrind is slow)if:github.ref == 'refs/heads/main'steps:- uses:actions/checkout@v4- uses:actions-rs/toolchain@v1with:toolchain:stableoverride:true# Build with debug symbols for better Valgrind output- name:Build with debug symbolsrun:cargo build# Install Valgrind- name:Install Valgrindrun:sudo apt-get update && sudo apt-get install -y valgrind# Run Valgrind on tests- name:Run Valgrind memory checkrun:| # Run each test binary through Valgrind
for test in target/debug/deps/*; do
if [ -x "$test" ] && [ ! -d "$test" ]; then
valgrind --leak-check=full \
--error-exitcode=1 \
--suppressions=valgrind.supp \
"$test" 2>&1 | tee -a valgrind.log
fi
done# This finds:# - Memory leaks (Rust usually doesn't have them)# - Invalid reads/writes in C/C++ dependencies# - Use of uninitialized memorycontinue-on-error:true# Don't fail the whole pipeline# Alternative: Run just the main binary- name:Run Valgrind on main binary (alternative)run:| valgrind --leak-check=full \
--error-exitcode=1 \
target/debug/smarthouse# Replace 'smarthouse' with your actual binary namecontinue-on-error:true# NEW JOB 7: Coverage Report (Optional)# Shows which parts of code are tested (complements Miri/Proptest)coverage:runs-on:ubuntu-lateststeps:- uses:actions/checkout@v4- name:Install cargo-tarpaulinrun:cargo install cargo-tarpaulin- name:Generate coverage reportrun:cargo tarpaulin --out Html --output-dir ./coverage- name:Upload coverage reportuses:actions/upload-artifact@v4with:name:coverage-reportpath:./coverage
Оптимизация CI для скорости
# Only run expensive jobs when unsafe code changesmiri-check:if:contains(github.event.pull_request.labels.*.name, 'unsafe-changes')# Cache dependencies to speed up builds- name:Cache cargo registryuses:actions/cache@v3with:path:~/.cargo/registrykey:${{ runner.os }}-cargo-${{ hashFiles('**/Cargo.lock') }}
Miri - это интерпретатор, который запускает код внутри особой безопасной песочницы. Miri даже не компилирует твою программу в реальный код. Он берёт твой Rust код и интерпретирует, проверяя:
Правила заимствования (одна ссылка на запись или много на чтение)
Выход за границы массива
Использование после перемещения
Miri понимает Rust: он знает, что такое &mut и почему два &mut на один объект — это плохо. Valgrind этого не поймёт. Miri замедляет работу кода в 100-1000х раз.
Valgrind запускает уже готовую скомпилированную программу и следит за ней со стороны:
Куда она обращается к памяти
Не забыла ли освободить то, что взяла
Не читает ли то, что уже удалила
Valgrind не знает правил Rust: он видит просто машинный код. Поэтому он может найти многие ошибки, но не понимает высокоуровневые концепции вроде заимствования или времени жизни. При этом Valgrind поддерживает проекты с миксом из Rust и C++ и т.д. языками. Valgrind замедляет работу кода в 20-50х раз.
Установка и запуск
Miri работает только в Nightly-версии Rust:
rustup toolchain install nightly
rustup +nightly component add miri
cargo +nightly miri testvalgrind ./my_program # программу уже скомпилировали
Проверка
Возьмём код с проблемами:
usestd::alloc::{alloc,dealloc,Layout};// BUG 1: Use-after-free (we'll read memory after freeing it)
fnuse_after_free()-> u8{letlayout=Layout::new::<u8>();letptr=unsafe{alloc(layout)};// Write something
unsafe{*ptr=42;}// Free the memory
unsafe{dealloc(ptr,layout);}// BUG: Reading after free! This is undefined behavior.
letvalue=unsafe{*ptr};value}// BUG 2: Memory leak (allocate but never free)
fnmemory_leak()-> *mutu8{letlayout=Layout::new::<u8>();letptr=unsafe{alloc(layout)};unsafe{*ptr=99;}// BUG: We never call dealloc! The memory leaks.
ptr}// BUG 3: Miri-specific - violating Rust's aliasing rules
fnaliasing_violation(){letmutx=5;letraw_ptr=&mutxas*muti32;// Create a second mutable pointer to the same memory
letraw_ptr2=&mutxas*muti32;unsafe{// Miri catches this: two mutable references to same data in scope
*raw_ptr=10;*raw_ptr2=20;// Undefined behavior per Rust's Stacked Borrows rules
}}fnmain(){println!("Testing use-after-free...");letval=use_after_free();println!("Read value: {} (this might be garbage)",val);println!("\nLeaking memory...");let_leaked=memory_leak();println!("Memory leaked successfully!");println!("\nViolating aliasing rules...");aliasing_violation();println!("Aliasing violation completed (somehow)");}#[cfg(test)]modtests{usesuper::*;#[test]fntest_use_after_free(){letval=use_after_free();// We're just calling it - the bug is inside
assert!(val==42||val!=42);// Always passes, but the UB is the problem
}#[test]fntest_aliasing_violation(){aliasing_violation();// Miri should catch this
}}
При запуске cargo test ошибок не будет -> тесты напишут Ок!.
В случае проверки с Miri, инструмент отловит проблемы Bug 1 и Bug 3. Утечки памяти не специализация Miri, может пропустить.
Bug 2 с утечкой памяти отловит Valgrind.
Когда что использовать?
Miri — когда ты пишешь:
unsafe код
Свои коллекции (Vec, HashMap)
FFI обёртки (здесь без unsafe не обойтись)
Хочешь поймать тонкие нарушения правил Rust
Valgrind — когда нужно:
Найти утечки памяти (Rust их почти не допускает, но C да)
Проверить программу на другом языке (C, C++, Fortran)
Отловить ошибки в уже готовом бинарнике, когда нет исходников
Proptest - это инструмент для тестирования на основе свойств (property-based testing). Он не проверяет память и не следит за выполнением кода. Вместо этого он автоматически генерирует тысячи случайных входных данных для твоей функции и проверяет, выполняются ли заданные тобой правила.
Пример работы
useproptest::prelude::*;// Функция, которая должна работать для любых чисел
fndivide(a: i32,b: i32)-> i32{a/b// Ой! А если b = 0? Будет паника!
}// Пишем свойство: "при любых a и b (где b != 0) деление работает"
proptest!{#[test]fntest_division_doesnt_panic(a: i32,bin0..1000){letresult=divide(a,b);prop_assert!(result.is_finite());}}
Для нахождения каких проблем нужен Proptest?
Тип проблемы
Пример
Крайние случаи
Деление на ноль, выход за границы массива
Неочевидные баги
Функция работает для 1, 2, 3, но ломается на 127 из-за переполнения
Нарушение инвариантов
“После сортировки длина массива не изменилась”
Алгоритмические ошибки
“Если я зашифрую и расшифрую — получится исходное сообщение”