1/6

Result<T, String> is where error handling goes to die. A String cannot be matched on, cannot carry structured fields, and forces every caller to parse English to decide what to do.

Model failure as an enum instead — one variant per thing that can actually go wrong:

#[derive(Debug)]
enum TxError {
    Empty,
    TooLarge { limit: u32, got: u32 },
}

Now a caller can match on the variant, and TooLarge carries the numbers a log line or an HTTP error body actually needs.