You've already forked rust-tutor
generic types, traits and lifetimes
- get claude to create rustlings style exercises based on chapter 10 of the book - update CLAUDE.md to prevent it from producing incorrect exercises - complete exercises/01-generic-cache-system
This commit is contained in:
6
exercises/01-generic-cache-system/Cargo.toml
Normal file
6
exercises/01-generic-cache-system/Cargo.toml
Normal file
@@ -0,0 +1,6 @@
|
||||
[package]
|
||||
name = "generic_function_toolkit"
|
||||
version = "0.1.0"
|
||||
edition = "2024"
|
||||
|
||||
[dependencies]
|
||||
15
exercises/01-generic-cache-system/README.md
Normal file
15
exercises/01-generic-cache-system/README.md
Normal file
@@ -0,0 +1,15 @@
|
||||
# Generic Cache System
|
||||
|
||||
Build a simple key-value cache that can store any types. You'll write generic functions that manage cached data, demonstrating how generics let you write one implementation that works with strings, numbers, or any other type.
|
||||
|
||||
## What you'll practice
|
||||
|
||||
- Writing generic functions with multiple type parameters `<K, V>`
|
||||
- Using trait bounds to constrain what types can be used
|
||||
- Working with `HashMap<K, V>` and generic collections
|
||||
- Combining `Option<T>` and `Result<T, E>` with generics
|
||||
|
||||
## Further information
|
||||
|
||||
- [Generic Data Types](https://doc.rust-lang.org/book/ch10-01-syntax.html)
|
||||
- [Using Trait Bounds](https://doc.rust-lang.org/book/ch10-02-traits.html#using-trait-bounds-to-conditionally-implement-methods)
|
||||
112
exercises/01-generic-cache-system/src/main.rs
Normal file
112
exercises/01-generic-cache-system/src/main.rs
Normal file
@@ -0,0 +1,112 @@
|
||||
use std::collections::HashMap;
|
||||
use std::hash::Hash;
|
||||
|
||||
fn cache_get<K: Eq + Hash, V>(map: &HashMap<K, V>, key: K) -> Option<&V> {
|
||||
for (k, v) in map.iter() {
|
||||
if *k == key {
|
||||
return Some(&v);
|
||||
}
|
||||
}
|
||||
return None;
|
||||
}
|
||||
|
||||
fn cache_insert<K: Eq + Hash, V: Copy>(cache: &mut HashMap<K, V>, key: K, value: V) -> Option<V> {
|
||||
for (k, v) in cache.iter() {
|
||||
let return_value = *v;
|
||||
if *k == key {
|
||||
//update value
|
||||
cache.insert(key, value);
|
||||
}
|
||||
//"there was an existing record with this key which we wrote over"
|
||||
//"and the key we wrote over was return_value"
|
||||
return Some(return_value);
|
||||
}
|
||||
// insert new value
|
||||
cache.insert(key, value);
|
||||
// "there wasnt an existing record with this key"
|
||||
return None;
|
||||
}
|
||||
|
||||
fn cache_compute_if_absent<K: Eq + Hash, V, F>(cache: &mut HashMap<K, V>, key: K, compute: F) -> &V
|
||||
where
|
||||
F: Fn() -> V,
|
||||
{
|
||||
cache.entry(key).or_insert_with(compute)
|
||||
}
|
||||
|
||||
fn batch_lookup<'a, K: Eq + Hash, V>(cache: &'a HashMap<K, V>, keys: &[K]) -> Vec<&'a V> {
|
||||
let mut result = Vec::new();
|
||||
|
||||
for key in keys {
|
||||
if let Some(value) = cache.get(key) {
|
||||
result.push(value);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
fn main() {
|
||||
println!("let's go!");
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_cache_get_found() {
|
||||
let mut cache = HashMap::new();
|
||||
cache.insert("key1", 42);
|
||||
assert_eq!(cache_get(&cache, &"key1"), Some(&42));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_cache_get_missing() {
|
||||
let cache: HashMap<&str, i32> = HashMap::new();
|
||||
assert_eq!(cache_get(&cache, &"missing"), None);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_cache_insert_new() {
|
||||
let mut cache = HashMap::new();
|
||||
assert_eq!(cache_insert(&mut cache, "new", 100), None);
|
||||
assert_eq!(cache.get("new"), Some(&100));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_cache_insert_existing() {
|
||||
let mut cache = HashMap::new();
|
||||
cache.insert("key", 50);
|
||||
assert_eq!(cache_insert(&mut cache, "key", 75), Some(50));
|
||||
assert_eq!(cache.get("key"), Some(&75));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_compute_if_absent_missing() {
|
||||
let mut cache = HashMap::new();
|
||||
let result = cache_compute_if_absent(&mut cache, "compute", || "computed".to_string());
|
||||
assert_eq!(result, &"computed".to_string());
|
||||
assert_eq!(cache.get("compute"), Some(&"computed".to_string()));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_compute_if_absent_exists() {
|
||||
let mut cache = HashMap::new();
|
||||
cache.insert("existing", "original".to_string());
|
||||
let result =
|
||||
cache_compute_if_absent(&mut cache, "existing", || "should not run".to_string());
|
||||
assert_eq!(result, &"original".to_string());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_batch_lookup() {
|
||||
let mut cache = HashMap::new();
|
||||
cache.insert(1, "one");
|
||||
cache.insert(3, "three");
|
||||
cache.insert(5, "five");
|
||||
|
||||
let keys = [1, 2, 3, 4, 5];
|
||||
let results = batch_lookup(&cache, &keys);
|
||||
assert_eq!(results, vec![&"one", &"three", &"five"]);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user