rust-quality について
Rust プロジェクトの保守担当者向けのコード品質リファレンススキルです。一般的なツールを用いたフォーマット、Lint、IDE 分析、コードパターン、CI チェックをまとめています。
主な機能
- 01rustfmtのフォーマット指針
- 02ClippyのLint設定
- 03rust-analyzerによるIDE分析
- 04コードの問題点とエラー処理の例
- 05Pre-commitとMakefileの設定
- 06CI品質チェック
活用シーン
- 01フォーマットとLintルールの設定
- 02Rustコードの保守性の確認
- 03CIへの品質チェック追加
MCP 対応
READMEでは、Rustの包括的なドキュメントの取得先として mcp__documentation__fetch_docs を案内しています。
インストール
コマンドをコピーしてターミナルで実行します。
$
npx skills add https:/ / github.com/ claude-dev-suite/ claude-dev-suite --skill rust-qualityインストール後、Claude Code で/rust-qualityを入力して読み込まれたか確認します。
詳細
Rust の品質 — クイックリファレンス
このスキルを使用しない場合
- SonarQube のセットアップ —
sonarqubeスキルを使用してください - セキュリティスキャン —
rust-securityスキルを使用してください
詳細な知識:包括的なドキュメントを参照するには、technology:
rustを指定してmcp__documentation__fetch_docsを使用してください。
ツールの概要
| ツール | 用途 | コマンド |
|---|---|---|
| rustfmt | フォーマット | cargo fmt |
| Clippy | 静的解析 | cargo clippy |
| rust-analyzer | IDE 解析 | LSP |
| cargo-deny | 依存関係ポリシー | cargo deny |
| cargo-audit | セキュリティ監査 | cargo audit |
Clippy のセットアップ
Clippy の実行
# Basic check
cargo clippy
# Strict mode - deny all warnings
cargo clippy -- -D warnings
# With pedantic lints
cargo clippy -- -W clippy::pedantic
# Fix auto-fixable
cargo clippy --fix
# All targets (tests, benches, examples)
cargo clippy --all-targets --all-features
clippy.toml
# Maximum cognitive complexity
cognitive-complexity-threshold = 15
# Maximum function length
too-many-lines-threshold = 50
# Maximum arguments
too-many-arguments-threshold = 5
# Allowed wildcard imports
allowed-wildcard-imports = ["crate::prelude::*"]
Cargo.toml の Lint 設定
[lints.rust]
unsafe_code = "deny"
missing_docs = "warn"
[lints.clippy]
# Categories
pedantic = { level = "warn", priority = -1 }
nursery = { level = "warn", priority = -1 }
# Specific lints
unwrap_used = "warn"
expect_used = "warn"
panic = "warn"
todo = "warn"
dbg_macro = "warn"
# Allow specific pedantic lints
module_name_repetitions = "allow"
must_use_candidate = "allow"
CI の設定
# Strict CI check
cargo clippy --all-targets --all-features -- \
-D warnings \
-D clippy::pedantic \
-D clippy::nursery \
-A clippy::module_name_repetitions
rustfmt のセットアップ
rustfmt.toml
edition = "2021"
max_width = 100
tab_spaces = 4
newline_style = "Unix"
# Imports
imports_granularity = "Module"
group_imports = "StdExternalCrate"
reorder_imports = true
# Items
reorder_modules = true
reorder_impl_items = true
# Formatting
use_small_heuristics = "Default"
fn_single_line = false
where_single_line = false
struct_lit_single_line = true
# Comments
comment_width = 100
wrap_comments = true
normalize_comments = true
# Macros
format_macro_matchers = true
format_macro_bodies = true
コマンド
# Format all
cargo fmt
# Check without changing
cargo fmt -- --check
# Format specific file
rustfmt src/main.rs
よく使われる Clippy lint
unwrap_used / expect_used
// BAD - Panics on None/Err
let value = some_option.unwrap();
let result = some_result.expect("should work");
// GOOD - Handle errors properly
let value = some_option.ok_or(MyError::NotFound)?;
let result = some_result.map_err(|e| MyError::from(e))?;
// GOOD - When panic is intentional (with justification)
let value = config.get("required_key")
.expect("required_key must be set in configuration");
clone_on_ref_ptr
// BAD - Cloning Arc/Rc unnecessarily
let clone = arc_value.clone();
// GOOD - Use Arc::clone for clarity
let clone = Arc::clone(&arc_value);
needless_pass_by_value
// BAD - Takes ownership unnecessarily
fn process(data: String) {
println!("{}", data);
}
// GOOD - Borrow instead
fn process(data: &str) {
println!("{}", data);
}
cognitive_complexity
// BAD - Too complex
fn process(data: &Data) -> Result<Output, Error> {
if data.is_valid() {
if data.type_a() {
if data.has_value() {
// deep nesting...
}
}
}
// ... more conditions
}
// GOOD - Extract and simplify
fn process(data: &Data) -> Result<Output, Error> {
validate(data)?;
match data.data_type() {
DataType::A => process_type_a(data),
DataType::B => process_type_b(data),
}
}
missing_errors_doc
// BAD - No error documentation
/// Processes the data.
pub fn process(data: &Data) -> Result<Output, Error> { ... }
// GOOD - Document errors
/// Processes the data.
///
/// # Errors
///
/// Returns `Error::InvalidData` if the data is malformed.
/// Returns `Error::NotFound` if the resource doesn't exist.
pub fn process(data: &Data) -> Result<Output, Error> { ... }
よくあるコードの問題と修正方法
1. 文字列ベースの型付け
// BAD - Using strings for types
fn set_status(status: &str) {
match status {
"active" => { ... }
"inactive" => { ... }
_ => panic!("unknown status"),
}
}
// GOOD - Use enums
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Status {
Active,
Inactive,
}
fn set_status(status: Status) {
match status {
Status::Active => { ... }
Status::Inactive => { ... }
}
}
2. エラー処理
// BAD - Using unwrap in library code
pub fn parse_config(path: &Path) -> Config {
let content = fs::read_to_string(path).unwrap();
serde_json::from_str(&content).unwrap()
}
// GOOD - Proper error handling with thiserror
#[derive(Debug, thiserror::Error)]
pub enum ConfigError {
#[error("failed to read config file: {0}")]
Io(#[from] std::io::Error),
#[error("failed to parse config: {0}")]
Parse(#[from] serde_json::Error),
}
pub fn parse_config(path: &Path) -> Result<Config, ConfigError> {
let content = fs::read_to_string(path)?;
let config = serde_json::from_str(&content)?;
Ok(config)
}
3. ビルダーパターン
// BAD - Constructor with many parameters
impl Server {
pub fn new(
host: String,
port: u16,
max_connections: usize,
timeout: Duration,
tls_config: Option<TlsConfig>,
) -> Self { ... }
}
// GOOD - Builder pattern
#[derive(Default)]
pub struct ServerBuilder {
host: String,
port: u16,
max_connections: usize,
timeout: Duration,
tls_config: Option<TlsConfig>,
}
impl ServerBuilder {
pub fn host(mut self, host: impl Into<String>) -> Self {
self.host = host.into();
self
}
pub fn port(mut self, port: u16) -> Self {
self.port = port;
self
}
pub fn build(self) -> Result<Server, BuildError> {
// Validate and build
}
}
// Usage
let server = Server::builder()
.host("localhost")
.port(8080)
.build()?;
4. Newtype パターン
// BAD - Primitive obsession
fn create_user(email: String, name: String, age: u32) { ... }
// GOOD - Newtypes for validation
#[derive(Debug, Clone)]
pub struct Email(String);
impl Email {
pub fn new(value: impl Into<String>) -> Result<Self, ValidationError> {
let value = value.into();
if !value.contains('@') {
return Err(ValidationError::InvalidEmail);
}
Ok(Self(value))
}
pub fn as_str(&self) -> &str {
&self.0
}
}
fn create_user(email: Email, name: Name, age: Age) { ... }
5. clone() の乱用を避ける
// BAD - Cloning everywhere
fn process(data: &Vec<Item>) {
let owned = data.clone();
for item in owned {
// process
}
}
// GOOD - Borrow when possible
fn process(data: &[Item]) {
for item in data {
// process
}
}
// GOOD - Take ownership when needed
fn process(data: Vec<Item>) {
for item in data {
// consume item
}
}
pre-commit のセットアップ
.pre-commit-config.yaml
repos:
- repo: local
hooks:
- id: cargo-fmt
name: cargo fmt
entry: cargo fmt --
language: system
types: [rust]
- id: cargo-clippy
name: cargo clippy
entry: cargo clippy --all-targets --all-features -- -D warnings
language: system
types: [rust]
pass_filenames: false
- id: cargo-test
name: cargo test
entry: cargo test
language: system
types: [rust]
pass_filenames: false
Makefile
.PHONY: fmt lint test check quality
fmt:
cargo fmt
lint:
cargo clippy --all-targets --all-features -- -D warnings
test:
cargo test
check:
cargo check --all-targets --all-features
quality: fmt check lint test
VS Code の設定
// .vscode/settings.json
{
"[rust]": {
"editor.defaultFormatter": "rust-lang.rust-analyzer",
"editor.formatOnSave": true
},
"rust-analyzer.check.command": "clippy",
"rust-analyzer.check.extraArgs": ["--all-targets", "--all-features"],
"rust-analyzer.diagnostics.disabled": [],
"rust-analyzer.lens.run.enable": true,
"rust-analyzer.lens.debug.enable": true
}
品質指標の目標
| 指標 | 目標 | ツール |
|---|---|---|
| 認知的複雑度 | < 15 | Clippy |
| 関数の行数 | < 50 | Clippy |
| 引数の数 | < 5 | Clippy |
| unsafe ブロック | 最小限にする | Clippy |
| テストカバレッジ | > 80% | cargo-tarpaulin |
CI/CD との統合
GitHub Actions
name: Quality
on: [push, pull_request]
jobs:
quality:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Install Rust
uses: dtolnay/rust-toolchain@stable
with:
components: rustfmt, clippy
- name: Cache cargo
uses: Swatinem/rust-cache@v2
- name: Check formatting
run: cargo fmt -- --check
- name: Clippy
run: cargo clippy --all-targets --all-features -- -D warnings
- name: Run tests
run: cargo test --all-features
- name: Build docs
run: cargo doc --no-deps
env:
RUSTDOCFLAGS: -D warnings
アンチパターン
| アンチパターン | 問題点 | 適切な方法 |
|---|---|---|
ライブラリコードでの unwrap() | パニックが伝播します | ? 演算子を使用する |
過剰な clone() | パフォーマンスに影響します | 可能な場合は借用する |
#[allow(clippy::all)] | すべての問題が隠れてしまいます | 特定の lint のみ許可する |
コメントのない unsafe | 安全性が不明確になります | 不変条件を文書化する |
| あらゆる用途で文字列を使用する | 型安全性がありません | enum または newtype を使用する |
| 巨大な関数 | テストや保守が困難です | 小さな関数に分割する |
よくある問題の解決方法
| 問題 | 考えられる原因 | 解決策 |
|---|---|---|
| Clippy の誤検知 | エッジケース、または意図した動作 | コメントを添えて #[allow(clippy::lint)] を使用する |
| rustfmt がコードを変更する | フォーマットの方針 | rustfmt.toml を設定する |
| lint の競合 | Pedantic と nursery の競合 | Cargo.toml で優先順位を設定する |
| コンパイルが遅い | 依存関係が多い | キャッシュに cargo-chef を使用する |
| デッドコードの警告 | エクスポートが未使用 | #[cfg(test)] を追加するか、削除する |
関連スキル
コメント
Markdown 対応