agpm-cli 0.4.11

AGent Package Manager - A Git-based package manager for coding agents
Documentation
---
name: rust-patterns
description: Common Rust patterns and idioms for writing idiomatic code
tags: [rust, patterns, best-practices]
---

# Rust Patterns and Idioms

## Error Handling

### Result Type Pattern
```rust
fn parse_config(path: &Path) -> Result<Config, ConfigError> {
    let contents = fs::read_to_string(path)
        .map_err(ConfigError::IoError)?;

    toml::from_str(&contents)
        .map_err(ConfigError::ParseError)
}
```

### Custom Error Types
```rust
#[derive(Debug, thiserror::Error)]
pub enum ConfigError {
    #[error("Failed to read config file: {0}")]
    IoError(#[from] std::io::Error),

    #[error("Failed to parse config: {0}")]
    ParseError(#[from] toml::de::Error),
}
```

## Builder Pattern

```rust
pub struct Client {
    url: String,
    timeout: Duration,
    retry_count: u32,
}

impl Client {
    pub fn builder() -> ClientBuilder {
        ClientBuilder::default()
    }
}

#[derive(Default)]
pub struct ClientBuilder {
    url: Option<String>,
    timeout: Option<Duration>,
    retry_count: Option<u32>,
}

impl ClientBuilder {
    pub fn url(mut self, url: String) -> Self {
        self.url = Some(url);
        self
    }

    pub fn timeout(mut self, timeout: Duration) -> Self {
        self.timeout = Some(timeout);
        self
    }

    pub fn build(self) -> Result<Client, &'static str> {
        Ok(Client {
            url: self.url.ok_or("URL is required")?,
            timeout: self.timeout.unwrap_or(Duration::from_secs(30)),
            retry_count: self.retry_count.unwrap_or(3),
        })
    }
}
```

## Newtype Pattern

```rust
pub struct UserId(u64);
pub struct OrderId(u64);

impl UserId {
    pub fn new(id: u64) -> Self {
        Self(id)
    }

    pub fn as_u64(&self) -> u64 {
        self.0
    }
}

// Prevents accidental mixing of different ID types
fn get_user(id: UserId) -> User {
    // Can't accidentally pass OrderId here
}
```

## Option Combinators

```rust
// Instead of nested if-let
fn process_data(data: Option<String>) -> Option<usize> {
    data.as_ref()
        .filter(|s| !s.is_empty())
        .map(|s| s.len())
}

// Using and_then for chaining
fn get_user_email(user_id: UserId) -> Option<String> {
    get_user(user_id)
        .and_then(|user| user.email)
}
```

## Iterator Patterns

```rust
// Collect into specific types
let numbers: Vec<i32> = (1..=10).collect();
let set: HashSet<_> = numbers.iter().collect();

// Chain iterators
let combined: Vec<_> = vec1.iter()
    .chain(vec2.iter())
    .filter(|&&x| x > 0)
    .collect();

// Partition based on predicate
let (evens, odds): (Vec<_>, Vec<_>) = numbers
    .into_iter()
    .partition(|&n| n % 2 == 0);
```

## RAII Pattern

```rust
pub struct FileGuard {
    path: PathBuf,
}

impl FileGuard {
    pub fn new(path: PathBuf) -> io::Result<Self> {
        File::create(&path)?;
        Ok(Self { path })
    }
}

impl Drop for FileGuard {
    fn drop(&mut self) {
        let _ = fs::remove_file(&self.path);
    }
}
```

## Type State Pattern

```rust
pub struct Connection<State> {
    addr: String,
    state: PhantomData<State>,
}

pub struct Disconnected;
pub struct Connected;

impl Connection<Disconnected> {
    pub fn new(addr: String) -> Self {
        Connection {
            addr,
            state: PhantomData,
        }
    }

    pub fn connect(self) -> Result<Connection<Connected>, Error> {
        // Connect logic
        Ok(Connection {
            addr: self.addr,
            state: PhantomData,
        })
    }
}

impl Connection<Connected> {
    pub fn send(&self, data: &[u8]) -> Result<(), Error> {
        // Can only send when connected
        Ok(())
    }
}
```