use chrono::NaiveDate;
use rust_decimal::Decimal;
use serde::{Deserialize, Deserializer};
#[derive(Debug, Clone, Deserialize)]
pub struct Transaction {
#[serde(rename = "Date", deserialize_with = "parse_date")]
pub date: NaiveDate,
#[serde(rename = "Category")]
pub category: String,
#[serde(rename = "Description")]
pub description: String,
#[serde(rename = "Amount")]
pub amount: Decimal,
}
fn parse_date<'de, D>(deserializer: D) -> Result<NaiveDate, D::Error>
where
D: Deserializer<'de>,
{
let raw = String::deserialize(deserializer)?;
NaiveDate::parse_from_str(raw.trim(), "%Y-%m-%d").map_err(serde::de::Error::custom)
}
use std::collections::BTreeMap;
use rust_decimal::Decimal;
use crate::transaction::Transaction;
pub fn summarize<I>(records: I) -> Result<BTreeMap<String, Decimal>, csv::Error>
where
I: Iterator<Item = Result<Transaction, csv::Error>>,
{
let mut totals: BTreeMap<String, Decimal> = BTreeMap::new();
for record in records {
let tx = record?;
let entry = totals.entry(tx.category).or_insert(Decimal::ZERO);
*entry += tx.amount;
}
Ok(totals)
}
pub fn grand_total(totals: &BTreeMap<String, Decimal>) -> Decimal {
totals.values().copied().sum()
}
mod aggregate;
mod transaction;
use std::error::Error;
use csv::ReaderBuilder;
use crate::aggregate::{grand_total, summarize};
use crate::transaction::Transaction;
fn main() -> Result<(), Box<dyn Error>> {
let path = std::env::args().nth(1).ok_or("usage: totals <file.csv>")?;
let mut reader = ReaderBuilder::new()
.has_headers(true)
.trim(csv::Trim::All)
.from_path(&path)?;
let totals = summarize(reader.deserialize::<Transaction>())?;
for (category, total) in &totals {
println!("{:<20} {:>12}", category, total);
}
println!("{:<20} {:>12}", "TOTAL", grand_total(&totals));
Ok(())
}
This snippet shows a focused, real-world Rust task: streaming a CSV of financial transactions off disk, deserializing each row into a typed struct, and folding the rows into per-category totals. It is split across three collaborating files so the concerns stay separated: the record type and its serde attributes, the aggregation logic that consumes an iterator of records, and the binary entry point that wires a csv::Reader to those pieces.
In transaction.rs, Transaction derives Deserialize so the csv crate can map header columns onto fields by name. The #[serde(rename)] attributes decouple the Rust field names from the exact CSV headers, which is important because CSV headers are frequently inconsistent with Rust naming conventions. Amounts are stored as rust_decimal::Decimal rather than f64; money must never be summed as binary floating point because rounding errors accumulate, and Decimal gives exact base-10 arithmetic. The custom deserialize_with on date parses an ISO date via chrono, turning a raw string column into a real NaiveDate during deserialization so downstream code never handles unparsed text.
In aggregate.rs, summarize accepts any Iterator<Item = Result<Transaction, csv::Error>>. Accepting an iterator rather than a Vec means the whole file is never buffered in memory — records are pulled and folded one at a time, so the memory cost is bounded regardless of file size. The function uses the ? operator inside the loop to propagate a malformed row as an error instead of silently skipping it, and it accumulates into a BTreeMap<String, Decimal> via the entry API. A BTreeMap is chosen over HashMap so the resulting categories come out in deterministic sorted order, which makes output stable and testable. Each entry(...).or_insert(Decimal::ZERO) either seeds a new category or adds to the running total in place.
In main.rs, ReaderBuilder opens the file with headers enabled, and reader.deserialize() yields the typed Result stream that summarize expects. The main function returns Result<(), Box<dyn Error>> so any I/O, parse, or decimal error short-circuits with a readable message rather than a panic. This pipeline is the pattern to reach for whenever tabular input must be validated, typed, and reduced: parsing failures surface loudly, money math stays exact, and the streaming shape keeps large files cheap to process.
Related snips
struct Config<'a> {
name: &'a str,
value: &'a str,
}
fn parse_config(line: &str) -> Config {
Lifetime annotations for flexible borrowing in structs
require "csv"
class PeopleCsvStream
include Enumerable
HEADERS = %w[id full_name email signed_up_at plan].freeze
Resilient CSV Export as a Streamed Response
use crossbeam::channel::unbounded;
use std::thread;
fn main() {
let (tx, rx) = unbounded();
Crossbeam for advanced concurrent data structures
// Creating a Promise
const myPromise = new Promise((resolve, reject) => {
const success = true;
setTimeout(() => {
if (success) {
Promises and async/await patterns for asynchronous JavaScript
use tracing::{info, instrument};
#[instrument]
fn process_request(user_id: u64) {
info!(user_id, "Processing request");
// Work happens here
tracing for structured logging and distributed tracing
use std::sync::mpsc;
use std::thread;
fn main() {
let (tx, rx) = mpsc::channel();
Channels (mpsc) for message passing between threads
Share this code
Here's the card — post it anywhere.