Day 6: 総合プロジェクト - 解答例

基本実装(必須機能)

use std::collections::HashMap;
use std::hash::Hash;
use std::sync::{Arc, Mutex};

/// スレッドセーフなインメモリKey-Valueデータベース
///
/// # Examples
///
/// 
/// use minidb::MiniDB; /// /// let db: MiniDB = MiniDB::new(); /// db.insert("age".to_string(), 25); /// assert_eq!(db.get(&"age".to_string()), Some(25)); ///
pub struct MiniDB<K, V> {
    /// Arcで共有、Mutexで排他制御、HashMapでデータ格納
    data: Arc<Mutex<HashMap<K, V>>>,
}

impl<K, V> MiniDB<K, V>
where
    K: Eq + Hash + Clone,
    V: Clone,
{
    /// 新しい空のデータベースを作成
    ///
    /// # Examples
    ///
    /// 
/// let db: MiniDB = MiniDB::new(); ///
    pub fn new() -> Self {
        MiniDB {
            data: Arc::new(Mutex::new(HashMap::new())),
        }
    }

    /// 指定された容量で新しいデータベースを作成
    ///
    /// # Examples
    ///
    /// 
/// let db: MiniDB = MiniDB::with_capacity(100); ///
    pub fn with_capacity(capacity: usize) -> Self {
        MiniDB {
            data: Arc::new(Mutex::new(HashMap::with_capacity(capacity))),
        }
    }

    /// キーと値のペアを挿入
    ///
    /// キーが既に存在する場合、値が上書きされます。
    ///
    /// # Examples
    ///
    /// 
/// let db = MiniDB::new(); /// db.insert("key", "value"); ///
    pub fn insert(&self, key: K, value: V) {
        // Mutexのロックを取得(他のスレッドはここでブロック)
        let mut map = self.data.lock().unwrap();

        // HashMapに挿入(キーと値の所有権を移動)
        map.insert(key, value);

        // スコープを抜けると自動的にロック解放
    }

    /// キーに対応する値を取得
    ///
    /// キーが存在しない場合は `None` を返します。
    ///
    /// # Examples
    ///
    /// 
/// let db = MiniDB::new(); /// db.insert("key", 42); /// assert_eq!(db.get(&"key"), Some(42)); /// assert_eq!(db.get(&"missing"), None); ///
    pub fn get(&self, key: &K) -> Option<V> {
        // イミュータブルなロックを取得(読み取り専用)
        let map = self.data.lock().unwrap();

        // HashMapから値を取得し、クローンを返す
        // get()は&Vを返すが、我々はVを返したいのでcloned()を使用
        map.get(key).cloned()
    }

    /// キーに対応する値を削除
    ///
    /// 削除した値を返します。キーが存在しない場合は `None` を返します。
    ///
    /// # Examples
    ///
    /// 
/// let db = MiniDB::new(); /// db.insert("key", 42); /// assert_eq!(db.remove(&"key"), Some(42)); /// assert_eq!(db.remove(&"key"), None); ///
    pub fn remove(&self, key: &K) -> Option<V> {
        // ミュータブルなロックを取得(書き込み用)
        let mut map = self.data.lock().unwrap();

        // HashMapから要素を削除し、値を返す
        map.remove(key)
    }

    /// 既存のキーの値を更新
    ///
    /// キーが存在する場合は `true`、存在しない場合は `false` を返します。
    ///
    /// # Examples
    ///
    /// 
/// let db = MiniDB::new(); /// db.insert("key", 1); /// assert_eq!(db.update(&"key", 2), true); /// assert_eq!(db.get(&"key"), Some(2)); /// assert_eq!(db.update(&"missing", 3), false); ///
    pub fn update(&self, key: &K, value: V) -> bool {
        let mut map = self.data.lock().unwrap();

        // キーが存在するかチェック
        if map.contains_key(key) {
            // 存在する場合は値を更新
            map.insert(key.clone(), value);
            true
        } else {
            // 存在しない場合は更新しない
            false
        }
    }

    /// データベース内の要素数を返す
    ///
    /// # Examples
    ///
    /// 
/// let db = MiniDB::new(); /// db.insert("a", 1); /// db.insert("b", 2); /// assert_eq!(db.len(), 2); ///
    pub fn len(&self) -> usize {
        let map = self.data.lock().unwrap();
        map.len()
    }

    /// データベースが空かどうかを確認
    ///
    /// # Examples
    ///
    /// 
/// let db: MiniDB = MiniDB::new(); /// assert!(db.is_empty()); /// db.insert("key".to_string(), 1); /// assert!(!db.is_empty()); ///
    pub fn is_empty(&self) -> bool {
        let map = self.data.lock().unwrap();
        map.is_empty()
    }

    /// すべてのデータをクリア
    ///
    /// # Examples
    ///
    /// 
/// let db = MiniDB::new(); /// db.insert("key", 1); /// db.clear(); /// assert!(db.is_empty()); ///
    pub fn clear(&self) {
        let mut map = self.data.lock().unwrap();
        map.clear();
    }
}

impl<K, V> Clone for MiniDB<K, V> {
    /// データベースのクローンを作成
    ///
    /// 注意: これは実際のデータをコピーするのではなく、
    /// 同じデータへの参照を共有します(Arc::clone)。
    ///
    /// # Examples
    ///
    /// 
/// let db1 = MiniDB::new(); /// db1.insert("key", 42); /// /// let db2 = db1.clone(); /// assert_eq!(db2.get(&"key"), Some(42)); ///
    fn clone(&self) -> Self {
        MiniDB {
            // Arcの参照カウントを増やすだけ(データはコピーしない)
            data: Arc::clone(&self.data),
        }
    }
}

impl<K, V> Default for MiniDB<K, V>
where
    K: Eq + Hash + Clone,
    V: Clone,
{
    fn default() -> Self {
        Self::new()
    }
}

// ===== テスト =====

#[cfg(test)]
mod tests {
    use super::*;
    use std::thread;
    use std::time::Duration;

    #[test]
    fn test_new() {
        let db: MiniDB<String, i32> = MiniDB::new();
        assert!(db.is_empty());
        assert_eq!(db.len(), 0);
    }

    #[test]
    fn test_insert_and_get() {
        let db = MiniDB::new();

        db.insert("name".to_string(), "Alice".to_string());
        db.insert("age".to_string(), "30".to_string());

        assert_eq!(db.get(&"name".to_string()), Some("Alice".to_string()));
        assert_eq!(db.get(&"age".to_string()), Some("30".to_string()));
        assert_eq!(db.get(&"missing".to_string()), None);
    }

    #[test]
    fn test_update() {
        let db: MiniDB<String, i32> = MiniDB::new();

        db.insert("counter".to_string(), 1);
        assert_eq!(db.get(&"counter".to_string()), Some(1));

        // 既存キーを更新
        assert!(db.update(&"counter".to_string(), 2));
        assert_eq!(db.get(&"counter".to_string()), Some(2));

        // 存在しないキーを更新(失敗)
        assert!(!db.update(&"missing".to_string(), 3));
        assert_eq!(db.get(&"missing".to_string()), None);
    }

    #[test]
    fn test_remove() {
        let db = MiniDB::new();

        db.insert("temp", 123);
        assert_eq!(db.get(&"temp"), Some(123));

        // 削除成功
        assert_eq!(db.remove(&"temp"), Some(123));
        assert_eq!(db.get(&"temp"), None);

        // 既に削除済み
        assert_eq!(db.remove(&"temp"), None);
    }

    #[test]
    fn test_len_and_clear() {
        let db = MiniDB::new();
        assert_eq!(db.len(), 0);

        db.insert("a", 1);
        db.insert("b", 2);
        db.insert("c", 3);
        assert_eq!(db.len(), 3);

        db.clear();
        assert_eq!(db.len(), 0);
        assert!(db.is_empty());
    }

    #[test]
    fn test_clone() {
        let db1 = MiniDB::new();
        db1.insert("shared", 42);

        let db2 = db1.clone();

        // db2から値を取得できる
        assert_eq!(db2.get(&"shared"), Some(42));

        // db2から挿入
        db2.insert("new", 100);

        // db1からも見える(同じデータを共有)
        assert_eq!(db1.get(&"new"), Some(100));
    }

    #[test]
    fn test_concurrent_insert() {
        let db: MiniDB<i32, i32> = MiniDB::new();
        let handles: Vec<_> = (0..10)
            .map(|i| {
                let db = db.clone();
                thread::spawn(move || {
                    for j in 0..100 {
                        db.insert(i * 100 + j, j);
                    }
                })
            })
            .collect();

        for handle in handles {
            handle.join().unwrap();
        }

        // 1000個の要素が挿入されている
        assert_eq!(db.len(), 1000);
    }

    #[test]
    fn test_concurrent_read_write() {
        let db = MiniDB::new();
        db.insert("counter", 0);

        let handles: Vec<_> = (0..5)
            .map(|_| {
                let db = db.clone();
                thread::spawn(move || {
                    for _ in 0..100 {
                        // 読み取り
                        let value = db.get(&"counter").unwrap_or(0);

                        // 書き込み
                        db.insert("counter", value + 1);

                        thread::sleep(Duration::from_micros(1));
                    }
                })
            })
            .collect();

        for handle in handles {
            handle.join().unwrap();
        }

        // データ競合がないことを確認
        assert!(db.get(&"counter").unwrap() > 0);
    }

    #[test]
    fn test_stress() {
        let db: MiniDB<i32, String> = MiniDB::new();
        let mut handles = vec![];

        // 書き込みスレッド
        for i in 0..5 {
            let db = db.clone();
            let handle = thread::spawn(move || {
                for j in 0..200 {
                    db.insert(i * 200 + j, format!("value-{}-{}", i, j));
                }
            });
            handles.push(handle);
        }

        // 読み取りスレッド
        for _ in 0..3 {
            let db = db.clone();
            let handle = thread::spawn(move || {
                for i in 0..1000 {
                    db.get(&i);
                }
            });
            handles.push(handle);
        }

        // 削除スレッド
        for _ in 0..2 {
            let db = db.clone();
            let handle = thread::spawn(move || {
                for i in (0..1000).step_by(10) {
                    db.remove(&i);
                }
            });
            handles.push(handle);
        }

        for handle in handles {
            handle.join().unwrap();
        }

        // データベースは正常に動作
        println!("Final size: {}", db.len());
    }
}

---

拡張実装(追加機能)

1. カスタムエラー型

use std::fmt;

#[derive(Debug, Clone, PartialEq)]
pub enum DBError {
    /// Mutexのロックが poisoned 状態
    LockPoisoned,
    /// キーが見つからない
    KeyNotFound,
    /// 無効な操作
    InvalidOperation(String),
}

impl fmt::Display for DBError {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        match self {
            DBError::LockPoisoned => write!(f, "Lock is poisoned"),
            DBError::KeyNotFound => write!(f, "Key not found"),
            DBError::InvalidOperation(msg) => write!(f, "Invalid operation: {}", msg),
        }
    }
}

impl std::error::Error for DBError {}

// Resultエイリアス
pub type Result<T> = std::result::Result<T, DBError>;

// エラー処理版のメソッド
impl<K, V> MiniDB<K, V>
where
    K: Eq + Hash + Clone,
    V: Clone,
{
    pub fn insert_checked(&self, key: K, value: V) -> Result<()> {
        let mut map = self.data.lock()
            .map_err(|_| DBError::LockPoisoned)?;

        map.insert(key, value);
        Ok(())
    }

    pub fn get_checked(&self, key: &K) -> Result<V> {
        let map = self.data.lock()
            .map_err(|_| DBError::LockPoisoned)?;

        map.get(key)
            .cloned()
            .ok_or(DBError::KeyNotFound)
    }
}

2. イテレータサポート

impl<K, V> MiniDB<K, V>
where
    K: Eq + Hash + Clone,
    V: Clone,
{
    /// すべてのキーのベクターを取得
    pub fn keys(&self) -> Vec<K> {
        let map = self.data.lock().unwrap();
        map.keys().cloned().collect()
    }

    /// すべての値のベクターを取得
    pub fn values(&self) -> Vec<V> {
        let map = self.data.lock().unwrap();
        map.values().cloned().collect()
    }

    /// すべてのキーバリューペアのベクターを取得
    pub fn entries(&self) -> Vec<(K, V)> {
        let map = self.data.lock().unwrap();
        map.iter()
            .map(|(k, v)| (k.clone(), v.clone()))
            .collect()
    }

    /// キーが存在するか確認
    pub fn contains_key(&self, key: &K) -> bool {
        let map = self.data.lock().unwrap();
        map.contains_key(key)
    }
}

#[test]
fn test_iteration() {
    let db = MiniDB::new();
    db.insert("a", 1);
    db.insert("b", 2);
    db.insert("c", 3);

    let keys = db.keys();
    assert_eq!(keys.len(), 3);
    assert!(keys.contains(&"a"));

    let values = db.values();
    assert_eq!(values.len(), 3);
    assert!(values.contains(&1));

    let entries = db.entries();
    assert_eq!(entries.len(), 3);
}

3. 条件付き操作

impl<K, V> MiniDB<K, V>
where
    K: Eq + Hash + Clone,
    V: Clone + PartialEq,
{
    /// 値が一致する場合のみ挿入
    pub fn insert_if_absent(&self, key: K, value: V) -> bool {
        let mut map = self.data.lock().unwrap();

        if !map.contains_key(&key) {
            map.insert(key, value);
            true
        } else {
            false
        }
    }

    /// 古い値と一致する場合のみ更新(Compare-And-Swap)
    pub fn compare_and_swap(&self, key: &K, old_value: V, new_value: V) -> bool {
        let mut map = self.data.lock().unwrap();

        if map.get(key) == Some(&old_value) {
            map.insert(key.clone(), new_value);
            true
        } else {
            false
        }
    }
}

---

まとめ

このMiniDBの実装は、Day 1-5で学んだ全ての概念を統合しています:

Day 概念 実装での使用
1 所有権 `insert`で所有権を受け取る
2 借用 `get`で`&K`を借用
3 エラー処理 `Result`と`Option`を返す
4 スマートポインタ `Arc`で共有
5 並行処理 `Mutex`でスレッドセーフ

おめでとうございます!Rust Piscineを完了しました!