mcpField
R
rust-quality

rust-quality

github.com/claude-dev-suiteテスト・品質4644 回インストール2 日前に更新
リポジトリ

rust-quality について

Rust プロジェクトの保守担当者向けのコード品質リファレンススキルです。一般的なツールを用いたフォーマット、Lint、IDE 分析、コードパターン、CI チェックをまとめています。

主な機能

  1. 01rustfmtのフォーマット指針
  2. 02ClippyのLint設定
  3. 03rust-analyzerによるIDE分析
  4. 04コードの問題点とエラー処理の例
  5. 05Pre-commitとMakefileの設定
  6. 06CI品質チェック

活用シーン

  1. 01フォーマットとLintルールの設定
  2. 02Rustコードの保守性の確認
  3. 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-analyzerIDE 解析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
}

品質指標の目標

指標目標ツール
認知的複雑度< 15Clippy
関数の行数< 50Clippy
引数の数< 5Clippy
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)] を追加するか、削除する

関連スキル

コメント