CodingNeed.

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

Essential cookies keep your account signed in. Optional analytics is not configured on this site. Your choice does not affect access to lessons.

Read the Privacy Policy. You can change this choice in the footer.