unarray-0.1.4/.cargo_vcs_info.json0000644000000001360000000000100125170ustar { "git": { "sha1": "0151bf12f216a1dc64f5e531a8a0bb8e6bdf09ca" }, "path_in_vcs": "" }unarray-0.1.4/.github/workflows/rust.yml000064400000000000000000000012111046102023000164170ustar 00000000000000name: Rust on: push: branches: [ "master" ] pull_request: branches: [ "master" ] env: CARGO_TERM_COLOR: always jobs: build: runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 - name: Build run: cargo build --verbose - name: Run tests run: cargo test --verbose - name: Run tests (release) run: cargo test --verbose --release - name: Install Miri run: | rustup toolchain install nightly --component miri rustup override set nightly cargo miri setup - name: Test with Miri run: | cargo miri test cargo miri test --release unarray-0.1.4/.gitignore000064400000000000000000000000241046102023000132730ustar 00000000000000/target /Cargo.lock unarray-0.1.4/Cargo.toml0000644000000013540000000000100105200ustar # THIS FILE IS AUTOMATICALLY GENERATED BY CARGO # # When uploading crates to the registry Cargo will automatically # "normalize" Cargo.toml files for maximal compatibility # with all versions of Cargo and also rewrite `path` dependencies # to registry (e.g., crates.io) dependencies. # # If you are reading this file be aware that the original Cargo.toml # will likely look very different (and much more reasonable). # See Cargo.toml.orig for the original contents. [package] edition = "2018" name = "unarray" version = "0.1.4" description = "Utilities for working with uninitialized arrays" readme = "README.md" license = "MIT OR Apache-2.0" repository = "https://github.com/cameron1024/unarray" [dev-dependencies.test-strategy] version = "0.2" unarray-0.1.4/Cargo.toml.orig000064400000000000000000000006411046102023000141770ustar 00000000000000[package] name = "unarray" description = "Utilities for working with uninitialized arrays" repository = "https://github.com/cameron1024/unarray" license = "MIT OR Apache-2.0" version = "0.1.4" edition = "2018" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dev-dependencies] proptest = { git = "https://github.com/input-output-hk/proptest" } test-strategy = "0.2" unarray-0.1.4/LICENSE-APACHE000064400000000000000000000261351046102023000132420ustar 00000000000000 Apache License Version 2.0, January 2004 http://www.apache.org/licenses/ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 1. Definitions. "License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. "Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. "Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. "You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. "Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. "Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. "Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). "Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. "Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." "Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. 2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. 3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. 4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: (a) You must give any other recipients of the Work or Derivative Works a copy of this License; and (b) You must cause any modified files to carry prominent notices stating that You changed the files; and (c) You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and (d) If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. 5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. 6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. 7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. 8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. 9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. END OF TERMS AND CONDITIONS APPENDIX: How to apply the Apache License to your work. To apply the Apache License to your work, attach the following boilerplate notice, with the fields enclosed by brackets "[]" replaced with your own identifying information. (Don't include the brackets!) The text should be enclosed in the appropriate comment syntax for the file format. We also recommend that a file or class name and description of purpose be included on the same "printed page" as the copyright notice for easier identification within third-party archives. Copyright [yyyy] [name of copyright owner] Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. unarray-0.1.4/LICENSE-MIT000064400000000000000000000020551046102023000127450ustar 00000000000000MIT License Copyright (c) [year] [fullname] Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. unarray-0.1.4/README.md000064400000000000000000000070441046102023000125730ustar 00000000000000# Unarray [![Docs badge]][docs.rs] [![crates badge]][crates.io] [![github badge]][github] [![license badge]][github] Utilities for working with uninitialized arrays - No dependencies - `#[no_std]` - No panics (all APIs return `Result` or `Option`) This crate provides a few sets of APIs: ### `uninit_buf` and `mark_initialized` These are a pair of functions which are generally used as follows: - stack-allocate an uninitialized array with `uninit_buf` - initialize each element - unsafely convert it to an initialized array with `mark_initialized` For example: ```rust use unarray::*; fn main() { let mut buffer = uninit_buf::(); for slot in &mut buffer { slot.write(123); } let array = unsafe { mark_initialized(buffer) }; assert_eq!(array, [123; 10]); } ``` This is simple to understand, but still requires `unsafe`, which is hard to justify in many cases ### `build_array_*` Functions to build arrays from a length and a function that maps from index -> value: ```rust let even_numbers = build_array(|i| i * 2); // const generic length parameter inferred assert_eq!(even_numbers, [0, 2, 4]); let numbers = build_array_option::(|i| 3.checked_sub(i)); assert_eq!(numbers, Some([3, 2, 1])); let numbers = build_array_option::(|i| 3.checked_sub(i)); assert_eq!(numbers, None); // since a single element failed, the whole operation failed ``` There is also an equivalent `build_array_result` for `Result`-returning functions ### Collecting iterators to arrays It's fairly common to want to collect an iterator into an array, but this is currently tricky in stable Rust, since iterators don't carry compile-time information about their length. Because of this, arrays don't implement `FromIterator`, which is required for `.collect()` to work. Instead, this library provides `ArrayFromIter`, which **does** implement `FromIterator`. This struct can be destructured to get an `Option<[T, N]>`. If the iterator contained exactly `N` elements, this is `Some(array)`, otherwise, it is `None`: ```rust let iter = [1, 2, 3].into_iter(); match iter.collect() { ArrayFromIter(Some([a, b, c])) => println!("exactly 3 elements: {a}, {b}, {c}"), ArrayFromIter(None) => println!("not 3 elements"), } ``` ### `UnarrayArrayExt` extension trait ```rust // 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::()); 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` ## License Licensed under either of * Apache License, Version 2.0 ([LICENSE-APACHE](LICENSE-APACHE) or http://www.apache.org/licenses/LICENSE-2.0) * MIT license ([LICENSE-MIT](LICENSE-MIT) or http://opensource.org/licenses/MIT) at your option. ## Contribution Unless you explicitly state otherwise, any contribution intentionally submitted for inclusion in the work by you, as defined in the Apache-2.0 license, shall be dual licensed as above, without any additional terms or conditions. [Docs badge]: https://img.shields.io/badge/docs.rs-rustdoc-green [docs.rs]: https://docs.rs/unarray/ [crates badge]: https://img.shields.io/crates/v/unarray [crates.io]: https://crates.io/crates/unarray [license badge]: https://img.shields.io/crates/l/unarray [github badge]: https://img.shields.io/github/checks-status/cameron1024/unarray/master [github]: https://github.com/cameron1024/unarray unarray-0.1.4/src/build.rs000064400000000000000000000126031046102023000135450ustar 00000000000000use crate::{mark_initialized, uninit_buf}; /// Build an array with a function that creates elements based on their index /// /// ``` /// # use unarray::*; /// let array: [usize; 5] = build_array(|i| i * 2); /// assert_eq!(array, [0, 2, 4, 6, 8]); /// ``` /// If `f` panics, any already-initialized elements will be dropped **without** running their /// `Drop` implmentations, potentially creating resource leaks. Note that this is still "safe", /// since Rust's notion of "safety" doesn't guarantee destructors are run. /// /// For builder functions which might fail, consider using [`build_array_result`] or /// [`build_array_option`] pub fn build_array T, const N: usize>(mut f: F) -> [T; N] { let mut result = uninit_buf(); for (index, slot) in result.iter_mut().enumerate() { let value = f(index); slot.write(value); } // SAFETY: // We have iterated over every element in result and called `.write()` on it, so every element // is initialized unsafe { mark_initialized(result) } } /// Build an array with a function that creates elements based on their value, short-circuiting if /// any index returns an `Err` /// /// ``` /// # use unarray::*; /// /// let success: Result<_, ()> = build_array_result(|i| Ok(i * 2)); /// assert_eq!(success, Ok([0, 2, 4])); /// ``` /// /// If `f` panics, any already-initialized elements will be dropped **without** running their /// `Drop` implmentations, potentially creating resource leaks. Note that this is still "safe", /// since Rust's notion of "safety" doesn't guarantee destructors are run. /// /// This is similar to the nightly-only [`core::array::try_from_fn`] pub fn build_array_result Result, const N: usize>( mut f: F, ) -> Result<[T; N], E> { let mut result = uninit_buf(); for (index, slot) in result.iter_mut().enumerate() { match f(index) { Ok(value) => slot.write(value), Err(e) => { // SAFETY: // We have failed at `index` which is the `index + 1`th element, so the first // `index` elements are safe to drop result .iter_mut() .take(index) .for_each(|slot| unsafe { slot.assume_init_drop() }); return Err(e); } }; } // SAFETY: // We have iterated over every element in result and called `.write()` on it, so every element // is initialized Ok(unsafe { mark_initialized(result) }) } /// Build an array with a function that creates elements based on their value, short-circuiting if /// any index returns a `None` /// /// ``` /// # use unarray::*; /// let success = build_array_option(|i| Some(i * 2)); /// assert_eq!(success, Some([0, 2, 4])); /// ``` /// /// If `f` panics, any already-initialized elements will be dropped **without** running their /// `Drop` implmentations, potentially creating resource leaks. Note that this is still "safe", /// since Rust's notion of "safety" doesn't guarantee destructors are run. /// /// This is similar to the nightly-only [`core::array::try_from_fn`] pub fn build_array_option Option, const N: usize>( mut f: F, ) -> Option<[T; N]> { let actual_f = |i: usize| -> Result { f(i).ok_or(()) }; match build_array_result(actual_f) { Ok(array) => Some(array), Err(()) => None, } } #[cfg(test)] mod tests { use core::sync::atomic::{AtomicUsize, Ordering}; use super::*; #[test] fn test_build_array() { let array = build_array(|i| i * 2); assert_eq!(array, [0, 2, 4]); } #[test] fn test_build_array_option() { let array = build_array_option(|i| Some(i * 2)); assert_eq!(array, Some([0, 2, 4])); let none: Option<[_; 10]> = build_array_option(|i| if i == 5 { None } else { Some(()) }); assert_eq!(none, None); } #[test] fn test_build_array_result() { let array = build_array_result(|i| Ok::(i * 2)); assert_eq!(array, Ok([0, 2, 4])); let err: Result<[_; 10], _> = build_array_result(|i| if i == 5 { Err(()) } else { Ok(()) }); assert_eq!(err, Err(())); } struct IncrementOnDrop<'a>(&'a AtomicUsize); impl Drop for IncrementOnDrop<'_> { fn drop(&mut self) { self.0.fetch_add(1, Ordering::Relaxed); } } #[test] fn result_doesnt_leak_on_err() { let drop_counter = 0.into(); // this will successfully create 3 structs, fail on the 4th, we expect 3 drops to be // called, since the 4th may be in an inconsistent state let _: Result<[_; 5], _> = build_array_result(|i| { if i == 3 { Err(()) } else { Ok(IncrementOnDrop(&drop_counter)) } }); assert_eq!(drop_counter.load(Ordering::Relaxed), 3); } #[test] fn option_doesnt_leak_on_err() { let drop_counter = 0.into(); // this will successfully create 3 structs, fail on the 4th, we expect 3 drops to be // called, since the 4th may be in an inconsistent state let _: Option<[_; 5]> = build_array_option(|i| { if i == 3 { None } else { Some(IncrementOnDrop(&drop_counter)) } }); assert_eq!(drop_counter.load(Ordering::Relaxed), 3); } } unarray-0.1.4/src/from_iter.rs000064400000000000000000000114771046102023000144440ustar 00000000000000use crate::{mark_initialized, uninit_buf}; use core::iter::FromIterator; /// A wrapper type to collect an [`Iterator`] into an array /// /// ``` /// # use unarray::*; /// let iter = vec![1, 2, 3].into_iter(); /// let ArrayFromIter(array) = iter.collect(); /// /// assert_eq!(array, Some([1, 2, 3])); /// ``` /// Since iterators don't carry compile-time information about their length (even /// [`core::iter::ExactSizeIterator`] only provides this at runtime), collecting may fail if the /// iterator doesn't yield **exactly** `N` elements: /// ``` /// use unarray::*; /// let too_many = vec![1, 2, 3, 4].into_iter(); /// let ArrayFromIter::(option) = too_many.collect(); /// assert!(option.is_none()); /// /// let too_few = vec![1, 2].into_iter(); /// let ArrayFromIter::(option) = too_few.collect(); /// assert!(option.is_none()); /// ``` pub struct ArrayFromIter(pub Option<[T; N]>); impl FromIterator for ArrayFromIter { fn from_iter>(iter: I) -> Self { let mut buffer = uninit_buf::(); let mut iter = iter.into_iter(); let mut buf_iter = buffer.iter_mut(); let mut num_written = 0; loop { let item = iter.next(); let slot = buf_iter.next(); match (item, slot) { (Some(item), Some(slot)) => { num_written += 1; slot.write(item); } // error case, we should free the previous ones and continue (Some(_), None) | (None, Some(_)) => { // SAFETY: // There are `num_written` elements fully initialized, so we can safely drop // them buffer .iter_mut() .take(num_written) .for_each(|slot| unsafe { slot.assume_init_drop() }); return Self(None); } // SAFETY // If this is reached, every prior iteration of the loop has matched // (Some(_), Some(_)). As such, both iterators have yielded the same number of // elements, so every slot has been written to (None, None) => return Self(Some(unsafe { mark_initialized(buffer) })), }; } } } #[cfg(test)] mod tests { use core::{ convert::TryInto, sync::atomic::{AtomicUsize, Ordering}, }; use crate::testing::vec_strategy; use proptest::{prop_assert, prop_assert_eq}; use test_strategy::proptest; use super::*; #[test] fn can_collect_array_from_iter() { let iter = vec![1, 2, 3].into_iter(); let ArrayFromIter(array) = iter.collect(); assert_eq!(array.unwrap(), [1, 2, 3]); } #[test] fn fails_if_incorrect_number_of_elements() { let iter = [1, 2, 3].iter(); let ArrayFromIter::<_, 4>(array) = iter.collect(); assert!(array.is_none()); let iter = [1, 2, 3].iter(); let ArrayFromIter::<_, 2>(array) = iter.collect(); assert!(array.is_none()); } const LEN: usize = 100; const SHORT_LEN: usize = LEN - 1; const LONG_LEN: usize = LEN + 1; #[derive(Clone)] struct IncrementOnDrop<'a>(&'a AtomicUsize); impl Drop for IncrementOnDrop<'_> { fn drop(&mut self) { self.0.fetch_add(1, Ordering::Relaxed); } } #[test] fn doesnt_leak_too_long() { let drop_count = 0.into(); let ArrayFromIter::<_, 3>(_) = vec![IncrementOnDrop(&drop_count); 4].into_iter().collect(); // since it failed, all 4 should be dropped assert_eq!(drop_count.load(Ordering::Relaxed), 4); } #[test] fn doesnt_leak_too_short() { let drop_count = 0.into(); let ArrayFromIter::<_, 3>(_) = vec![IncrementOnDrop(&drop_count); 2].into_iter().collect(); // since it failed, both should be dropped assert_eq!(drop_count.load(Ordering::Relaxed), 2); } #[proptest] #[cfg_attr(miri, ignore)] fn undersized_proptest(#[strategy(vec_strategy(LEN))] vec: Vec) { let ArrayFromIter::(array) = vec.into_iter().collect(); prop_assert!(array.is_none()); } #[proptest] #[cfg_attr(miri, ignore)] fn oversized_proptest(#[strategy(vec_strategy(LEN))] vec: Vec) { let ArrayFromIter::(array) = vec.into_iter().collect(); prop_assert!(array.is_none()); } #[proptest] #[cfg_attr(miri, ignore)] fn just_right_proptest(#[strategy(vec_strategy(LEN))] vec: Vec) { let expected: [String; LEN] = vec.clone().try_into().unwrap(); let ArrayFromIter(array) = vec.into_iter().collect(); prop_assert_eq!(array.unwrap(), expected); } } unarray-0.1.4/src/lib.rs000064400000000000000000000167531046102023000132260ustar 00000000000000//! # 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::()); //! 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::` //! 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; N]` to a `[T; N]` /// /// ``` /// # 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]) /// ``` /// /// 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; 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::(); /// 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::(); /// 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(src: [MaybeUninit; N]) -> [T; N] { core::mem::transmute_copy::<[MaybeUninit; 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; N]`, since this type explicitly requires no /// initialization /// /// ``` /// # 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]) /// ``` /// /// This is similar to the nightly-only [`core::mem::MaybeUninit::uninit_array`] pub fn uninit_buf() -> [MaybeUninit; N] { // SAFETY: // This is safe because we are assuming that a `[MaybeUninit; 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() } } unarray-0.1.4/src/map.rs000064400000000000000000000156021046102023000132250ustar 00000000000000use crate::{mark_initialized, uninit_buf}; /// An extension trait that adds methods to `[T; N]` /// /// This trait provides [`UnarrayArrayExt::map_result`] and [`UnarrayArrayExt::map_option`], /// which provide functionality similar to the nightly-only [`array::try_map`] pub trait UnarrayArrayExt { /// Maps an array, short-circuiting if any element produces an `Err` /// /// ``` /// # use unarray::*; /// let elements = ["123", "234", "345"]; /// let mapped = elements.map_result(|s| s.parse()); /// assert_eq!(mapped, Ok([123, 234, 345])); /// ``` /// /// This function applies `f` to every element. If any element produces an `Err`, the function /// immediately returns that error. Otherwise, it returns `Ok(result)` where `result` contains /// the mapped elements in an array. /// /// This function does not allocate space on the heap /// /// For functions that return an `Option`, consider using [`UnarrayArrayExt::map_option`] fn map_result(self, f: impl FnMut(T) -> Result) -> Result<[S; N], E>; /// Maps an array, short-circuiting if any element produces a `None` /// /// ``` /// # use unarray::*; /// fn parse(s: &str) -> Option { /// match s { /// "true" => Some(true), /// "false" => Some(false), /// _ => None, /// } /// } /// /// let elements = ["true", "false", "true"]; /// let mapped = elements.map_option(parse); /// assert_eq!(mapped, Some([true, false, true])); /// ``` /// /// This function applies `f` to every element. If any element produces `None`, the function /// immediately returns `None`. Otherwise, it returns `Some(result)` where `result` contains /// the mapped elements in an array. /// /// This function does not allocate space on the heap /// /// For functions that return an `Result`, consider using [`UnarrayArrayExt::map_result`] fn map_option(self, f: impl FnMut(T) -> Option) -> Option<[S; N]>; } impl UnarrayArrayExt for [T; N] { fn map_result(self, mut f: impl FnMut(T) -> Result) -> Result<[S; N], E> { let mut result = uninit_buf(); // This is quaranteed to loop over every element (or panic), since both `result` and `self` have N elements // If a panic occurs, uninitialized data is never dropped, since `MaybeUninit` wraps its // contained data in `ManuallyDrop` for (index, (item, slot)) in IntoIterator::into_iter(self).zip(&mut result).enumerate() { match f(item) { Ok(s) => slot.write(s), Err(e) => { // SAFETY: // We have failed at `index` which is the `index + 1`th element, so the first // `index` elements are safe to drop result .iter_mut() .take(index) .for_each(|slot| unsafe { slot.assume_init_drop() }); return Err(e); } }; } // SAFETY: // At this point in execution, we have iterated over all elements of `result`. If any // errors were encountered, we would have already returned. So it's safe to remove the // MaybeUninit wrapper Ok(unsafe { mark_initialized(result) }) } fn map_option(self, mut f: impl FnMut(T) -> Option) -> Option<[S; N]> { // transform to a `Result`-returning function so we can avoid duplicating unsafe code let actual_f = |t: T| -> Result { f(t).ok_or(()) }; let result: Result<[S; N], ()> = UnarrayArrayExt::map_result(self, actual_f); match result { Ok(result) => Some(result), Err(()) => None, } } } #[cfg(test)] mod tests { use core::{ convert::TryInto, sync::atomic::{AtomicUsize, Ordering}, }; use super::UnarrayArrayExt; use crate::testing::array_strategy; use proptest::prelude::*; use test_strategy::proptest; #[test] fn test_map_option() { let array = [1, 2, 3]; let result = array.map_option(|i| Some(i * 2)).unwrap(); assert_eq!(result, [2, 4, 6]); } #[test] #[should_panic] fn test_map_option_panic() { let array = [1, 2, 3]; array.map_option(|i| { if i > 2 { panic!(); } Some(i) }); } #[test] fn test_map_result() { let array = [1, 2, 3]; let result: Result<_, ()> = array.map_result(|i| Ok(i * 2)); assert_eq!(result.unwrap(), [2, 4, 6]); } #[test] #[should_panic] fn test_map_result_panic() { let array = [1, 2, 3]; let _ = array.map_result(|i| -> Result { if i > 2 { panic!(); } Ok(i) }); } struct IncrementOnDrop<'a>(&'a AtomicUsize); impl Drop for IncrementOnDrop<'_> { fn drop(&mut self) { self.0.fetch_add(1, Ordering::Relaxed); } } #[test] fn map_array_result_doesnt_leak() { let drop_counter = 0.into(); // this will successfully create 3 structs, fail on the 4th, we expect 3 drops to be // called, since the 4th may be in an inconsistent state let _ = [0, 1, 2, 3, 4].map_result(|i| { if i == 3 { Err(()) } else { Ok(IncrementOnDrop(&drop_counter)) } }); assert_eq!(drop_counter.load(Ordering::Relaxed), 3); } #[test] fn map_array_option_doesnt_leak() { let drop_counter = 0.into(); // this will successfully create 3 structs, fail on the 4th, we expect 3 drops to be // called, since the 4th may be in an inconsistent state let _ = [0, 1, 2, 3, 4].map_option(|i| { if i == 3 { None } else { Some(IncrementOnDrop(&drop_counter)) } }); assert_eq!(drop_counter.load(Ordering::Relaxed), 3); } const LEN: usize = 100; #[proptest] #[cfg_attr(miri, ignore)] fn proptest_option_map(#[strategy(array_strategy::())] array: [String; LEN]) { let expected = array.iter().map(|s| s.len()).collect::>(); let expected: [usize; LEN] = expected.try_into().unwrap(); let result = array.map_option(|s| Some(s.len())); prop_assert_eq!(expected, result.unwrap()); } #[proptest] #[cfg_attr(miri, ignore)] fn proptest_result_map(#[strategy(array_strategy::())] array: [String; LEN]) { let expected = array.iter().map(|s| s.len()).collect::>(); let expected: [usize; LEN] = expected.try_into().unwrap(); let result: Result<_, ()> = array.map_result(|s| Ok(s.len())); prop_assert_eq!(expected, result.unwrap()); } } unarray-0.1.4/src/testing.rs000064400000000000000000000005141046102023000141210ustar 00000000000000use core::convert::TryInto; use proptest::collection::*; use proptest::prelude::*; pub fn array_strategy() -> impl Strategy { vec(any::(), N).prop_map(|v| v.try_into().unwrap()) } pub fn vec_strategy(n: usize) -> impl Strategy> { vec(any::(), n) }