- Feature Name:
array::try_from_iter
- Start Date: 2023-02-12
- RFC PR: rust-lang/rfcs#0000
- Rust Issue: rust-lang/rust#0000
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,
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 Item
s, but solely from the Iterator
not holding enough Item
s.
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.
array::try_from_iter
provides a safe API for creating fixed-size arrays [T; N]
from IntoIterator
s.
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.
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
.
There are actually many good arguments against adopting this. The following is an abbreviated list of some:
- The question of the name. What should this associated function be called?
try_from_iter
is just one of multiple options. Alternativelytry_from_iterator
could also be used. The problem with a name liketry_from_iterator
is that it may be associated withtry_from_fn
andtry_collect
which base theirtry
fallibility on theTry
yielded from function orIterator
. In our case this would be an inappropriate association, astry_from_iter
fails if there are not enough items yielded. Still, there may be alternative names that fit better. - Should this really be an associated function?
- Do we need this if we could use
iter::next_chunk
? Currently we can already achieve the functionality we get fromtry_from_iter
withiter::next_chunk
and the new function is really just a wrapper around what already exists. Having a dedicated method may improve discoverability, but as it stands this is a very strong argument against this.
Having a dedicated associated function on array
would improve discoverability and give a standard way
of turning any collection implementing IntoIterator
into an array of fixed size.
Since there still is not complete consensus on around what should be returned from any TryFrom<Iterator>
there are many good alternatives and arguments for different APIs and implementations.
Some of them are listed here:
- Simply use
iter::next_chunk
as suggested here. - Using something like an
ArrayVec<T, N>
instead. - Take
&mut iter
rather than ownership ofiter
. This point was raised here and raises the valid question of how to handleIntoIterator
s with>N
items. Taking&mut iter
would allow those leftover items to still be used. While taking&mut T
does not seem like the standard API forfrom
andtry_from
functions, it is still a good valuable to consider. - Implement
TryFrom<IntoIterator<Item = T>>
for[T; N]
. This is problematic because of Invalid collision with TryFrom implementation? #50133. - New
TryFromIterator
trait. Would likely also requireTryIntoIterator
. - Naming alternatives:
5.1try_fill_from
5.2try_collect_from
(suggests it should be used with anIterator
directly)
5.3from_iter
(does not suggest fallibility)
Adding anything to the core
and std
API has lasting implications, and should this be scrutinized from
all angles.
There exist already a few open issues on the topic of turning iterators into fixed-sized array.
- Naming?
- Should the associated function be on
array
orIterator
? - Is this necessary?
- Are there more pragmatic/ergonomic ways of doing this?
- Could a dedicated
TryFromIterator
trait solve this better?
In the future, a dedicated trait may be a better fit. This would require more investigation
and a larger change to core
and std
and thus is to be seen.