I've hit a situation where I want to do multiple operations (in this case, save several files), and if any number of them fail, I want to report all failures, not just the first.
// not this
fn bad() -> Result<(), SaveError> {
save_1()?;
save_2()?;
Ok(())
}
// more like this
fn save_many() -> Result<(),SeveralSaveError> {
let res1 = save_1();
let res2 = save_2();
let errs : Vec<_> = [res1,res2].into_iter().filter_map(|r|r.err()).collect();
if !errs.is_empty() {
Err(errs)?
}
Ok(())
}
enum SaveError {
AnyFailed(Vec<IoError>),
}
This is fairly cumbersome, though, and this seems like a problem that there could already be a purpose-built crate for, that I just haven't found yet. Does anyone know existing crates for this? Or a nice idiom for how to achieve this without a library? (googling "rust multiple errors" doesn't give many useful results)
Collating multiple errors into one is not well supported. The std error trait only supports linear error stacks (via Error::source) and most error handling libraries follow suit.
Why? Primarily because it's not easy to have a dyn-safe trait provide an iterator of multiple sources; you're limited to internal iteration (e.g. for_each_cause(&self, impl FnMut(&dyn MyError)), boxing (e.g. fn causes(&self) -> Box<dyn Iterator<Item=&dyn MyError>>), or homogeneous slices (e.g. fn sources(&self) -> &[io::Error]). And error traits are generally expecting to end up being put into a dyn trait object eventually.
That said, both miette and error-stack do have some support for "tree errors" with multiple causes. There doesn't seem to be any real ?/monadish support for collecting multiple errors, though, likely in part due to the limited availability of "tree error" reporting.
And actually an illustration of a slightly harder organizational problem because rustc isn't fn(Source) -> Result<Program, Vec<Diagnostic>>, it's more fn(Source) -> (Result<Program, ()>, Vec<Diagnostic>), which can be a more awkward shape to deal with.
The best I've personally found for small cases is something roughly in the shape of
struct FatalError;
impl From<()> for FatalError {
fn from(_: ()) -> Self { Self }
}
type Saves = [Save; 2];
type SaveMany = Result<Saves, FatalError>;
fn save_many_with(emit: &mut dyn FnMut(Diagnostic)) -> SaveMany {
let save1 = save1().map_err(&mut *emit);
let save2 = save2().map_err(&mut *emit);
Ok([save1?, save2?])
}
// for warning diagnostic collection
fn save_many() -> (SaveMany, Vec<Diagnostic>>) {
let mut diags = vec![];
let emit = &mut |diag| diags.push(diag);
let saves = save_many_with(emit);
(saves, diags)
}
// for tree errors
fn save_many() -> Result<Saves, Vec<Diagnostic>> {
let mut diags = vec![];
let emit = &mut |diag| diags.push(diag);
let saves = save_many_with(emit);
saves.map_err(|FatalError| diags)
}
Basically, the pattern is to have some "error sink" which you can map errors into, deal only in unit errors otherwise, then ? the emitted-error-results only when the lack of result can no longer be tolerated. This works irregardless of whether the top layer is Result<T, Es> or (Option<T>, Es).
This currently uses Result<T, ()> so you can ? it in -> Result<T, FatalError>, but you could similarly use Option<T> everywhere, or potentially once Try is stable, enable FromResidual<Option<!>> for Result<T, FatalError>.