Skip to content

Add array::try_from_iter as safe way of creating a fixed sized array from an IntoIterator. #229

Closed
@emilHof

Description

@emilHof

Proposal

This feature aims to introduce a standard way of fallibly creating a [T; N] from an
IntoIterator<Item = T>. It functions by passing an IntoIterator<Item = T> which will result in a
[T; N], if and only if the IntoIterator holds >= N items. Otherwise, it returns an
array::IntoIter<T> with the original IntoIterator's items. The proposed method signature follows:

pub fn try_from_iter<I, const N: usize>(iter: I) -> Result<[I::Item; N], IntoIter<I::Item, N>>
where
    I: IntoIterator,

Problem statement

In cases in which we want to construct a fixed-size array from a collection or iterator we currently
do not have a standard and safe way of doing so. While it is possible to do this without additional
allocation, this requires some use of unsafe and thus leaves the implementer with more room for error
and potential UB.

There has been much work done on fallible Iterator methods such as try_collect and try_fold, and
more work that is being undertaken to stabilize things like try_process, these do not strictly apply to
this. The purpose of this API is creating a fixed-size array from an iterator where the failure case does
not come from the Iterator's Items, but solely from the Iterator not holding enough Items.

Therefore, it seems like giving array a dedicated associated method that narrows the scope of failure
to the relationship between the size of the Iterator and the size of the array would be the pragmatic
thing to do.

Additionally, the relatively recent addition of iter:next_chunk
means this implements basically for free.

Motivation, use-cases

array::try_from_iter provides a safe API for creating fixed-size arrays [T; N] from IntoIterators.

For a somewhat contrived example, say we want to create a [String; 32] from a HashSet<String>.

let set: HashSet<String> = HashSet::new();

/* add some items to the set... */

let Ok(my_array) = std::array::try_from_iter::<_, 32>(set) else {
    /* handle error... */
};

Oftentimes it can be more efficient to deal with fixed-size arrays, as they do away with the inherent
indirection that come with many of the other collection types. This means arrays can be great when
lookup speeds are of high importance. The problem is that Rust does not make it particularly easy to
dynamically allocate fixed-size arrays at runtime without the use of unsafe code. This feature allows
developers to turn their collections into fixed-size arrays of specified size in a safe and ergonomic
manner.

Naturally it can be the case that a collection or IntoIterator does not hold enough elements to fill the
entire length of the array. If that happens try_from_iter returns an array::IntoIter that that holds
the elements of the original IntoIterator.

As this change is additive its adaptation should be quite straight forward. There may of course an
argument to be made for choosing a different name with better discoverability.

The addition of any methods that can replace existing unsafe code in crates should be an improvement, at
least as it pertains to safety.

Solution sketches

The implementation of this feature should be quite straight forward as it is essentially a wrapper around
iter::next_chunk:

#[inline]
pub fn try_from_iter<I, const N: usize>(iter: I) -> Result<[I::Item; N], IntoIter<I::Item, N>>
where
    I: IntoIterator,
{
    iter.into_iter().next_chunk()
}

It turns the passed type/collection into iter, an Iterator<Item = T> and then calls next_chunk.

Links and related work

What happens now?

This issue is part of the libs-api team API change proposal process. Once this issue is filed the libs-api team will review open proposals in its weekly meeting. You should receive feedback within a week or two.

Metadata

Metadata

Assignees

No one assigned

    Labels

    ACP-acceptedAPI Change Proposal is accepted (seconded with no objections)T-libs-apiapi-change-proposalA proposal to add or alter unstable APIs in the standard libraries

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions