I’m trying to work with fixed-size arrays. I want to transform an array of Option
values [Option<T>; N]
to an Option<[T ; N]>
such that I get Some
if all entries are Some
, but None
otherwise.
Rust typically uses iterators to transform collections, but these do not have length guarantees.
This shoulld be legal, as the result if present must be the same length as the argument, but is there a way to do this whilst preserving the compile-time length guarantees, without using unwrap
or similar?
>Solution :
On nightly, using try_map()
(maybe there is a crate that supports that on stable, I don’t know):
#![feature(array_try_map)]
pub fn convert<T, const N: usize>(arr: [Option<T>; N]) -> Option<[T; N]> {
arr.try_map(|v| v)
}
On stable, allocating:
pub fn convert<T, const N: usize>(arr: [Option<T>; N]) -> Option<[T; N]> {
let arr = arr.into_iter().collect::<Option<Vec<T>>>()?;
Some(
arr.try_into()
.unwrap_or_else(|_| panic!("the array is of size {N}")),
)
}
On stable, non-allocating, but requires Default
and may be little inefficient:
pub fn convert<T: Default, const N: usize>(arr: [Option<T>; N]) -> Option<[T; N]> {
let mut result = [(); N].map(|()| T::default());
for (item, result_item) in std::iter::zip(arr, &mut result) {
*result_item = item?;
}
Some(result)
}
On stable, non-allocating, using unsafe. Should be last resort. The following code is subtly incorrect as it does not drop elements in case of None
in the middle, which just demonstrates how hard it is to write correct unsafe code:
use std::mem::{ManuallyDrop, MaybeUninit};
pub fn convert<T, const N: usize>(arr: [Option<T>; N]) -> Option<[T; N]> {
let arr = ManuallyDrop::new(arr);
let mut result_arr = MaybeUninit::<[T; N]>::uninit();
for (i, item) in arr.iter().enumerate() {
// SAFETY: This is in bounds, and wrapped in `ManuallyDrop` so not double drop.
unsafe {
result_arr
.as_mut_ptr()
.cast::<T>()
.add(i)
.write(std::ptr::read(item)?);
}
}
// SAFETY: We initialized it above.
Some(unsafe { result_arr.assume_init() })
}