If you are writing your own library then your best bet is to define your own Error enum in each module. Below each Error enum write a bunch of From trait implementations to convert errors into a custom variant in your Error enum. This allows you to use the "?" operator to "bubble" errors up through your library and build some sort of useful printable error tree. Don't used boxed errors and don't use std:io:Error for your own custom purposes. Doing things this way will mean that consumers of your library will not be bound to use some error handling library that they may not want to use. It is also very fast.
If this is not a library then feel free to use any number of error handling helper libraries out there. It would be unfair to single one of them out. However, once I got the hang of writing my on custom errors I ended up using them for my binary applications too.