AgentSkillsCN

rust-patterns

ECSdb中使用的Rust模式与约定

SKILL.md
--- frontmatter
name: rust-patterns
description: Rust patterns and conventions used in ECSdb

ECSdb Rust Patterns

This guide covers the Rust patterns and conventions used throughout ECSdb.

Error Handling

Error Types

Each crate defines its own error type:

rust
use thiserror::Error;

#[derive(Debug, Error)]
pub enum StorageError {
    #[error("key not found")]
    NotFound,

    #[error("column family not found: {0}")]
    ColumnFamilyNotFound(String),

    #[error("I/O error: {0}")]
    Io(#[from] std::io::Error),

    #[error("backend error: {0}")]
    Backend(String),
}

pub type StorageResult<T> = Result<T, StorageError>;

Error Conversion

Use #[from] for automatic conversion:

rust
#[derive(Debug, Error)]
pub enum QueryError {
    #[error("storage error: {0}")]
    Storage(#[from] StorageError),

    #[error("parse error: {0}")]
    Parse(String),
}

The ? Operator

Prefer ? over unwrap():

rust
// Good
fn process() -> Result<Value, Error> {
    let data = storage.get(key)?;
    let parsed = parse(data)?;
    Ok(transform(parsed))
}

// Bad
fn process() -> Value {
    let data = storage.get(key).unwrap();  // Panic risk!
    parse(data).unwrap()
}

Async Patterns

Async Traits

Use async-trait for async trait methods:

rust
use async_trait::async_trait;

#[async_trait]
pub trait StorageEngine: Send + Sync {
    async fn get(&self, cf: &str, key: &[u8]) -> Result<Option<Vec<u8>>>;
    async fn put(&self, cf: &str, key: &[u8], value: &[u8]) -> Result<()>;
}

Spawning Tasks

Use tokio for async runtime:

rust
use tokio::spawn;

async fn background_work() {
    spawn(async move {
        // Background task
    });
}

Serialization

MessagePack for Data

Use rmp-serde for efficient binary serialization:

rust
use serde::{Deserialize, Serialize};

#[derive(Debug, Serialize, Deserialize)]
pub struct ComponentData {
    pub entity_id: Uuid,
    pub version: u64,
    pub data: Vec<u8>,
}

impl ComponentData {
    pub fn to_bytes(&self) -> Vec<u8> {
        rmp_serde::to_vec(self).expect("serialization should not fail")
    }

    pub fn from_bytes(bytes: &[u8]) -> Result<Self, rmp_serde::decode::Error> {
        rmp_serde::from_slice(bytes)
    }
}

JSON for Queries

Use serde_json for query parsing:

rust
use serde_json::Value;

pub fn parse_query(json: &str) -> Result<QueryAst, ParseError> {
    let value: Value = serde_json::from_str(json)?;
    // Parse into AST
}

Type Patterns

Newtype Pattern

Wrap primitive types for type safety:

rust
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct EntityId(pub Uuid);

#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct ComponentTypeId(pub u32);

impl ComponentTypeId {
    pub fn from_name(name: &str) -> Self {
        use std::hash::{Hash, Hasher};
        let mut hasher = std::collections::hash_map::DefaultHasher::new();
        name.hash(&mut hasher);
        Self(hasher.finish() as u32)
    }
}

Builder Pattern

For complex configuration:

rust
pub struct ConfigBuilder {
    data_dir: PathBuf,
    cache_size: usize,
}

impl ConfigBuilder {
    pub fn new(data_dir: PathBuf) -> Self {
        Self {
            data_dir,
            cache_size: 256 * 1024 * 1024,  // Default 256MB
        }
    }

    pub fn cache_size(mut self, size: usize) -> Self {
        self.cache_size = size;
        self
    }

    pub fn build(self) -> Config {
        Config {
            data_dir: self.data_dir,
            cache_size: self.cache_size,
        }
    }
}

Testing Patterns

Test Organization

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

    // Test fixtures
    fn create_test_entity() -> Entity {
        Entity::new(Uuid::new_v4())
    }

    // Unit tests
    #[test]
    fn test_entity_creation() {
        let entity = create_test_entity();
        assert!(entity.id != Uuid::nil());
    }

    // Async tests
    #[tokio::test]
    async fn test_async_operation() {
        let result = async_fn().await;
        assert!(result.is_ok());
    }
}

Test Helpers

Create helper modules for common test utilities:

rust
// tests/common/mod.rs
pub fn setup_test_db() -> TestDb {
    let temp_dir = TempDir::new().unwrap();
    TestDb::new(temp_dir)
}

pub struct TestDb {
    _temp_dir: TempDir,  // Keep alive
    pub engine: FjallStorageEngine,
}

Concurrency Patterns

Thread Safety

Use Arc for shared ownership, RwLock for interior mutability:

rust
use std::sync::{Arc, RwLock};

pub struct Manager {
    state: Arc<RwLock<State>>,
}

impl Manager {
    pub fn read_state(&self) -> impl Deref<Target = State> + '_ {
        self.state.read().unwrap()
    }

    pub fn write_state(&self) -> impl DerefMut<Target = State> + '_ {
        self.state.write().unwrap()
    }
}

Atomic Operations

For simple counters and flags:

rust
use std::sync::atomic::{AtomicU64, Ordering};

pub struct TransactionManager {
    next_xid: AtomicU64,
}

impl TransactionManager {
    pub fn allocate_xid(&self) -> u64 {
        self.next_xid.fetch_add(1, Ordering::SeqCst)
    }
}

Documentation Patterns

Module Documentation

rust
//! # Storage Engine
//!
//! This module provides the storage abstraction layer for ECSdb.
//!
//! ## Overview
//!
//! The storage engine uses fjall as the underlying LSM-tree.
//!
//! ## Example
//!
//! ```rust
//! let engine = FjallStorageEngine::open(config)?;
//! engine.put("cf", b"key", b"value").await?;
//! ```

Item Documentation

rust
/// A single component attached to an entity.
///
/// Components are stored in per-type column families with versioning
/// for temporal queries.
///
/// # Example
///
/// ```rust
/// let component = ComponentData::new(entity_id, "Position", data, 1, txn_id);
/// ```
pub struct ComponentData {
    /// The entity this component belongs to
    pub entity_id: Uuid,
    // ...
}