`Try` trait, `Residual<O>` trait and `try_collect_into_array`

Can anyone tell me how the hell this function works (mod.rs - source)? What are we returning?
In this function, I understand MaybeUninit, Guard and other low-level parts. I partially understand how the Try trait works and completely broke my head trying to understand R: Residual<[T; N]>, Result<R::TryType, IntoIter<T, N>>, Ok(Try::from_output(output)). Complete function code:

/// Pulls `N` items from `iter` and returns them as an array. If the iterator
/// yields fewer than `N` items, `Err` is returned containing an iterator over
/// the already yielded items.
///
/// Since the iterator is passed as a mutable reference and this function calls
/// `next` at most `N` times, the iterator can still be used afterwards to
/// retrieve the remaining items.
///
/// If `iter.next()` panicks, all items already yielded by the iterator are
/// dropped.
#[inline]
fn try_collect_into_array<I, T, R, const N: usize>(
    iter: &mut I,
) -> Result<R::TryType, IntoIter<T, N>>
where
    I: Iterator,
    I::Item: Try<Output = T, Residual = R>,
    R: Residual<[T; N]>,
{
    if N == 0 {
        // SAFETY: An empty array is always inhabited and has no validity invariants.
        return Ok(Try::from_output(unsafe { mem::zeroed() }));
    }

    let mut array = MaybeUninit::uninit_array::<N>();
    let mut guard = Guard { array_mut: &mut array, initialized: 0 };

    for _ in 0..N {
        match iter.next() {
            Some(item_rslt) => {
                let item = match item_rslt.branch() {
                    ControlFlow::Break(r) => {
                        return Ok(FromResidual::from_residual(r));
                    }
                    ControlFlow::Continue(elem) => elem,
                };

                // SAFETY: `guard.initialized` starts at 0, which means push can be called
                // at most N times, which this loop does.
                unsafe {
                    guard.push_unchecked(item);
                }
            }
            None => {
                let alive = 0..guard.initialized;
                mem::forget(guard);
                // SAFETY: `array` was initialized with exactly `initialized`
                // number of elements.
                return Err(unsafe { IntoIter::new_unchecked(array, alive) });
            }
        }
    }

    mem::forget(guard);
    // SAFETY: All elements of the array were populated in the loop above.
    let output = unsafe { array.transpose().assume_init() };
    Ok(Try::from_output(output))
}

Prerequisite reading to understand the Try trait and the "residual" idea: 3058-try-trait-v2 - The Rust RFC Book.


This is usually done in core via a helper type alias to make it a bit more readable:

Using that, for example, Iterator::try_find is written as

What's going on for try_find is that you pass it a closure that returns Result<bool, E> or Option<bool> (or …), and then it returns Result<Option<Self::Item>, E> or Option<Option<Self::Item>> (or …) respectively.

The same kind of thing is happening in try_collect_into_array, but the traits are written out in a way that makes it a bit harder to follow.

It takes in an Iterator over Result<T, E> or Option<T> (or …), and returns a Result<Result<[T; N], E>, IntoIter<T, N> or a Result<Option<[T; N]>, IntoIter<T, N> (or …) respectively.

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.