-
Notifications
You must be signed in to change notification settings - Fork 15
Expand file tree
/
Copy pathlazy_evaluation.rs
More file actions
90 lines (81 loc) · 2.92 KB
/
lazy_evaluation.rs
File metadata and controls
90 lines (81 loc) · 2.92 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
//! Deferred computation with `.attach_with()` and `.context_with()`.
//!
//! **Run this example:** `cargo run --example lazy_evaluation`
//!
//! In `basic.rs`, you learned `.context()` and `.attach()`. This example shows
//! when to use the `_with()` variants that defer computation.
//!
//! **Use `_with()` on Results when you need to compute the value:**
//! - Formatting with `format!()`
//! - Expensive computations that shouldn't run on success
//!
//! The closure only runs if the Result is Err, avoiding unnecessary work.
//!
//! **Don't use `_with()` when already constructing an error:**
//!
//! If you're using `bail!()` or `report!()`, you're already on the error path.
//! Just compute the value directly and use `.attach()` or `.context()`.
//!
//! **What's next?**
//! - Want to understand type preservation? → `typed_reports.rs`
//! - See all examples? → `examples/README.md`
use rootcause::prelude::*;
fn database_query(id: u32) -> Result<String, Report> {
if id == 999 {
bail!("Connection timeout");
}
Ok(format!("Record {id}"))
}
// Defer expensive formatting or other computations until actually needed
fn fetch_user_record(user_id: u32, db_host: &str, timestamp: u64) -> Result<String, Report> {
let record = database_query(user_id).context_with(|| {
format!("Failed to fetch user {user_id} from {db_host} at timestamp {timestamp}")
})?;
Ok(record)
}
fn validate_item(item: &str) -> Result<(), Report> {
if item.is_empty() {
bail!("Item cannot be empty");
}
if item.len() > 100 {
bail!("Item exceeds maximum length");
}
Ok(())
}
// This can be extra important in loops as the savings compound
fn process_batch(items: &[&str]) -> Result<(), Report> {
for (index, item) in items.iter().enumerate() {
validate_item(item)
.attach_with(|| format!("Item {index}: '{item}'"))
.context("Batch processing failed")?;
}
Ok(())
}
// Anti-pattern: If you already have an error, it no longer makes sense to defer
fn validate_data(data: &str) -> Result<(), Report> {
if data.is_empty() {
// Don't do this:
//
// return Err(report!("Empty data"))
// .attach_with(|| format!("Length: {}, Expected: >0", data.len()));
//
// Instead, just compute directly:
return Err(report!("Empty data").attach(format!("Length: {}, Expected: >0", data.len())));
}
Ok(())
}
fn main() {
println!("Deferring expensive formatting:\n");
if let Err(report) = fetch_user_record(999, "db.example.com", 1234567890) {
eprintln!("{report}\n");
}
println!("Avoiding unnecessary work in a loop:\n");
let items = vec!["valid", "", "also valid"];
if let Err(report) = process_batch(&items) {
eprintln!("{report}\n");
}
println!("Already on error path - compute directly:\n");
if let Err(report) = validate_data("") {
eprintln!("{report}\n");
}
}