スキル一覧に戻る
eous

rust-best-practices

by eous

0🍴 0📅 2025年12月9日
GitHubで見るManusで実行

SKILL.md


name: rust-best-practices description: Rust development best practices, patterns, and conventions. Use when writing Rust code, reviewing .rs files, discussing ownership, lifetimes, borrowing, or Cargo configuration. Triggers on mentions of Rust, Cargo, ownership, borrowing, lifetimes, traits, async Rust, tokio.

Rust Best Practices

Ownership and Borrowing

Prefer Borrowing Over Ownership

// Bad - takes ownership unnecessarily
fn print_name(name: String) {
    println!("{}", name);
}

// Good - borrows immutably
fn print_name(name: &str) {
    println!("{}", name);
}

Use References Appropriately

// Immutable borrow for reading
fn calculate_length(s: &String) -> usize {
    s.len()
}

// Mutable borrow for modification
fn push_char(s: &mut String, c: char) {
    s.push(c);
}

Error Handling

Use Result and Option

fn find_user(id: u32) -> Option<User> {
    users.get(&id).cloned()
}

fn parse_config(path: &str) -> Result<Config, ConfigError> {
    let content = fs::read_to_string(path)?;
    toml::from_str(&content).map_err(ConfigError::Parse)
}

Custom Error Types

use thiserror::Error;

#[derive(Error, Debug)]
pub enum AppError {
    #[error("database error: {0}")]
    Database(#[from] sqlx::Error),

    #[error("not found: {0}")]
    NotFound(String),

    #[error("validation error: {field}")]
    Validation { field: String },
}

Propagate with ?

fn process_file(path: &str) -> Result<Data, Error> {
    let content = fs::read_to_string(path)?;
    let parsed = serde_json::from_str(&content)?;
    let validated = validate(parsed)?;
    Ok(validated)
}

Structs and Enums

Builder Pattern

#[derive(Default)]
pub struct ServerBuilder {
    port: Option<u16>,
    host: Option<String>,
}

impl ServerBuilder {
    pub fn port(mut self, port: u16) -> Self {
        self.port = Some(port);
        self
    }

    pub fn host(mut self, host: impl Into<String>) -> Self {
        self.host = Some(host.into());
        self
    }

    pub fn build(self) -> Result<Server, BuildError> {
        Ok(Server {
            port: self.port.unwrap_or(8080),
            host: self.host.unwrap_or_else(|| "localhost".into()),
        })
    }
}

Newtype Pattern

pub struct UserId(u64);
pub struct Email(String);

impl Email {
    pub fn new(email: String) -> Result<Self, ValidationError> {
        if email.contains('@') {
            Ok(Self(email))
        } else {
            Err(ValidationError::InvalidEmail)
        }
    }
}

Enums for State

enum ConnectionState {
    Disconnected,
    Connecting { attempt: u32 },
    Connected { session_id: String },
    Error { message: String },
}

Traits

Implement Standard Traits

#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct User {
    pub id: u64,
    pub name: String,
}

Trait Objects vs Generics

// Static dispatch (monomorphization)
fn process<T: Handler>(handler: T) { ... }

// Dynamic dispatch (trait object)
fn process(handler: &dyn Handler) { ... }
fn process(handler: Box<dyn Handler>) { ... }

Extension Traits

pub trait StringExt {
    fn truncate_ellipsis(&self, max_len: usize) -> String;
}

impl StringExt for str {
    fn truncate_ellipsis(&self, max_len: usize) -> String {
        if self.len() <= max_len {
            self.to_string()
        } else {
            format!("{}...", &self[..max_len - 3])
        }
    }
}

Lifetimes

Elision Rules

// Lifetimes elided - single input reference
fn first_word(s: &str) -> &str { ... }

// Explicit when multiple inputs
fn longest<'a>(x: &'a str, y: &'a str) -> &'a str { ... }

Struct Lifetimes

struct Parser<'a> {
    input: &'a str,
    position: usize,
}

impl<'a> Parser<'a> {
    fn new(input: &'a str) -> Self {
        Self { input, position: 0 }
    }
}

Async Rust

Tokio Patterns

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    let result = fetch_data().await?;
    Ok(())
}

async fn fetch_data() -> Result<Data, Error> {
    let client = reqwest::Client::new();
    let response = client.get(URL).send().await?;
    response.json().await.map_err(Into::into)
}

Concurrent Operations

use futures::future::join_all;

async fn fetch_all(urls: Vec<String>) -> Vec<Result<Response, Error>> {
    let futures: Vec<_> = urls.into_iter()
        .map(|url| fetch_one(url))
        .collect();

    join_all(futures).await
}

Testing

Unit Tests

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_add() {
        assert_eq!(add(2, 2), 4);
    }

    #[test]
    #[should_panic(expected = "division by zero")]
    fn test_divide_by_zero() {
        divide(1, 0);
    }
}

Integration Tests

// tests/integration_test.rs
use mylib::process;

#[test]
fn test_full_workflow() {
    let result = process("input");
    assert!(result.is_ok());
}

Performance

Avoid Unnecessary Allocations

// Bad - allocates new String
fn process(s: &str) -> String {
    s.to_uppercase()
}

// Good when possible - use Cow
use std::borrow::Cow;
fn process(s: &str) -> Cow<str> {
    if s.chars().any(|c| c.is_lowercase()) {
        Cow::Owned(s.to_uppercase())
    } else {
        Cow::Borrowed(s)
    }
}

Use Iterators

// Good - lazy, zero-cost
let sum: i32 = numbers.iter()
    .filter(|n| **n > 0)
    .map(|n| n * 2)
    .sum();

Anti-Patterns to Avoid

  • .unwrap() in production code
  • Excessive .clone() to "fix" borrow checker
  • unsafe without clear justification
  • Ignoring compiler warnings
  • Not using clippy
  • Global mutable state (lazy_static with Mutex)

スコア

総合スコア

50/100

リポジトリの品質指標に基づく評価

SKILL.md

SKILL.mdファイルが含まれている

+20
LICENSE

ライセンスが設定されている

0/10
説明文

100文字以上の説明がある

0/10
人気

GitHub Stars 100以上

0/15
最近の活動

3ヶ月以内に更新がある

0/10
フォーク

10回以上フォークされている

0/5
Issue管理

オープンIssueが50未満

+5
言語

プログラミング言語が設定されている

+5
タグ

1つ以上のタグが設定されている

0/5

レビュー

💬

レビュー機能は近日公開予定です