Skip to content

Commit b5411ab

Browse files
Merge pull request #72 from ojob/main
Rust Patterns, ch01: ensure doctests are verifiable
2 parents c7164ef + 89ca504 commit b5411ab

1 file changed

Lines changed: 19 additions & 4 deletions

File tree

‎rust-patterns-book/src/ch01-generics-the-full-picture.md‎

Lines changed: 19 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -40,12 +40,14 @@ fn max_of_str<'a>(a: &'a str, b: &'a str) -> &'a str { if a >= b { a } else { b
4040

4141
**Comparison with C++**: Rust generics work like C++ templates but with one crucial difference — **bounds checking happens at definition, not instantiation**. In C++, a template compiles only when used with a specific type, leading to cryptic error messages deep in library code. In Rust, `T: PartialOrd` is checked when you define the function, so errors are caught early and messages are clear.
4242

43-
```rust
43+
```rust,compile_fail
4444
// Rust: error at definition site — "T doesn't implement Display"
4545
fn broken<T>(val: T) {
4646
println!("{val}"); // ❌ Error: T doesn't implement Display
4747
}
48+
```
4849

50+
```rust
4951
// Fix: add the bound
5052
fn fixed<T: std::fmt::Display>(val: T) {
5153
println!("{val}"); // ✅
@@ -56,7 +58,7 @@ fn fixed<T: std::fmt::Display>(val: T) {
5658

5759
Monomorphization has a cost — binary size. Each unique instantiation duplicates the function body:
5860

59-
```rust
61+
```rust,ignore
6062
// This innocent function...
6163
fn serialize<T: serde::Serialize>(value: &T) -> Vec<u8> {
6264
serde_json::to_vec(value).unwrap()
@@ -67,7 +69,7 @@ fn serialize<T: serde::Serialize>(value: &T) -> Vec<u8> {
6769

6870
**Mitigation strategies**:
6971

70-
```rust
72+
```rust,ignore
7173
// 1. Extract the non-generic core ("outline" pattern)
7274
fn serialize<T: serde::Serialize>(value: &T) -> Result<Vec<u8>, serde_json::Error> {
7375
// Generic part: only the serialization call
@@ -102,7 +104,7 @@ Three ways to handle "different types, same interface" in Rust:
102104
| **Enum** | Match arm | Compile time | ❌ (closed set) | Zero — no vtable |
103105
| **Trait object** (`dyn Trait`) | Dynamic (vtable) | Runtime | ✅ (open set) | Vtable pointer + indirect call |
104106

105-
```rust
107+
```rust,ignore
106108
// --- GENERICS: Open set, zero cost, compile-time ---
107109
fn process<H: Handler>(handler: H, request: Request) -> Response {
108110
handler.handle(request) // Monomorphized — one copy per H
@@ -349,6 +351,10 @@ impl<K: Eq + Hash + Clone, V> Cache<K, V> {
349351
}
350352

351353
fn insert(&mut self, key: K, value: V) {
354+
if self.capacity == 0 {
355+
// no capacity!
356+
return;
357+
}
352358
if self.map.contains_key(&key) {
353359
self.map.insert(key, value);
354360
return;
@@ -372,6 +378,7 @@ impl<K: Eq + Hash + Clone, V> Cache<K, V> {
372378
}
373379

374380
fn main() {
381+
// Test of a basic cache
375382
let mut cache = Cache::new(3);
376383
cache.insert("a", 1);
377384
cache.insert("b", 2);
@@ -381,6 +388,14 @@ fn main() {
381388
cache.insert("d", 4); // Evicts "a"
382389
assert_eq!(cache.get(&"a"), None);
383390
assert_eq!(cache.get(&"d"), Some(&4));
391+
392+
// Left to the reader: what type should `capacity` attribute be,
393+
// to ensure that such a useless cache cannot be defined?
394+
let mut empty_cache = Cache::new(0);
395+
empty_cache.insert("0", 0);
396+
assert_eq!(empty_cache.get(&"0"), None);
397+
assert_eq!(empty_cache.len(), 0);
398+
384399
println!("Cache works! len = {}", cache.len());
385400
}
386401
```

0 commit comments

Comments
 (0)