3 · Reliability and project · 45 MIN
Project · A checked expense total
Ownership and error handling meet at the data boundary.
The report function borrows a slice of expenses and returns either a total or a typed static error message. checked_add prevents silent wraparound; category validation happens before each record contributes to the sum. No external state is mutated, so failure cannot leave half a balance stored elsewhere. The test module shows ordinary, empty and overflow cases. Save this example in a Cargo binary’s src/main.rs and run cargo test; main is only a small demonstration, while tests establish the contract.
Carry failures through Result and keep the input borrowed.
Read the example
struct Expense { category: String, amount: u32 }
fn total(rows: &[Expense]) -> Result<u32, &'static str> {
let mut sum = 0_u32;
for row in rows {
if row.category.trim().is_empty() { return Err("empty category"); }
sum = sum.checked_add(row.amount).ok_or("total overflow")?;
}
Ok(sum)
}
fn main() { println!("{:?}", total(&[Expense{category:"food".into(), amount:5}])); }
#[cfg(test)] mod tests {
use super::*;
#[test] fn empty() { assert_eq!(total(&[]), Ok(0)); }
#[test] fn overflow() {
let rows = [Expense{category:"a".into(),amount:u32::MAX}, Expense{category:"b".into(),amount:1}];
assert_eq!(total(&rows), Err("total overflow"));
}
}Check the expected output
cargo run: Ok(5). cargo test: two tests pass.
Your challenge
Extend the report to group totals by category using checked arithmetic and add tests for validation and repeated categories.
Solution cost: O(n) time for the total; grouping adds O(k) storage. time · Account for collection storage separately from the returned result. space
Common trap
Using ordinary addition makes overflow behavior depend on build settings.
Study the project implementation
struct Expense { category: String, amount: u32 }
fn total(rows: &[Expense]) -> Result<u32, &'static str> {
let mut sum = 0_u32;
for row in rows {
if row.category.trim().is_empty() { return Err("empty category"); }
sum = sum.checked_add(row.amount).ok_or("total overflow")?;
}
Ok(sum)
}
fn main() { println!("{:?}", total(&[Expense{category:"food".into(), amount:5}])); }
#[cfg(test)] mod tests {
use super::*;
#[test] fn empty() { assert_eq!(total(&[]), Ok(0)); }
#[test] fn overflow() {
let rows = [Expense{category:"a".into(),amount:u32::MAX}, Expense{category:"b".into(),amount:1}];
assert_eq!(total(&rows), Err("total overflow"));
}
}Further reading: Official documentation