Day 6: 総合プロジェクト - 解答例
基本実装(必須機能)
use std::collections::HashMap;
use std::hash::Hash;
use std::sync::{Arc, Mutex};
/// スレッドセーフなインメモリKey-Valueデータベース
///
/// # Examples
///
///
/// use minidb::MiniDB;
///
/// let db: MiniDBpub 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 pub fn new() -> Self {
MiniDB {
data: Arc::new(Mutex::new(HashMap::new())),
}
}
/// 指定された容量で新しいデータベースを作成
///
/// # Examples
///
///
/// let db: MiniDB 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 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を完了しました!