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.
