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:
2026-01-22 11:58:33 +00:00
parent f55039411e
commit 02fbf819e4
14 changed files with 567 additions and 0 deletions

View File

@@ -0,0 +1,6 @@
[package]
name = "generic_function_toolkit"
version = "0.1.0"
edition = "2024"
[dependencies]

View 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)

View 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"]);
}
}