unarray/lib.rs
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 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195
//! # Unarray
//!
//! Helper utilities for working with arrays of uninitialized memory.
//!
//! ## Current stable Rust
//!
//! Creating arrays in Rust can be somewhat painful. Currently, your best option in the general
//! case is to allocate your elements in a `Vec`, then convert to an array:
//! ```
//! # use core::convert::TryInto;
//! const LEN: usize = 1000;
//! let mut elements = Vec::with_capacity(LEN); // heap allocation here
//!
//! for i in 0..LEN {
//! elements.push(123);
//! }
//!
//! let result: [i32; LEN] = elements.try_into().unwrap();
//! ```
//! This needlessly allocates space on the heap, which is then immediately freed. If your type
//! implements `Copy`, and has a sensible default value, you can avoid this allocation by creating
//! an array literal (e.g. `[0; 1000]`), then iterating over each element and setting it, but this
//! also incurrs an unnecessary initialization cost. Why set each element to `0`, then set it
//! again, when you could just set it once?
//!
//! ## `uninit_buf` and `mark_initialized`
//!
//! The lowest-level tools provided by this library are the pair of functions: [`uninit_buf`] and
//! [`mark_initialized`]. These are ergonomic wrappers around the [`core::mem::MaybeUninit`] type.
//! Roughly speaking, most uses of these functions will follow the following steps:
//! - Stack-allocate a region of uninitialized memory with [`uninit_buf`]
//! - Initialize each element
//! - Unsafely declare that all elements are initialized using [`mark_initialized`]
//!
//! For example:
//! ```
//! # use unarray::*;
//! let mut buffer = uninit_buf();
//!
//! for elem in &mut buffer {
//! elem.write(123);
//! }
//!
//! let result = unsafe { mark_initialized(buffer) };
//! assert_eq!(result, [123; 1000]);
//! ```
//! These functions closely map onto tools provided by [`core::mem::MaybeUninit`], so should feel
//! familiar. However, [`mark_initialized`] is an unsafe function, since it's possible to create
//! uninitialized values that aren't wrapped in `MaybeUninit`. It's up to the programmer to make
//! sure every element has been initialized before calling [`mark_initialized`], otherwise it's UB.
//!
//! For this, there are also fully safe APIs that cover some of the common patterns via an
//! extension trait on `[T; N]`:
//!
//! ## `UnarrayArrayExt` extension trait
//!
//! ```
//! # use unarray::*;
//! // mapping an array via a `Result`
//! let strings = ["123", "234"];
//! let numbers = strings.map_result(|s| s.parse());
//! assert_eq!(numbers, Ok([123, 234]));
//!
//! let bad_strings = ["123", "uh oh"];
//! let result = bad_strings.map_result(|s| s.parse::<i32>());
//! assert!(result.is_err()); // since one of the element fails, the whole operation fails
//! ```
//! There is also `map_option` for functions which return an `Option`
//!
//! ## Collecting iterators
//!
//! Iterators generally don't know their length at compile time. But it's often the case that the
//! programmer knows the length ahead of time. In cases like this, it's common to want to collect
//! these elements into an array, without heap allocation or initializing default elements.
//!
//! Arrays don't implement `FromIterator` for this very reason. So this library provides
//! `ArrayFromIter`:
//! ```
//! # use unarray::*;
//! let iter = [1, 2, 3].into_iter().map(|i| i * 2);
//! let ArrayFromIter(array) = iter.collect(); // inferred to be `ArrayFromIter::<i32, 3>`
//! assert_eq!(array, Some([2, 4, 6]));
//! ```
//! However, this can fail, since the iterator may not actually yield the right number of elements.
//! In these cases, the inner option is `None`:
//! ```
//! # use unarray::*;
//! let iter = [1, 2, 3, 4].into_iter();
//! match iter.collect() {
//! ArrayFromIter(Some([a, b, c])) => println!("3 elements, {a}, {b}, {c}"),
//! ArrayFromIter(None) => println!("not 3 elements"),
//! }
//! ```
//! ## `build_array-*` functions
//!
//! Finally, it's often the case that you want to initialize each array element based on its index.
//! For that, [`build_array`] takes a const generic length, and a function that takes an index and
//! returns an element, and builds the array for you:
//! ```
//! use unarray::*;
//! let array: [usize; 5] = build_array(|i| i * 2);
//! assert_eq!(array, [0, 2, 4, 6, 8]);
//! ```
//! There are also variants that allow fallibly constructing an array, via [`build_array_result`]
//! or [`build_array_option`], similar to [`UnarrayArrayExt::map_result`] and [`UnarrayArrayExt::map_option`].
#![cfg_attr(not(test), no_std)]
#![deny(clippy::missing_safety_doc, missing_docs)]
use core::mem::MaybeUninit;
mod build;
mod map;
mod from_iter;
#[cfg(test)]
mod testing;
pub use build::{build_array, build_array_option, build_array_result};
pub use map::UnarrayArrayExt;
pub use from_iter::ArrayFromIter;
/// Convert a `[MaybeUninit<T>; N]` to a `[T; N]`
///
/// ```
/// # use unarray::*;
/// let mut buffer = uninit_buf::<i32, 1000>();
///
/// for elem in &mut buffer {
/// elem.write(123);
/// }
///
/// let result = unsafe { mark_initialized(buffer) };
/// assert_eq!(result, [123; 1000])
/// ```
///
/// This largely acts as a workaround to the fact that [`core::mem::transmute`] cannot be used with
/// const generic arrays, as it can't prove they have the same size (even when intuitively they are
/// the same, e.g. `[i32; N]` and `[u32; N]`).
///
/// This is similar to the nightly-only [`core::mem::MaybeUninit::array_assume_init`]
///
/// # Safety
///
/// Internally, this uses [`core::mem::transmute_copy`] to convert a `[MaybeUninit<T>; N]` to `[T; N]`.
/// As such, you must make sure every element has been initialized before calling this function. If
/// there are uninitialized elements in `src`, these will be converted to `T`s, which is UB. For
/// example:
/// ```no_run
/// # use unarray::*;
/// // ⚠️ This example produces UB ⚠️
/// let bools = uninit_buf::<bool, 10>();
/// let uh_oh = unsafe { mark_initialized(bools) }; // UB: creating an invalid instance
/// if uh_oh[0] { // double UB: reading from unintiailized memory
/// // ...
/// }
/// ```
/// Even if you never use a value, it's still UB. This is especially true for types with
/// [`core::ops::Drop`] implementations:
/// ```no_run
/// # use unarray::*;
/// // ⚠️ This example produces UB ⚠️
/// let strings = uninit_buf::<String, 10>();
/// let uh_oh = unsafe { mark_initialized(strings) }; // UB: creating an invalid instance
///
/// // uh_oh is dropped here, freeing memory at random addresses
/// ```
pub unsafe fn mark_initialized<T, const N: usize>(src: [MaybeUninit<T>; N]) -> [T; N] {
core::mem::transmute_copy::<[MaybeUninit<T>; N], [T; N]>(&src)
}
/// Create an array of unintialized memory
///
/// This function is just a safe wrapper around `MaybeUninit::uninit().assume_init()`, which is
/// safe when used to create a `[MaybeUninit<T>; N]`, since this type explicitly requires no
/// initialization
///
/// ```
/// # use unarray::*;
/// let mut buffer = uninit_buf::<i32, 1000>();
///
/// for elem in &mut buffer {
/// elem.write(123);
/// }
///
/// let result = unsafe { mark_initialized(buffer) };
/// assert_eq!(result, [123; 1000])
/// ```
///
/// This is similar to the nightly-only [`core::mem::MaybeUninit::uninit_array`]
pub fn uninit_buf<T, const N: usize>() -> [MaybeUninit<T>; N] {
// SAFETY:
// This is safe because we are assuming that a `[MaybeUninit<T>; N]` is initialized. However,
// since `MaybeUninit` doesn't require initialization, doing nothing counts as "initializing",
// so this is always safe
unsafe { MaybeUninit::uninit().assume_init() }
}