I was thinking about the ability of creating a fixed-size array from iterator, and I come up with this solution (the main can be run with sanitizers to check if everything work fine):
extern crate rand;
use std::{mem, ptr, fmt::Debug, ops::{Deref, Drop}};
use rand::{thread_rng, Rng};
pub trait TryFromIterator<A>: Sized {
type Error;
fn try_from_iter<T>(iter: T) -> Result<Self, Self::Error>
where
T: Iterator<Item = A>;
}
#[derive(Debug)]
pub struct IterError;
macro_rules! impl_try_collect_array {
($($N:expr)+) => {
$(
impl<T: Sized> TryFromIterator<T> for [T; $N] {
type Error = IterError;
fn try_from_iter<I>(mut iter: I) -> Result<Self, Self::Error>
where
I: Iterator<Item = T>,
{
let mut out: mem::ManuallyDrop<[T; $N]>;
let stop_index = unsafe {
out = std::mem::uninitialized();
let mut out_iter = out.iter_mut().enumerate();
loop {
if let Some((index, out_item)) = out_iter.next() {
if let Some(in_item) = iter.next() {
ptr::copy_nonoverlapping(&in_item, out_item, 1);
mem::forget(in_item);
} else {
break Some(index);
}
} else {
break None;
}
}
};
if let Some(stop_index) = stop_index {
unsafe {
for item in out.iter_mut().take(stop_index) {
ptr::drop_in_place(item);
}
}
Err(IterError)
} else {
Ok(mem::ManuallyDrop::into_inner(out))
}
}
}
)+
}
}
// Used to inject `try_collect` inside `Iterator`
trait TryCollectExt<T: Sized>: Iterator + Sized {
fn try_collect<F>(self) -> Result<F, F::Error>
where
F: TryFromIterator<Self::Item>;
}
impl<I, T> TryCollectExt<T> for I
where
T: Sized,
I: Iterator<Item = T> + Sized,
{
fn try_collect<F>(self) -> Result<F, F::Error>
where
F: TryFromIterator<T>,
{
TryFromIterator::try_from_iter(self)
}
}
impl_try_collect_array!(1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32);
#[test]
fn test1() {
let data: [u8; 10] = (0u8..).into_iter().try_collect().unwrap();
assert_eq!(data, [0u8, 1, 2, 3, 4, 5, 6, 7, 8, 9]);
}
#[test]
#[should_panic]
fn test2() {
let _data: [u8; 10] = (0u8..5).into_iter().try_collect().unwrap();
}
#[derive(Debug, Clone)]
struct NoisyVec<T: Debug>(Vec<T>);
impl<T: Debug> Deref for NoisyVec<T> {
type Target = Vec<T>;
fn deref(&self) -> &Self::Target {
&self.0
}
}
impl<T: Debug> Drop for NoisyVec<T> {
fn drop(&mut self) {
println!("Dropping {:?}", self.0);
}
}
#[derive(Debug, Clone)]
struct PanicVec<T>(Vec<T>);
impl<T> IntoIterator for PanicVec<T> {
type Item = T;
type IntoIter = PanicIterator<Vec<T>>;
fn into_iter(self) -> Self::IntoIter {
println!("Calling into_iter");
PanicIterator(self.0.into_iter())
}
}
struct PanicIterator<T: IntoIterator>(T::IntoIter);
impl<T: IntoIterator> Iterator for PanicIterator<T> {
type Item = T::Item;
fn next(&mut self) -> Option<Self::Item> {
println!("Calling next");
let mut rng = thread_rng();
let rand_value = rng.gen_range(0, 10);
if rand_value < 6 {
self.0.next()
} else {
panic!("Whoops, PanicIterator is panicing!")
}
}
}
fn get_array_not_fault() -> Result<[NoisyVec<i32>; 7], IterError> {
let vec = NoisyVec((0..10).into_iter().collect::<Vec<_>>());
std::iter::repeat(vec).try_collect()
}
fn get_array_fault() -> Result<[NoisyVec<i32>; 7], IterError> {
let vec = NoisyVec((0..10).into_iter().collect::<Vec<_>>());
std::iter::repeat(vec).take(5).try_collect()
}
fn get_array_panicing() -> Result<[Vec<i32>; 7], IterError> {
let vec = (0..10).into_iter().collect::<Vec<i32>>();
let vec_of_vec = PanicVec(std::iter::repeat(vec).take(10).collect::<Vec<Vec<i32>>>());
vec_of_vec.into_iter().try_collect()
}
fn main() {
println!("Testing with not faulting test");
if let Ok(data) = get_array_not_fault() {
println!("Data obtained as expected: {:?}", data);
} else {
eprintln!("Unexpectedly cannot obtain data");
}
println!("Testing with faulting test");
if let Ok(data) = get_array_fault() {
eprintln!("Unexpected data obtained: {:?}", data);
} else {
println!("Cannot obtain data as expected");
}
println!("Testing using a (maybe) panicing iterator");
if let Ok(data) = get_array_panicing() {
eprintln!("Unexpectedly obtained data: {:?}", data);
} else {
eprintln!("Unexpectedly cannot obtain data (should have paniced)");
}
}
It seems to work flawlessly (with address sanitizer I get an ODR violation, but I think it is another kind of problem), so it could be nice. But... is it worth? I mean, it is very useful to initialize a fixed array with zero cost and without unsafe code around, but I don't see any other way this trait (and the try_collect function) could be used. At least, not until we have const generics...
What do you think?
EDIT: as suggested by ExpHP, the code has been slightly modified to use mem::ManuallyDrop
and avoid crashes in case of panics.