Error handling in Rust right now is more work than people coming from elsewhere expect. There's no blessed crate that just makes the pain go away. You either hand-roll an error enum with a From impl for every variant, which is correct and tedious in equal measure, or you reach for error-chain and learn its macros, or you give up on precision and pass Box<dyn Error> around.
The hand-rolled enum is the honest option. You define your Error, you write impl std::error::Error, you write the Display, and you add a From<io::Error> and a From<ParseIntError> and one more every time you call into a new library. It's all boilerplate the compiler could plausibly generate, but it can't yet, so you write it. The upside is total clarity: every error your function can produce is named in the type.
Box<dyn Error> is the lazy escape, and at the edges of a program, in a main that just needs to bail and print something, it's fine. The ? operator works through it because everything implements Error. The cost is you've thrown away the structure, and a caller can't match on what went wrong without downcasting, which nobody enjoys.
I keep thinking someone will write the crate that makes the common case a one-liner whilst keeping the typed case available. Until then I'll keep writing From impls by hand and muttering. It's not bad, exactly. It's just more ceremony than the language's reputation for ergonomics would lead you to expect.