There's a bunch of different situations that can be discussed, and it's hard to generalise. However:
Your function typically has a specific intent when it tries to call another function. Say that you have a poorly designed function that reads from a file, parses the data, opens a DB connection and stores the data.
Should I really expect an end-user to understand an error generated by diesel/postgres/wtfdb? No, most likely I want to instruct them to generate debug logs and report an issue/contact support. This is most likely the best user experience for an application. In this case, each "action" of the function would "hide" the underlying error––it might provide information about what failed (file not found, DB rejected credentials, what part of the file couldn't be parsed, etc), but the user doesn't care (and shouldn't!) about Rust type of diesel error was generated.
To answer your question specifically, I might go without something like this:
#[derive(Debug, Error, Clone)]
pub enum MyFunctionError {
#[error("unable to read data from file: {0}")]
ReadData(String),
#[error("failed to parse data: {0}")]
Parse(String),
#[error("database refused our connection: {0} (host: {1}, username: {2})")]
DatabaseConnection(String, String, String),
#[error("failed to write rows: {0}")]
WriteData(String),
}
Obviously this is a contrived example. I wouldn't use `#[from]` and just use `.map_err` to give internal meaning to error provenances. `DatabaseConnection` and `WriteData` might have come from the same underlying WTFDbError, but I can give it more meaning by annotating it.
When building a library, however, yes, I most likely do want to use `#[from] io::Error` syntax and let the calling library figure out what to do (which might very well giving the user a userful error message and dumping an error log).