Skip to content

Conversation

@80Ltrumpet
Copy link

This pairs nicely with Exn::from_iter, so I believe it fits in this crate.

This pairs nicely with `Exn::from_iter`.
@tisonkun
Copy link
Contributor

tisonkun commented Jan 17, 2026

Thanks for your contribution @80Ltrumpet!

I can understand this use case but I can foresee a for-loop solution that is very clear.

This fp-style API doesn't read quite fluently to me.

  1. std's collect::<Result<Vec<_>, Err>> fails on the first error. This can create some ambiguity.
  2. If you always want to collect Oks and Errs to two destinations, you can leverage itertools:
use itertools::Itertools;
use itertools::Either;

let results = vec![ ... ];

let (successes, errors): (Vec<_>, Vec<_>) = results.into_iter().partition_map(|r| match r {
    Ok(v) => Either::Left(v),
    Err(e) => Either::Right(e),
});

@andylokandy what do you think?

@80Ltrumpet
Copy link
Author

80Ltrumpet commented Jan 17, 2026

Thanks for reviewing, @tisonkun! The advantage of this solution is that it only allocates what is necessary to achieve the goal. The partition_map example you gave will allocate Vecs for both variants, which serves a different use-case, I think. That is, it would let you gracefully handle the Errs before continuing to process the Oks, whereas this collect_all idea (which may not be a very good name for it 😅) would still treat an Err as fatal (discarding the Oks), but let you collect all of the Errs before adding context to them with Exn::from_iter.

I'm open to any suggestions for improvement, and if you think this fits somewhere else or has a similarly concise expression via some other means (e.g., itertools, for-loop, etc.), I'd like to know that, too!

@tisonkun
Copy link
Contributor

tisonkun commented Jan 17, 2026

I get your point now. Then the semantics of this API is very specific:

  1. Users should know that it would not stop at the first error, but continue to consume all following errors;
  2. Users should know that they are required to put a B: FromIterator<E> as type parameter so that the internal collect would work.

Typically, such a specific requirement should be implemented downstream. In my cases, when we need to raise multiple errors, we collect the errors first and call Exn::from_iter (after #33, this should be Exn::raise_all). We may or may not collect the remaining OK values. So it would be better to handle concretely in the user side.

To avoid allocate for Ok variant when the first error occurs:

fn process<T, E>(mut results: impl Iterator<Item = Result<T, E>>) -> Result<Vec<T>, Vec<E>> {
  let mut successes = vec![];
  let mut errors = vec![];
  while let Some(result) = results.next() {
    match result {
      Ok(value) => successes.push(value),
      Err(err) => { errors.push(err); break; }
    }
  }
  if errors.is_empty() { return Ok(successes); }
  while let Some(result) = results.next() {
    if let Err(err) = result {
      errors.push(err);
    }
  }
  Err(errors)
}

... or simply use your code snippet here.

That said, your code snippet is worth adding as an example, but it does not need to be a formal API.

In addition, here is some pseudo code on how we use Exn::from_iter in ScopeDB:

pub fn resolve(
    inputs: Inputs
) -> Result<(...), MyError> {
    // some other logics

    let mut errors = vec![];
    for candidate in candidates {
        match resolve_sig(...) {
            Ok(...) => return Ok(...),
            Err(err) => {
                errors.push(err.raise(RegistryError {
                    span,
                    message: format!("invalid argument for function: {}", candidate.sig),
                }));
            }
        }
    }

    if errors.is_empty() {
        bail!(RegistryError {
            span,
            message: format!("function not found: {name}({})", args.iter().format(", "))
        });
    } else {
        Err(Exn::from_iter(
            errors,
            RegistryError {
                span,
                message: format!(
                    "no matching function for {name}({})",
                    args.iter().format(", ")
                ),
            },
        ))
    }
}
pub struct ReportFrame {
    // ... some other fields
    message: String,
    children: Vec<ReportFrame>,
}

impl ReportFrame {
    pub fn raise(self) -> Exn<ReportError> {
        let error = ReportError {
            // ...
            message: self.message,
        };

        if self.children.is_empty() {
            Exn::new(error)
        } else {
            Exn::from_iter(self.children.into_iter().map(ReportFrame::raise), error)
        }
    }
}

@80Ltrumpet
Copy link
Author

That said, your code snippet is worth adding as an example, but it does not need to be a formal API.

Sounds good to me. Thanks for your consideration!

@80Ltrumpet 80Ltrumpet closed this Jan 17, 2026
@80Ltrumpet 80Ltrumpet deleted the iterator-ext branch January 17, 2026 16:30
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants