├── .github └── workflows │ └── main.yml ├── .gitignore ├── Cargo.toml ├── LICENSE ├── README.md ├── fuzz ├── .gitignore ├── Cargo.toml └── fuzz_targets │ └── vec_like_ops.rs ├── rust-toolchain ├── src ├── lib.rs ├── single.rs └── vec.rs └── tests ├── comparability_tests.rs └── std_required_tests.rs /.github/workflows/main.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: [pull_request] 4 | 5 | jobs: 6 | build_and_test: 7 | name: Build and Test 8 | runs-on: ubuntu-latest 9 | steps: 10 | - name: Checkout 11 | uses: actions/checkout@v2 12 | 13 | # - name: Find latest rust stable version 14 | # id: rust_stable_version 15 | # run: | 16 | # VER=$(curl -s https://static.rust-lang.org/dist/channel-rust-stable.toml | grep -A 1 "\[pkg\.rust\]" | grep version | sed -r 's/.*\"(.*)\".*/\1/') 17 | # echo "$VER" 18 | # echo "##[set-output name=rustc;]$VER" 19 | 20 | # - name: Cache rust toolchain 21 | # id: cache_rust_toolchain 22 | # uses: actions/cache@v1 23 | # with: 24 | # key: ${{ steps.rust_stable_version.outputs.rustc }} 25 | # path: "~/.rustup/toolchains/stable-x86_64-unknown-linux-gnu/" 26 | 27 | - name: Install stable toolchain 28 | if: steps.cache_rust_toolchain.output.cache-hit != 'true' 29 | uses: actions-rs/toolchain@v1 30 | with: 31 | profile: minimal 32 | toolchain: stable 33 | components: rustfmt, clippy 34 | override: true 35 | 36 | - name: Build 37 | uses: actions-rs/cargo@v1 38 | with: 39 | command: build 40 | 41 | - name: Test 42 | uses: actions-rs/cargo@v1 43 | with: 44 | command: test 45 | 46 | - name: Check formatting 47 | uses: actions-rs/cargo@v1 48 | with: 49 | command: fmt 50 | args: --all -- --check 51 | 52 | - name: Clippy 53 | uses: actions-rs/cargo@v1 54 | with: 55 | command: clippy 56 | miri: 57 | runs-on: ubuntu-latest 58 | steps: 59 | - uses: actions/checkout@v2 60 | 61 | - uses: actions-rs/toolchain@v1 62 | with: 63 | profile: minimal 64 | toolchain: nightly 65 | override: true 66 | components: miri, rust-src 67 | 68 | - uses: actions-rs/cargo@v1 69 | with: 70 | command: miri 71 | args: setup 72 | 73 | - uses: actions-rs/cargo@v1 74 | with: 75 | command: miri 76 | args: test --all-features -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Generated by Cargo 2 | # will have compiled files and executables 3 | /target/ 4 | 5 | # Remove Cargo.lock from gitignore if creating an executable, leave it for libraries 6 | # More information here https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html 7 | Cargo.lock 8 | 9 | # These are backup files generated by rustfmt 10 | **/*.rs.bk 11 | 12 | # IDE files 13 | .idea 14 | 15 | # tarpaulin reports 16 | tarpaulin-report.* 17 | 18 | # fuzz related 19 | fuzz/target 20 | fuzz/artifacts 21 | fuzz/corpus 22 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "fixed-slice-vec" 3 | version = "0.10.0" 4 | authors = [ 5 | "Zachary Pierce ", 6 | "Jon Lamb ", 7 | "Russell Mull ", 8 | "dan pittman ", 9 | ] 10 | edition = "2018" 11 | license = "Apache-2.0" 12 | homepage = "https://github.com/auxoncorp/fixed-slice-vec" 13 | repository = "https://github.com/auxoncorp/fixed-slice-vec" 14 | readme = "README.md" 15 | description = "`FixedSliceVec` is a dynamic length Vec with runtime-determined maximum capacity backed by a slice." 16 | keywords = ["vec", "vector", "no_std", "slice", "no-std"] 17 | categories = ["embedded", "data-structures", "no-std"] 18 | 19 | [dev-dependencies] 20 | arrayvec = "0.5" 21 | proptest = "0.9.6" 22 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | 2 | Apache License 3 | Version 2.0, January 2004 4 | http://www.apache.org/licenses/ 5 | 6 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 7 | 8 | 1. Definitions. 9 | 10 | "License" shall mean the terms and conditions for use, reproduction, 11 | and distribution as defined by Sections 1 through 9 of this document. 12 | 13 | "Licensor" shall mean the copyright owner or entity authorized by 14 | the copyright owner that is granting the License. 15 | 16 | "Legal Entity" shall mean the union of the acting entity and all 17 | other entities that control, are controlled by, or are under common 18 | control with that entity. For the purposes of this definition, 19 | "control" means (i) the power, direct or indirect, to cause the 20 | direction or management of such entity, whether by contract or 21 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 22 | outstanding shares, or (iii) beneficial ownership of such entity. 23 | 24 | "You" (or "Your") shall mean an individual or Legal Entity 25 | exercising permissions granted by this License. 26 | 27 | "Source" form shall mean the preferred form for making modifications, 28 | including but not limited to software source code, documentation 29 | source, and configuration files. 30 | 31 | "Object" form shall mean any form resulting from mechanical 32 | transformation or translation of a Source form, including but 33 | not limited to compiled object code, generated documentation, 34 | and conversions to other media types. 35 | 36 | "Work" shall mean the work of authorship, whether in Source or 37 | Object form, made available under the License, as indicated by a 38 | copyright notice that is included in or attached to the work 39 | (an example is provided in the Appendix below). 40 | 41 | "Derivative Works" shall mean any work, whether in Source or Object 42 | form, that is based on (or derived from) the Work and for which the 43 | editorial revisions, annotations, elaborations, or other modifications 44 | represent, as a whole, an original work of authorship. For the purposes 45 | of this License, Derivative Works shall not include works that remain 46 | separable from, or merely link (or bind by name) to the interfaces of, 47 | the Work and Derivative Works thereof. 48 | 49 | "Contribution" shall mean any work of authorship, including 50 | the original version of the Work and any modifications or additions 51 | to that Work or Derivative Works thereof, that is intentionally 52 | submitted to Licensor for inclusion in the Work by the copyright owner 53 | or by an individual or Legal Entity authorized to submit on behalf of 54 | the copyright owner. For the purposes of this definition, "submitted" 55 | means any form of electronic, verbal, or written communication sent 56 | to the Licensor or its representatives, including but not limited to 57 | communication on electronic mailing lists, source code control systems, 58 | and issue tracking systems that are managed by, or on behalf of, the 59 | Licensor for the purpose of discussing and improving the Work, but 60 | excluding communication that is conspicuously marked or otherwise 61 | designated in writing by the copyright owner as "Not a Contribution." 62 | 63 | "Contributor" shall mean Licensor and any individual or Legal Entity 64 | on behalf of whom a Contribution has been received by Licensor and 65 | subsequently incorporated within the Work. 66 | 67 | 2. Grant of Copyright License. Subject to the terms and conditions of 68 | this License, each Contributor hereby grants to You a perpetual, 69 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 70 | copyright license to reproduce, prepare Derivative Works of, 71 | publicly display, publicly perform, sublicense, and distribute the 72 | Work and such Derivative Works in Source or Object form. 73 | 74 | 3. Grant of Patent License. Subject to the terms and conditions of 75 | this License, each Contributor hereby grants to You a perpetual, 76 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 77 | (except as stated in this section) patent license to make, have made, 78 | use, offer to sell, sell, import, and otherwise transfer the Work, 79 | where such license applies only to those patent claims licensable 80 | by such Contributor that are necessarily infringed by their 81 | Contribution(s) alone or by combination of their Contribution(s) 82 | with the Work to which such Contribution(s) was submitted. If You 83 | institute patent litigation against any entity (including a 84 | cross-claim or counterclaim in a lawsuit) alleging that the Work 85 | or a Contribution incorporated within the Work constitutes direct 86 | or contributory patent infringement, then any patent licenses 87 | granted to You under this License for that Work shall terminate 88 | as of the date such litigation is filed. 89 | 90 | 4. Redistribution. You may reproduce and distribute copies of the 91 | Work or Derivative Works thereof in any medium, with or without 92 | modifications, and in Source or Object form, provided that You 93 | meet the following conditions: 94 | 95 | (a) You must give any other recipients of the Work or 96 | Derivative Works a copy of this License; and 97 | 98 | (b) You must cause any modified files to carry prominent notices 99 | stating that You changed the files; and 100 | 101 | (c) You must retain, in the Source form of any Derivative Works 102 | that You distribute, all copyright, patent, trademark, and 103 | attribution notices from the Source form of the Work, 104 | excluding those notices that do not pertain to any part of 105 | the Derivative Works; and 106 | 107 | (d) If the Work includes a "NOTICE" text file as part of its 108 | distribution, then any Derivative Works that You distribute must 109 | include a readable copy of the attribution notices contained 110 | within such NOTICE file, excluding those notices that do not 111 | pertain to any part of the Derivative Works, in at least one 112 | of the following places: within a NOTICE text file distributed 113 | as part of the Derivative Works; within the Source form or 114 | documentation, if provided along with the Derivative Works; or, 115 | within a display generated by the Derivative Works, if and 116 | wherever such third-party notices normally appear. The contents 117 | of the NOTICE file are for informational purposes only and 118 | do not modify the License. You may add Your own attribution 119 | notices within Derivative Works that You distribute, alongside 120 | or as an addendum to the NOTICE text from the Work, provided 121 | that such additional attribution notices cannot be construed 122 | as modifying the License. 123 | 124 | You may add Your own copyright statement to Your modifications and 125 | may provide additional or different license terms and conditions 126 | for use, reproduction, or distribution of Your modifications, or 127 | for any such Derivative Works as a whole, provided Your use, 128 | reproduction, and distribution of the Work otherwise complies with 129 | the conditions stated in this License. 130 | 131 | 5. Submission of Contributions. Unless You explicitly state otherwise, 132 | any Contribution intentionally submitted for inclusion in the Work 133 | by You to the Licensor shall be under the terms and conditions of 134 | this License, without any additional terms or conditions. 135 | Notwithstanding the above, nothing herein shall supersede or modify 136 | the terms of any separate license agreement you may have executed 137 | with Licensor regarding such Contributions. 138 | 139 | 6. Trademarks. This License does not grant permission to use the trade 140 | names, trademarks, service marks, or product names of the Licensor, 141 | except as required for reasonable and customary use in describing the 142 | origin of the Work and reproducing the content of the NOTICE file. 143 | 144 | 7. Disclaimer of Warranty. Unless required by applicable law or 145 | agreed to in writing, Licensor provides the Work (and each 146 | Contributor provides its Contributions) on an "AS IS" BASIS, 147 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 148 | implied, including, without limitation, any warranties or conditions 149 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 150 | PARTICULAR PURPOSE. You are solely responsible for determining the 151 | appropriateness of using or redistributing the Work and assume any 152 | risks associated with Your exercise of permissions under this License. 153 | 154 | 8. Limitation of Liability. In no event and under no legal theory, 155 | whether in tort (including negligence), contract, or otherwise, 156 | unless required by applicable law (such as deliberate and grossly 157 | negligent acts) or agreed to in writing, shall any Contributor be 158 | liable to You for damages, including any direct, indirect, special, 159 | incidental, or consequential damages of any character arising as a 160 | result of this License or out of the use or inability to use the 161 | Work (including but not limited to damages for loss of goodwill, 162 | work stoppage, computer failure or malfunction, or any and all 163 | other commercial damages or losses), even if such Contributor 164 | has been advised of the possibility of such damages. 165 | 166 | 9. Accepting Warranty or Additional Liability. While redistributing 167 | the Work or Derivative Works thereof, You may choose to offer, 168 | and charge a fee for, acceptance of support, warranty, indemnity, 169 | or other liability obligations and/or rights consistent with this 170 | License. However, in accepting such obligations, You may act only 171 | on Your own behalf and on Your sole responsibility, not on behalf 172 | of any other Contributor, and only if You agree to indemnify, 173 | defend, and hold each Contributor harmless for any liability 174 | incurred by, or claims asserted against, such Contributor by reason 175 | of your accepting any such warranty or additional liability. 176 | 177 | END OF TERMS AND CONDITIONS 178 | 179 | Copyright 2020 Auxon Corporation 180 | 181 | Licensed under the Apache License, Version 2.0 (the "License"); 182 | you may not use this file except in compliance with the License. 183 | You may obtain a copy of the License at 184 | 185 | http://www.apache.org/licenses/LICENSE-2.0 186 | 187 | Unless required by applicable law or agreed to in writing, software 188 | distributed under the License is distributed on an "AS IS" BASIS, 189 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 190 | See the License for the specific language governing permissions and 191 | limitations under the License. 192 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # fixed-slice-vec 2 | 3 | `FixedSliceVec` is a dynamic length Vec with runtime-determined maximum capacity backed by a slice. 4 | 5 | ### Overview 6 | 7 | This library is focused on meeting the following, narrow use case: 8 | * **`no_std`** : Rust programming without the std library. 9 | * **No global allocator**: No access to the `alloc` crate 10 | * **Runtime capacity** : Maximum possible items in a collection or maximum 11 | possible backing bytes of storage is unknown until runtime. 12 | 13 | ### Getting Started 14 | 15 | `fixed-slice-vec` is a Rust library, built and tested via Cargo. It 16 | has no dependencies outside of the Rust core library. 17 | 18 | To add `fixed-slice-vec` to your Rust project, add a dependency to it 19 | in your Cargo.toml file. 20 | 21 | ```toml 22 | fixed-slice-vec = "0.10.0" 23 | ``` 24 | 25 | ### Usage 26 | 27 | #### FixedSliceVec 28 | 29 | In your Rust project source code, you can create a FixedSliceVec a number of 30 | ways (see the project Rust API docs for details). 31 | The most common form of construction is from a slice of uninitialized bytes. 32 | 33 | ```rust 34 | use fixed_slice_vec::FixedSliceVec; 35 | use core::mem::MaybeUninit; 36 | // Safe to construct arrays of uninitialized values. 37 | let mut bytes: [MaybeUninit; 1024] = unsafe { MaybeUninit::uninit().assume_init() }; 38 | let byte_slice = &mut bytes[..512]; 39 | let mut vec: FixedSliceVec = FixedSliceVec::from_uninit_bytes(byte_slice); 40 | 41 | assert_eq!(0, vec.len()); 42 | assert!(vec.capacity() >= 63, "The exact capacity will depend on source-slice alignment"); 43 | 44 | vec.try_push(2.7f64).expect("Ran out of capacity unexpectedly"); 45 | assert_eq!(1, vec.len()); 46 | 47 | vec.clear(); 48 | assert!(vec.is_empty()); 49 | ``` 50 | 51 | #### single module 52 | 53 | As a companion to `FixedSliceVec`, the `single` submodule provides 54 | functions for working with individual Rust values backed by arbitrary 55 | byte slices. See the API Docs for details and examples. 56 | 57 | #### Comparison 58 | 59 | Several other `Vec`-like crates exist and should be considered 60 | as possible alternatives to `FixedSliceVec`. 61 | 62 | * The standard library's [Vec](https://doc.rust-lang.org/std/vec/struct.Vec.html) 63 | has a runtime dynamic capacity backed by an allocator. This should probably 64 | be your first choice if you have access to an allocator. 65 | * [ArrayVec](https://crates.io/crates/arrayvec) has a compile-time 66 | fixed capacity. It is widely used and available on stable. 67 | * [StaticVec](https://crates.io/crates/staticvec) has a compile-time 68 | fixed capacity. It uses recent const generic features and is currently 69 | nightly-only. 70 | * [SliceVec](https://crates.io/crates/slicevec) has runtime fixed capacity. 71 | This is the closest in its target use case to `FixedSliceVec`. We 72 | only discovered it existed after developing `FixedSliceVec`, so there's some 73 | evidence of convergent design or needs. It appears largely 74 | unmaintained over the last few years, does not make use of the 75 | [MaybeUninit](https://doc.rust-lang.org/std/mem/union.MaybeUninit.html) 76 | pattern for handling uninitialized data in Rust, and does not drop items 77 | correctly in some cases. It does not support creating an instance from raw bytes 78 | and requires `Default` elements for some operations. 79 | 80 | 81 | ### License 82 | 83 | Copyright 2020 Auxon Corporation, released under the [Apache 2.0 license](./LICENSE). 84 | -------------------------------------------------------------------------------- /fuzz/.gitignore: -------------------------------------------------------------------------------- 1 | 2 | target 3 | corpus 4 | artifacts 5 | -------------------------------------------------------------------------------- /fuzz/Cargo.toml: -------------------------------------------------------------------------------- 1 | 2 | [package] 3 | name = "fixed-slice-vec-fuzz" 4 | version = "0.0.0" 5 | authors = ["Automatically generated"] 6 | publish = false 7 | edition = "2018" 8 | 9 | [package.metadata] 10 | cargo-fuzz = true 11 | 12 | [dependencies] 13 | libfuzzer-sys = { version = "0.3", features = ["arbitrary-derive"] } 14 | 15 | [dependencies.fixed-slice-vec] 16 | path = ".." 17 | 18 | # Prevent this from interfering with workspaces 19 | [workspace] 20 | members = ["."] 21 | 22 | [[bin]] 23 | name = "vec_like_ops" 24 | path = "fuzz_targets/vec_like_ops.rs" 25 | test = false 26 | doc = false 27 | -------------------------------------------------------------------------------- /fuzz/fuzz_targets/vec_like_ops.rs: -------------------------------------------------------------------------------- 1 | #![no_main] 2 | extern crate fixed_slice_vec; 3 | extern crate libfuzzer_sys; 4 | use fixed_slice_vec::FixedSliceVec; 5 | use libfuzzer_sys::{arbitrary, fuzz_target}; 6 | 7 | #[derive(Debug, Clone, arbitrary::Arbitrary)] 8 | pub struct Harness { 9 | storage: Vec, 10 | ops: Vec>, 11 | } 12 | #[derive(Debug, Clone, arbitrary::Arbitrary)] 13 | pub enum VecLikeOp { 14 | Push(T), 15 | Pop, 16 | Clear, 17 | Truncate(usize), 18 | Remove(usize), 19 | SwapRemove(usize), 20 | Extend(Vec), 21 | } 22 | 23 | fuzz_target!(|harness: Harness| { 24 | let Harness { mut storage, ops } = harness; 25 | let mut fsv: FixedSliceVec = unsafe { FixedSliceVec::from_bytes(&mut storage[..]) }; 26 | for op in ops { 27 | match op { 28 | VecLikeOp::Clear => { 29 | fsv.clear(); 30 | } 31 | VecLikeOp::Pop => { 32 | let _ = fsv.pop(); 33 | } 34 | VecLikeOp::Push(s) => { 35 | let _ = fsv.try_push(s); 36 | } 37 | VecLikeOp::Remove(index) => { 38 | let _ = fsv.try_remove(index); 39 | } 40 | VecLikeOp::SwapRemove(index) => { 41 | let _ = fsv.try_swap_remove(index); 42 | } 43 | VecLikeOp::Truncate(len) => { 44 | let _ = fsv.truncate(len); 45 | } 46 | VecLikeOp::Extend(other) => { 47 | let _ = fsv.try_extend(other.into_iter()); 48 | } 49 | } 50 | } 51 | }); 52 | -------------------------------------------------------------------------------- /rust-toolchain: -------------------------------------------------------------------------------- 1 | stable 2 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | //! `FixedSliceVec` is a dynamic length Vec with runtime-determined maximum capacity backed by a slice. 2 | //! 3 | //! ## Overview 4 | //! 5 | //! This library is focused on meeting the following, narrow use case: 6 | //! * **`no_std`** : Rust programming without the std library. 7 | //! * **No global allocator**: No access to the `alloc` crate 8 | //! * **Runtime capacity** : Maximum possible items in a collection or maximum 9 | //! possible backing bytes of storage is unknown until runtime. 10 | //! 11 | //! ## Getting Started 12 | //! 13 | //! `fixed-slice-vec` is a Rust library, built and tested via Cargo. It 14 | //! has no dependencies outside of the Rust core library. 15 | //! 16 | //! To add `fixed-slice-vec` to your Rust project, add a dependency to it 17 | //! in your Cargo.toml file. 18 | //! 19 | //! ```toml 20 | //! fixed-slice-vec = "0.10.0" 21 | //! ``` 22 | //! 23 | //! ## Usage 24 | //! 25 | //! ### FixedSliceVec 26 | //! 27 | //! In your Rust project source code, you can create a FixedSliceVec a number of 28 | //! ways (see the project Rust API docs for details). 29 | //! The most common form of construction is from a slice of uninitialized bytes. 30 | //! 31 | //! ```rust 32 | //! use fixed_slice_vec::FixedSliceVec; 33 | //! use core::mem::MaybeUninit; 34 | //! // Safe to construct arrays of uninitialized values. 35 | //! let mut bytes: [MaybeUninit; 1024] = unsafe { MaybeUninit::uninit().assume_init() }; 36 | //! let byte_slice = &mut bytes[..512]; 37 | //! let mut vec: FixedSliceVec = FixedSliceVec::from_uninit_bytes(byte_slice); 38 | //! 39 | //! assert_eq!(0, vec.len()); 40 | //! assert!(vec.capacity() >= 63, "The exact capacity will depend on source-slice alignment"); 41 | //! 42 | //! vec.try_push(2.7f64).expect("Ran out of capacity unexpectedly"); 43 | //! assert_eq!(1, vec.len()); 44 | //! 45 | //! vec.clear(); 46 | //! assert!(vec.is_empty()); 47 | //! ``` 48 | //! 49 | //! ### single module 50 | //! 51 | //! As a companion to `FixedSliceVec`, the `single` submodule provides 52 | //! functions for working with individual Rust values backed by arbitrary 53 | //! byte slices. See the API Docs for details and examples. 54 | //! 55 | //! ### Comparison 56 | //! 57 | //! Several other `Vec`-like crates exist and should be considered 58 | //! as possible alternatives to `FixedSliceVec`. 59 | //! 60 | //! * The standard library's [Vec](https://doc.rust-lang.org/std/vec/struct.Vec.html) 61 | //! has a runtime dynamic capacity backed by an allocator. This should probably 62 | //! be your first choice if you have access to an allocator. 63 | //! * [ArrayVec](https://crates.io/crates/arrayvec) has a compile-time 64 | //! fixed capacity. It is widely used and available on stable. 65 | //! * [StaticVec](https://crates.io/crates/staticvec) has a compile-time 66 | //! fixed capacity. It uses recent const generic features and is currently 67 | //! nightly-only. 68 | //! * [SliceVec](https://crates.io/crates/slicevec) has runtime fixed capacity. 69 | //! This is the closest in its target use case to `FixedSliceVec`. We 70 | //! only discovered it existed after developing `FixedSliceVec`, so there's some 71 | //! evidence of convergent design or needs. It appears largely 72 | //! unmaintained over the last few years, does not make use of the 73 | //! [MaybeUninit](https://doc.rust-lang.org/std/mem/union.MaybeUninit.html) 74 | //! pattern for handling uninitialized data in Rust, and does not drop items 75 | //! correctly in some cases. It does not support creating an instance from raw bytes 76 | //! and requires `Default` elements for some operations. 77 | //! 78 | //! 79 | //! ## License 80 | //! 81 | //! Copyright 2020 Auxon Corporation, released under the [Apache 2.0 license](./LICENSE). 82 | #![no_std] 83 | #![deny(warnings)] 84 | #![deny(missing_docs)] 85 | #![deny(clippy::all)] 86 | 87 | pub mod single; 88 | pub mod vec; 89 | 90 | pub use crate::vec::*; 91 | -------------------------------------------------------------------------------- /src/single.rs: -------------------------------------------------------------------------------- 1 | //! Functions relating to managing references to well-typed Rust values 2 | //! stored inside arbitrary byte slices. 3 | use core::mem::*; 4 | 5 | /// Plenty can go wrong when attempting to embed a value in arbitrary bytes 6 | #[derive(Debug, PartialEq)] 7 | pub enum EmbedValueError { 8 | /// Difficulty generating the necessary mutable reference 9 | /// to the embedded location. 10 | SplitUninitError(SplitUninitError), 11 | /// Initializing the value went wrong somehow. 12 | ConstructionError(E), 13 | } 14 | 15 | impl From for EmbedValueError { 16 | #[inline] 17 | fn from(e: SplitUninitError) -> Self { 18 | EmbedValueError::SplitUninitError(e) 19 | } 20 | } 21 | 22 | /// Initialize a value into location within a provided byte slice, 23 | /// and return a mutable reference to that value. 24 | /// 25 | /// The user-provided constructor function also has access to the 26 | /// portions of the byte slice after the region allocated for 27 | /// the embedded value itself. 28 | /// 29 | /// # Safety 30 | /// 31 | /// Panics in debug mode if destination slice's underlying 32 | /// pointer has somehow been contrived to be null. 33 | /// 34 | /// This function does nothing to ensure that the embedded value will be 35 | /// dropped when the returned reference is dropped. The caller is 36 | /// responsible for cleaning up any side effects of the embedded value 37 | /// outside of the destination slice. 38 | /// 39 | /// If the item type `T` contains any padding bytes 40 | /// then those padding bytes may be observable in the provided slice 41 | /// after the reference is dropped. Observing padding bytes is 42 | /// undefined behavior. 43 | #[inline] 44 | pub unsafe fn embed<'a, T, F, E>( 45 | destination: &'a mut [u8], 46 | f: F, 47 | ) -> Result<&'a mut T, EmbedValueError> 48 | where 49 | F: FnOnce(&'a mut [u8]) -> Result, 50 | { 51 | debug_assert!(!destination.as_ptr().is_null()); 52 | let (_prefix, uninit_ref, suffix) = split_uninit_from_bytes(destination)?; 53 | let ptr = uninit_ref.as_mut_ptr(); 54 | core::ptr::write(ptr, f(suffix).map_err(EmbedValueError::ConstructionError)?); 55 | // We literally just initialized the value, so it's safe to call it init 56 | if let Some(ptr) = ptr.as_mut() { 57 | Ok(ptr) 58 | } else { 59 | unreachable!("Just initialized the value and the pointer is based on a non-null slice") 60 | } 61 | } 62 | 63 | /// Initialize a value into location within a provided byte slice, 64 | /// and return a mutable reference to that value. 65 | /// 66 | /// The user-provided constructor function also has access to the 67 | /// portions of the byte slice after the region allocated for 68 | /// the embedded value itself. 69 | /// 70 | /// # Safety 71 | /// 72 | /// Panics in debug mode if destination slice's underlying 73 | /// pointer has somehow been contrived to be null. 74 | /// 75 | /// This function does nothing to ensure that the embedded value will be 76 | /// dropped when the returned reference is dropped. The caller is 77 | /// responsible for cleaning up any side effects of the embedded value 78 | /// outside of the destination slice. 79 | #[inline] 80 | pub fn embed_uninit<'a, T, F, E>( 81 | destination: &'a mut [MaybeUninit], 82 | f: F, 83 | ) -> Result<&'a mut T, EmbedValueError> 84 | where 85 | F: FnOnce(&'a mut [MaybeUninit]) -> Result, 86 | { 87 | debug_assert!(!destination.as_ptr().is_null()); 88 | let (_prefix, uninit_ref, suffix) = split_uninit_from_uninit_bytes(destination)?; 89 | unsafe { 90 | let ptr = uninit_ref.as_mut_ptr(); 91 | core::ptr::write(ptr, f(suffix).map_err(EmbedValueError::ConstructionError)?); 92 | // We literally just initialized the value, so it's safe to call it init 93 | if let Some(ptr) = ptr.as_mut() { 94 | Ok(ptr) 95 | } else { 96 | unreachable!("Just initialized the value and the pointer is based on a non-null slice") 97 | } 98 | } 99 | } 100 | 101 | /// Plenty can go wrong when attempting to find space for a value in arbitrary bytes. 102 | #[derive(Debug, PartialEq)] 103 | pub enum SplitUninitError { 104 | /// Zero sized types shouldn't be placed anywhere into a byte slice anyhow. 105 | ZeroSizedTypesUnsupported, 106 | /// Could not calculate a valid alignment offset from the given the 107 | /// starting point which would result in a properly-aligned value. 108 | Unalignable, 109 | /// Could not theoretically fit the target value into the provided byte slice 110 | /// due to a combination of the type's alignment and size. 111 | InsufficientSpace, 112 | } 113 | 114 | /// Split out a mutable reference to an uninitialized struct at an available 115 | /// location within a provided slice of bytes. 116 | /// 117 | /// Does not access or mutate the content of the provided `destination` byte 118 | /// slice. 119 | #[allow(clippy::type_complexity)] 120 | #[inline] 121 | pub fn split_uninit_from_bytes( 122 | destination: &mut [u8], 123 | ) -> Result<(&mut [u8], &mut MaybeUninit, &mut [u8]), SplitUninitError> { 124 | debug_assert!(!destination.as_ptr().is_null()); 125 | // Here we rely on the assurance that MaybeUninit has the same layout 126 | // as its parameterized type, and our knowledge of the implementation 127 | // of `split_uninit_from_uninit_bytes`, namely that it never accesses 128 | // or mutates any content passed to it. 129 | let uninit_bytes = unsafe { &mut *(destination as *mut [u8] as *mut [MaybeUninit]) }; 130 | let (prefix, uninit_ref, suffix): (_, &mut MaybeUninit, _) = 131 | split_uninit_from_uninit_bytes(uninit_bytes)?; 132 | let uninit_prefix = unsafe { &mut *(prefix as *mut [MaybeUninit] as *mut [u8]) }; 133 | let uninit_ref = unsafe { transmute(uninit_ref) }; 134 | let uninit_suffix = unsafe { &mut *(suffix as *mut [MaybeUninit] as *mut [u8]) }; 135 | Ok((uninit_prefix, uninit_ref, uninit_suffix)) 136 | } 137 | 138 | /// Split out a mutable reference to an uninitialized struct at an available 139 | /// location within a provided slice of maybe-uninitialized bytes. 140 | /// 141 | /// Does not access or mutate the content of the provided `destination` byte 142 | /// slice. 143 | #[allow(clippy::type_complexity)] 144 | #[inline] 145 | pub fn split_uninit_from_uninit_bytes( 146 | destination: &mut [MaybeUninit], 147 | ) -> Result< 148 | ( 149 | &mut [MaybeUninit], 150 | &mut MaybeUninit, 151 | &mut [MaybeUninit], 152 | ), 153 | SplitUninitError, 154 | > { 155 | debug_assert!(!destination.as_ptr().is_null()); 156 | if size_of::() == 0 { 157 | return Err(SplitUninitError::ZeroSizedTypesUnsupported); 158 | } 159 | let ptr = destination.as_mut_ptr(); 160 | let offset = ptr.align_offset(align_of::()); 161 | if offset == core::usize::MAX { 162 | return Err(SplitUninitError::Unalignable); 163 | } 164 | if offset > destination.len() { 165 | return Err(SplitUninitError::InsufficientSpace); 166 | } 167 | if let Some(end) = offset.checked_add(size_of::()) { 168 | if end > destination.len() { 169 | return Err(SplitUninitError::InsufficientSpace); 170 | } 171 | } else { 172 | return Err(SplitUninitError::InsufficientSpace); 173 | } 174 | let (prefix, rest) = destination.split_at_mut(offset); 175 | let (middle, suffix) = rest.split_at_mut(size_of::()); 176 | let maybe_uninit = middle.as_mut_ptr() as *mut MaybeUninit; 177 | let maybe_uninit = if let Some(maybe_uninit) = unsafe { maybe_uninit.as_mut() } { 178 | maybe_uninit 179 | } else { 180 | unreachable!("Should be non-null since we rely on the input byte slice being non-null.") 181 | }; 182 | Ok((prefix, maybe_uninit, suffix)) 183 | } 184 | 185 | #[cfg(test)] 186 | #[allow(dead_code)] 187 | mod tests { 188 | use super::*; 189 | #[derive(PartialEq)] 190 | struct ZST; 191 | 192 | #[derive(Default)] 193 | struct TooBig { 194 | colossal: [Colossal; 32], 195 | } 196 | #[derive(Default)] 197 | struct Colossal { 198 | huge: [Huge; 32], 199 | } 200 | #[derive(Default)] 201 | struct Huge { 202 | large: [Large; 32], 203 | } 204 | #[derive(Default)] 205 | struct Large { 206 | medium: [u64; 32], 207 | } 208 | 209 | #[test] 210 | fn zero_sized_types_not_permitted() { 211 | let mut bytes = [0u8; 64]; 212 | if let Err(e) = split_uninit_from_bytes::(&mut bytes[..]) { 213 | assert_eq!(SplitUninitError::ZeroSizedTypesUnsupported, e); 214 | } else { 215 | unreachable!("Expected an err"); 216 | } 217 | if let Err(e) = unsafe { embed(&mut bytes[..], |_| -> Result { Ok(ZST) }) } { 218 | assert_eq!( 219 | EmbedValueError::SplitUninitError(SplitUninitError::ZeroSizedTypesUnsupported), 220 | e 221 | ); 222 | } else { 223 | unreachable!("Expected an err"); 224 | } 225 | 226 | let mut uninit_bytes: [MaybeUninit; 64] = 227 | unsafe { MaybeUninit::uninit().assume_init() }; 228 | if let Err(e) = split_uninit_from_uninit_bytes::(&mut uninit_bytes[..]) { 229 | assert_eq!(SplitUninitError::ZeroSizedTypesUnsupported, e); 230 | } else { 231 | unreachable!("Expected an err"); 232 | } 233 | if let Err(e) = embed_uninit(&mut uninit_bytes[..], |_| -> Result { Ok(ZST) }) { 234 | assert_eq!( 235 | EmbedValueError::SplitUninitError(SplitUninitError::ZeroSizedTypesUnsupported), 236 | e 237 | ); 238 | } else { 239 | unreachable!("Expected an err"); 240 | } 241 | } 242 | 243 | #[test] 244 | fn split_not_enough_space_detected() { 245 | let mut bytes = [0u8; 64]; 246 | if let Err(e) = split_uninit_from_bytes::(&mut bytes[..]) { 247 | match e { 248 | SplitUninitError::InsufficientSpace | SplitUninitError::Unalignable => (), 249 | _ => unreachable!("Unexpected error kind"), 250 | } 251 | } else { 252 | unreachable!("Expected an err"); 253 | } 254 | } 255 | 256 | #[test] 257 | fn split_uninit_not_enough_space_detected() { 258 | let mut uninit_bytes: [MaybeUninit; 64] = 259 | unsafe { MaybeUninit::uninit().assume_init() }; 260 | if let Err(e) = split_uninit_from_uninit_bytes::(&mut uninit_bytes[..]) { 261 | match e { 262 | SplitUninitError::InsufficientSpace | SplitUninitError::Unalignable => (), 263 | _ => unreachable!("Unexpected error kind"), 264 | } 265 | } else { 266 | unreachable!("Expected an err"); 267 | } 268 | } 269 | 270 | #[test] 271 | fn split_uninit_from_bytes_observe_leftovers() { 272 | let mut bytes = [0u8; 61]; 273 | match split_uninit_from_bytes::<[u16; 3]>(&mut bytes[..]) { 274 | Ok((prefix, mid, suffix)) => { 275 | *mid = MaybeUninit::new([3, 4, 5]); 276 | for v in prefix { 277 | assert_eq!(0, *v); 278 | } 279 | for v in suffix { 280 | assert_eq!(0, *v); 281 | } 282 | } 283 | Err(SplitUninitError::Unalignable) => return (), // Most likely MIRI messing with align-ability 284 | Err(e) => unreachable!("Unexpected error: {:?}", e), 285 | } 286 | } 287 | 288 | #[test] 289 | fn split_uninit_from_uninit_bytes_observe_leftovers() { 290 | let mut bytes: [MaybeUninit; 64] = unsafe { MaybeUninit::uninit().assume_init() }; 291 | match split_uninit_from_uninit_bytes::<[u16; 3]>(&mut bytes[..]) { 292 | Ok((prefix, mid, suffix)) => { 293 | *mid = MaybeUninit::new([3, 4, 5]); 294 | let had_prefix = prefix.len() > 0; 295 | let had_suffix = suffix.len() > 0; 296 | assert!(had_prefix | had_suffix); 297 | } 298 | Err(SplitUninitError::Unalignable) => return (), // Most likely MIRI messing with align-ability 299 | Err(e) => unreachable!("Unexpected error: {:?}", e), 300 | } 301 | } 302 | 303 | #[test] 304 | fn split_uninit_from_bytes_empty() { 305 | let bytes: &mut [u8] = &mut []; 306 | assert_eq!( 307 | SplitUninitError::InsufficientSpace, 308 | split_uninit_from_bytes::<[u16; 3]>(bytes).unwrap_err() 309 | ); 310 | } 311 | 312 | #[test] 313 | fn split_uninit_from_uninit_bytes_empty() { 314 | let bytes: &mut [MaybeUninit] = &mut []; 315 | assert_eq!( 316 | SplitUninitError::InsufficientSpace, 317 | split_uninit_from_uninit_bytes::<[u16; 3]>(bytes).unwrap_err() 318 | ); 319 | } 320 | 321 | #[test] 322 | fn embed_not_enough_space_detected() { 323 | let mut bytes = [0u8; 64]; 324 | if let Err(e) = unsafe { 325 | embed(&mut bytes[..], |_| -> Result { 326 | unreachable!("Don't expect this to execute since we can tell from the types that there is not enough space") 327 | }) 328 | } { 329 | match e { 330 | EmbedValueError::SplitUninitError(SplitUninitError::InsufficientSpace) 331 | | EmbedValueError::SplitUninitError(SplitUninitError::Unalignable) => (), 332 | _ => unreachable!("Unexpected error kind"), 333 | } 334 | } else { 335 | unreachable!("Expected an err"); 336 | } 337 | } 338 | 339 | #[test] 340 | fn embed_uninit_not_enough_space_detected() { 341 | let mut uninit_bytes: [MaybeUninit; 64] = 342 | unsafe { MaybeUninit::uninit().assume_init() }; 343 | if let Err(e) = embed_uninit(&mut uninit_bytes[..], |_| -> Result { 344 | unreachable!("Don't expect this to execute since we can tell from the types that there is not enough space") 345 | }) { 346 | match e { 347 | EmbedValueError::SplitUninitError(SplitUninitError::InsufficientSpace) 348 | | EmbedValueError::SplitUninitError(SplitUninitError::Unalignable) => (), 349 | _ => unreachable!("Unexpected error kind"), 350 | } 351 | } else { 352 | unreachable!("Expected an err"); 353 | } 354 | } 355 | 356 | #[test] 357 | fn happy_path_split() { 358 | let mut bytes = [0u8; 512]; 359 | let (prefix, _large_ref, suffix) = match split_uninit_from_bytes::(&mut bytes[..]) { 360 | Ok(r) => r, 361 | Err(SplitUninitError::Unalignable) => return (), // Most likely MIRI messing with align-ability 362 | Err(e) => unreachable!("Unexpected error: {:?}", e), 363 | }; 364 | assert_eq!( 365 | prefix.len() + core::mem::size_of::() + suffix.len(), 366 | bytes.len() 367 | ); 368 | } 369 | 370 | #[test] 371 | fn happy_path_split_uninit() { 372 | let mut uninit_bytes: [MaybeUninit; 512] = 373 | unsafe { MaybeUninit::uninit().assume_init() }; 374 | let (prefix, _large_ref, suffix) = 375 | match split_uninit_from_uninit_bytes::(&mut uninit_bytes[..]) { 376 | Ok(r) => r, 377 | Err(SplitUninitError::Unalignable) => return (), // Most likely MIRI messing with align-ability 378 | Err(e) => unreachable!("Unexpected error: {:?}", e), 379 | }; 380 | assert_eq!( 381 | prefix.len() + core::mem::size_of::() + suffix.len(), 382 | uninit_bytes.len() 383 | ); 384 | } 385 | 386 | #[test] 387 | fn happy_path_embed() { 388 | const BACKING_BYTES_MAX_SIZE: usize = 512; 389 | let mut bytes = [2u8; BACKING_BYTES_MAX_SIZE]; 390 | let large_ref = match unsafe { 391 | embed(&mut bytes[..], |b| -> Result { 392 | assert!(b.iter().all(|b| *b == 2)); 393 | let mut l = Large::default(); 394 | l.medium[0] = 3; 395 | l.medium[1] = 1; 396 | l.medium[2] = 4; 397 | Ok(l) 398 | }) 399 | } { 400 | Ok(r) => r, 401 | Err(EmbedValueError::SplitUninitError(SplitUninitError::Unalignable)) => return (), // Most likely MIRI messing with align-ability 402 | Err(e) => unreachable!("Unexpected error: {:?}", e), 403 | }; 404 | 405 | assert_eq!(3, large_ref.medium[0]); 406 | assert_eq!(1, large_ref.medium[1]); 407 | assert_eq!(4, large_ref.medium[2]); 408 | } 409 | #[test] 410 | fn happy_path_embed_uninit() { 411 | const BACKING_BYTES_MAX_SIZE: usize = 512; 412 | let mut uninit_bytes: [MaybeUninit; BACKING_BYTES_MAX_SIZE] = 413 | unsafe { MaybeUninit::uninit().assume_init() }; 414 | let large_ref = match embed_uninit(&mut uninit_bytes[..], |_| -> Result { 415 | let mut l = Large::default(); 416 | l.medium[0] = 3; 417 | l.medium[1] = 1; 418 | l.medium[2] = 4; 419 | Ok(l) 420 | }) { 421 | Ok(r) => r, 422 | Err(EmbedValueError::SplitUninitError(SplitUninitError::Unalignable)) => return (), // Most likely MIRI messing with align-ability 423 | Err(e) => unreachable!("Unexpected error: {:?}", e), 424 | }; 425 | assert_eq!(3, large_ref.medium[0]); 426 | assert_eq!(1, large_ref.medium[1]); 427 | assert_eq!(4, large_ref.medium[2]); 428 | } 429 | #[test] 430 | fn embed_does_not_run_drops() { 431 | let mut storage: [u8; 16] = [0u8; 16]; 432 | #[derive(Debug)] 433 | struct Target(bool); 434 | impl Drop for Target { 435 | fn drop(&mut self) { 436 | self.0 = true; 437 | } 438 | } 439 | let emb = unsafe { 440 | embed(&mut storage[..], move |_leftovers| { 441 | Result::::Ok(Target(false)) 442 | }) 443 | .unwrap() 444 | }; 445 | 446 | assert!(!emb.0); 447 | } 448 | #[test] 449 | fn embed_uninit_does_not_run_drops() { 450 | let mut storage: [MaybeUninit; 16] = unsafe { MaybeUninit::uninit().assume_init() }; 451 | #[derive(Debug)] 452 | struct Target(bool); 453 | impl Drop for Target { 454 | fn drop(&mut self) { 455 | self.0 = true; 456 | } 457 | } 458 | let emb = embed_uninit(&mut storage[..], move |_leftovers| { 459 | Result::::Ok(Target(false)) 460 | }) 461 | .unwrap(); 462 | 463 | assert!(!emb.0); 464 | } 465 | } 466 | -------------------------------------------------------------------------------- /src/vec.rs: -------------------------------------------------------------------------------- 1 | //! FixedSliceVec is a structure for defining variably populated vectors backed 2 | //! by a slice of storage capacity. 3 | use core::borrow::{Borrow, BorrowMut}; 4 | use core::convert::From; 5 | use core::hash::{Hash, Hasher}; 6 | use core::iter::Extend; 7 | use core::mem::MaybeUninit; 8 | use core::ops::{Deref, DerefMut}; 9 | 10 | /// Vec-like structure backed by a storage slice of possibly uninitialized data. 11 | /// 12 | /// The maximum length (the capacity) is fixed at runtime to the length of the 13 | /// provided storage slice. 14 | pub struct FixedSliceVec<'a, T: Sized> { 15 | /// Backing storage, provides capacity 16 | storage: &'a mut [MaybeUninit], 17 | /// The number of items that have been 18 | /// initialized 19 | len: usize, 20 | } 21 | 22 | impl<'a, T: Sized> Drop for FixedSliceVec<'a, T> { 23 | fn drop(&mut self) { 24 | self.clear(); 25 | } 26 | } 27 | 28 | impl<'a, T: Sized> FixedSliceVec<'a, T> { 29 | /// Create a FixedSliceVec backed by a slice of possibly-uninitialized data. 30 | /// The backing storage slice is used as capacity for Vec-like operations, 31 | /// 32 | /// The initial length of the FixedSliceVec is 0. 33 | #[inline] 34 | pub fn new(storage: &'a mut [MaybeUninit]) -> Self { 35 | FixedSliceVec { storage, len: 0 } 36 | } 37 | 38 | /// Create a well-aligned FixedSliceVec backed by a slice of the provided bytes. 39 | /// The slice is as large as possible given the item type and alignment of 40 | /// the provided bytes. 41 | /// 42 | /// If you are interested in recapturing the prefix and suffix bytes on 43 | /// either side of the carved-out FixedSliceVec buffer, consider using `align_from_bytes` instead: 44 | /// 45 | /// ``` 46 | /// # let mut bytes = [3u8, 1, 4, 1, 5, 9]; 47 | /// let vec = unsafe { fixed_slice_vec::FixedSliceVec::from_bytes(&mut bytes[..]) }; 48 | /// # let vec: fixed_slice_vec::FixedSliceVec = vec; 49 | /// ``` 50 | /// 51 | /// The bytes are treated as if they might be uninitialized, so even if `T` is `u8`, 52 | /// the length of the returned `FixedSliceVec` will be zero. 53 | /// 54 | /// # Safety 55 | /// 56 | /// If the item type `T` of the FixedSliceVec contains any padding bytes 57 | /// then those padding bytes may be observable in the provided slice 58 | /// after the `FixedSliceVec` is dropped. Observing padding bytes is 59 | /// undefined behavior. 60 | #[inline] 61 | pub unsafe fn from_bytes(bytes: &'a mut [u8]) -> FixedSliceVec<'a, T> { 62 | FixedSliceVec::align_from_bytes(bytes).1 63 | } 64 | 65 | /// Create a well-aligned FixedSliceVec backed by a slice of the provided 66 | /// uninitialized bytes. The typed slice is as large as possible given its 67 | /// item type and the alignment of the provided bytes. 68 | /// 69 | /// If you are interested in recapturing the prefix and suffix bytes on 70 | /// either side of the carved-out FixedSliceVec buffer, consider using `align_from_uninit_bytes`: 71 | /// 72 | #[inline] 73 | pub fn from_uninit_bytes(bytes: &'a mut [MaybeUninit]) -> FixedSliceVec<'a, T> { 74 | FixedSliceVec::align_from_uninit_bytes(bytes).1 75 | } 76 | 77 | /// Create a well-aligned FixedSliceVec backed by a slice of the provided bytes. 78 | /// The slice is as large as possible given the item type and alignment of 79 | /// the provided bytes. Returns the unused prefix and suffix bytes on 80 | /// either side of the carved-out FixedSliceVec. 81 | /// 82 | /// ``` 83 | /// let mut bytes = [3u8, 1, 4, 1, 5, 9]; 84 | /// let (prefix, vec, suffix) = unsafe { fixed_slice_vec::FixedSliceVec::align_from_bytes(&mut bytes[..]) }; 85 | /// let vec: fixed_slice_vec::FixedSliceVec = vec; 86 | /// ``` 87 | /// 88 | /// The bytes are treated as if they might be uninitialized, so even if `T` is `u8`, 89 | /// the length of the returned `FixedSliceVec` will be zero. 90 | /// 91 | /// # Safety 92 | /// 93 | /// If the item type `T` of the FixedSliceVec contains any padding bytes 94 | /// then those padding bytes may be observable in the provided slice 95 | /// after the `FixedSliceVec` is dropped. Observing padding bytes is 96 | /// undefined behavior. 97 | #[inline] 98 | pub unsafe fn align_from_bytes( 99 | bytes: &'a mut [u8], 100 | ) -> (&'a mut [u8], FixedSliceVec<'a, T>, &'a mut [u8]) { 101 | let (prefix, storage, suffix) = bytes.align_to_mut(); 102 | (prefix, FixedSliceVec { storage, len: 0 }, suffix) 103 | } 104 | 105 | /// Create a well-aligned FixedSliceVec backed by a slice of the provided bytes. 106 | /// The slice is as large as possible given the item type and alignment of 107 | /// the provided bytes. Returns the unused prefix and suffix bytes on 108 | /// either side of the carved-out FixedSliceVec. 109 | /// 110 | /// ``` 111 | /// # use core::mem::MaybeUninit; 112 | /// let mut bytes: [MaybeUninit; 15] = unsafe { MaybeUninit::uninit().assume_init() }; 113 | /// let (prefix, vec, suffix) = fixed_slice_vec::FixedSliceVec::align_from_uninit_bytes(&mut 114 | /// bytes[..]); 115 | /// let vec: fixed_slice_vec::FixedSliceVec = vec; 116 | /// ``` 117 | /// 118 | /// The length of the returned `FixedSliceVec` will be zero. 119 | #[inline] 120 | pub fn align_from_uninit_bytes( 121 | bytes: &'a mut [MaybeUninit], 122 | ) -> ( 123 | &'a mut [MaybeUninit], 124 | FixedSliceVec<'a, T>, 125 | &'a mut [MaybeUninit], 126 | ) { 127 | let (prefix, storage, suffix) = unsafe { bytes.align_to_mut() }; 128 | (prefix, FixedSliceVec { storage, len: 0 }, suffix) 129 | } 130 | 131 | /// Returns an unsafe mutable pointer to the FixedSliceVec's buffer. 132 | /// 133 | /// The caller must ensure that the FixedSliceVec and the backing 134 | /// storage data for the FixedSliceVec (provided at construction) 135 | /// outlives the pointer this function returns. 136 | /// 137 | /// Furthermore, the contents of the buffer are not guaranteed 138 | /// to have been initialized at indices >= len. 139 | /// 140 | /// # Examples 141 | /// 142 | /// ``` 143 | /// use core::mem::MaybeUninit; 144 | /// let mut storage: [MaybeUninit; 16] = unsafe { MaybeUninit::uninit().assume_init() }; 145 | /// let mut x: fixed_slice_vec::FixedSliceVec = fixed_slice_vec::FixedSliceVec::from_uninit_bytes(&mut storage[..]); 146 | /// assert!(x.try_extend([1u16, 2, 4, 8].iter().copied()).is_ok()); 147 | /// let size = x.len(); 148 | /// let x_ptr = x.as_mut_ptr(); 149 | /// 150 | /// // Set elements via raw pointer writes. 151 | /// unsafe { 152 | /// for i in 0..size { 153 | /// *x_ptr.add(i) = MaybeUninit::new(i as u16); 154 | /// } 155 | /// } 156 | /// assert_eq!(&*x, &[0,1,2,3]); 157 | /// ``` 158 | #[inline] 159 | pub fn as_mut_ptr(&mut self) -> *mut MaybeUninit { 160 | self.storage.as_mut_ptr() 161 | } 162 | /// Returns a raw pointer to the FixedSliceVec's buffer. 163 | /// 164 | /// The caller must ensure that the FixedSliceVec and the backing 165 | /// storage data for the FixedSliceVec (provided at construction) 166 | /// outlives the pointer this function returns. 167 | /// 168 | /// Furthermore, the contents of the buffer are not guaranteed 169 | /// to have been initialized at indices >= len. 170 | /// 171 | /// The caller must also ensure that the memory the pointer (non-transitively) points to 172 | /// is never written to using this pointer or any pointer derived from it. 173 | /// 174 | /// If you need to mutate the contents of the slice with pointers, use [`as_mut_ptr`]. 175 | /// 176 | /// # Examples 177 | /// 178 | /// ``` 179 | /// use core::mem::MaybeUninit; 180 | /// let mut storage: [MaybeUninit; 16] = unsafe { MaybeUninit::uninit().assume_init() }; 181 | /// let mut x: fixed_slice_vec::FixedSliceVec = fixed_slice_vec::FixedSliceVec::from_uninit_bytes(&mut storage[..]); 182 | /// x.extend([1u16, 2, 4].iter().copied()); 183 | /// let x_ptr = x.as_ptr(); 184 | /// 185 | /// unsafe { 186 | /// for i in 0..x.len() { 187 | /// assert_eq!((*x_ptr.add(i)).assume_init(), 1 << i); 188 | /// } 189 | /// } 190 | /// ``` 191 | /// 192 | /// [`as_mut_ptr`]: #method.as_mut_ptr 193 | #[inline] 194 | pub fn as_ptr(&self) -> *const MaybeUninit { 195 | self.storage.as_ptr() 196 | } 197 | 198 | /// The length of the FixedSliceVec. The number of initialized 199 | /// values that have been added to it. 200 | #[inline] 201 | pub fn len(&self) -> usize { 202 | self.len 203 | } 204 | 205 | /// The maximum amount of items that can live in this FixedSliceVec 206 | #[inline] 207 | pub fn capacity(&self) -> usize { 208 | self.storage.len() 209 | } 210 | 211 | /// Returns true if there are no items present. 212 | #[inline] 213 | pub fn is_empty(&self) -> bool { 214 | self.len == 0 215 | } 216 | 217 | /// Returns true if the FixedSliceVec is full to capacity. 218 | #[inline] 219 | pub fn is_full(&self) -> bool { 220 | self.len == self.capacity() 221 | } 222 | 223 | /// Attempt to add a value to the FixedSliceVec. 224 | /// 225 | /// Returns an error if there is not enough capacity to hold another item. 226 | #[inline] 227 | pub fn try_push(&mut self, value: T) -> Result<(), StorageError> { 228 | if self.is_full() { 229 | return Err(StorageError(value)); 230 | } 231 | self.storage[self.len] = MaybeUninit::new(value); 232 | self.len += 1; 233 | Ok(()) 234 | } 235 | 236 | /// Attempt to add a value to the FixedSliceVec. 237 | /// 238 | /// # Panics 239 | /// 240 | /// Panics if there is not sufficient capacity to hold another item. 241 | #[inline] 242 | pub fn push(&mut self, value: T) { 243 | self.try_push(value).unwrap(); 244 | } 245 | 246 | /// Attempt to insert a value to the FixedSliceVec. 247 | /// 248 | /// Returns an error if there is not enough capacity to hold another item. 249 | #[inline] 250 | pub fn try_insert(&mut self, index: usize, value: T) -> Result<(), StorageError> { 251 | if index > self.len() { 252 | return Err(StorageError(value)); 253 | } 254 | self.try_push(value)?; 255 | self.as_mut_slice()[index..].rotate_right(1); 256 | Ok(()) 257 | } 258 | 259 | /// Inserts a value to the FixedSliceVec. 260 | /// 261 | /// # Panics 262 | /// 263 | /// Panics if there is not sufficient capacity to hold another item. 264 | #[inline] 265 | pub fn insert(&mut self, index: usize, value: T) { 266 | self.try_insert(index, value).unwrap() 267 | } 268 | 269 | /// Attempt to add as many values as will fit from an iterable. 270 | /// 271 | /// Returns Ok(()) if all of the items in the iterator can fit into `self`. 272 | /// Returns an Err containing the iterator if `self` fills up and there 273 | /// are items remaining in the iterator. 274 | #[inline] 275 | pub fn try_extend( 276 | &mut self, 277 | iterable: impl IntoIterator, 278 | ) -> Result<(), impl Iterator> { 279 | let mut iter = iterable.into_iter().peekable(); 280 | loop { 281 | if iter.peek().is_some() { 282 | if self.is_full() { 283 | return Err(iter); 284 | } else if let Some(item) = iter.next() { 285 | self.storage[self.len] = MaybeUninit::new(item); 286 | self.len += 1; 287 | } else { 288 | unreachable!("`FixedSliceVec::try_extend` peeked above to ensure that `next` would return Some") 289 | } 290 | } else { 291 | return Ok(()); 292 | } 293 | } 294 | } 295 | 296 | /// Remove the last item from the FixedSliceVec. 297 | #[inline] 298 | pub fn pop(&mut self) -> Option { 299 | if self.len == 0 { 300 | return None; 301 | } 302 | self.len -= 1; 303 | Some(unsafe { self.storage[self.len].as_ptr().read() }) 304 | } 305 | 306 | /// Removes the FixedSliceVec's tracking of all items in it while retaining the 307 | /// same capacity. 308 | #[inline] 309 | pub fn clear(&mut self) { 310 | let original_len = self.len; 311 | self.len = 0; 312 | unsafe { 313 | // Note we cannot use the usual DerefMut helper to produce a slice because it relies 314 | // on the `len` field, which we have updated above already. 315 | // The early setting of `len` is designed to avoid double-free errors if there 316 | // is a panic in the middle of the following drop. 317 | (core::slice::from_raw_parts_mut(self.storage.as_mut_ptr() as *mut T, original_len) 318 | as *mut [T]) 319 | .drop_in_place(); 320 | } 321 | } 322 | 323 | /// Shortens the FixedSliceVec, keeping the first `len` elements and dropping the rest. 324 | /// 325 | /// If len is greater than the current length, this has no effect. 326 | /// Note that this method has no effect on the capacity of the FixedSliceVec. 327 | #[inline] 328 | pub fn truncate(&mut self, len: usize) { 329 | let original_len = self.len; 330 | if len > original_len { 331 | return; 332 | } 333 | self.len = len; 334 | unsafe { 335 | // Note we cannot use the usual DerefMut helper to produce a slice because it relies 336 | // on the `len` field, which we have updated above already. 337 | // The early setting of `len` is designed to avoid double-free errors if there 338 | // is a panic in the middle of the following drop. 339 | (&mut core::slice::from_raw_parts_mut(self.storage.as_mut_ptr() as *mut T, original_len)[len..] as *mut [T]).drop_in_place(); 340 | } 341 | } 342 | /// Removes and returns the element at position `index` within the FixedSliceVec, 343 | /// shifting all elements after it to the left. 344 | /// 345 | /// # Panics 346 | /// 347 | /// Panics if `index` is out of bounds. 348 | pub fn remove(&mut self, index: usize) -> T { 349 | // Error message and overall impl strategy following along with std vec, 350 | if index >= self.len { 351 | panic!( 352 | "removal index (is {}) should be < len (is {})", 353 | index, self.len 354 | ); 355 | } 356 | unsafe { self.unchecked_remove(index) } 357 | } 358 | 359 | /// Removes and returns the element at position `index` within the FixedSliceVec, 360 | /// shifting all elements after it to the left. 361 | pub fn try_remove(&mut self, index: usize) -> Result { 362 | if index >= self.len { 363 | return Err(IndexError); 364 | } 365 | Ok(unsafe { self.unchecked_remove(index) }) 366 | } 367 | 368 | /// Remove and return an element without checking if it's actually there. 369 | #[inline] 370 | unsafe fn unchecked_remove(&mut self, index: usize) -> T { 371 | let ptr = (self.as_mut_ptr() as *mut T).add(index); 372 | let out = core::ptr::read(ptr); 373 | core::ptr::copy(ptr.offset(1), ptr, self.len - index - 1); 374 | self.len -= 1; 375 | out 376 | } 377 | /// Removes an element from the vector and returns it. 378 | /// 379 | /// The removed element is replaced by the last element of the vector. 380 | /// 381 | /// This does not preserve ordering, but is O(1). 382 | /// 383 | /// # Panics 384 | /// 385 | /// Panics if `index` is out of bounds. 386 | pub fn swap_remove(&mut self, index: usize) -> T { 387 | if index >= self.len { 388 | panic!( 389 | "swap_remove index (is {}) should be < len (is {})", 390 | index, self.len 391 | ); 392 | } 393 | unsafe { self.unchecked_swap_remove(index) } 394 | } 395 | /// Removes an element from the vector and returns it. 396 | /// 397 | /// The removed element is replaced by the last element of the vector. 398 | /// 399 | /// This does not preserve ordering, but is O(1). 400 | pub fn try_swap_remove(&mut self, index: usize) -> Result { 401 | if index >= self.len { 402 | return Err(IndexError); 403 | } 404 | Ok(unsafe { self.unchecked_swap_remove(index) }) 405 | } 406 | 407 | /// swap_remove, without the length-checking 408 | #[inline] 409 | unsafe fn unchecked_swap_remove(&mut self, index: usize) -> T { 410 | let target_ptr = (self.as_mut_ptr() as *mut T).add(index); 411 | let end_ptr = (self.as_ptr() as *const T).add(self.len - 1); 412 | let end_value = core::ptr::read(end_ptr); 413 | self.len -= 1; 414 | core::ptr::replace(target_ptr, end_value) 415 | } 416 | 417 | /// Obtain an immutable slice view on the initialized portion of the 418 | /// FixedSliceVec. 419 | #[inline] 420 | pub fn as_slice(&self) -> &[T] { 421 | Deref::deref(self) 422 | } 423 | 424 | /// Obtain a mutable slice view on the initialized portion of the 425 | /// FixedSliceVec. 426 | #[inline] 427 | pub fn as_mut_slice(&mut self) -> &mut [T] { 428 | DerefMut::deref_mut(self) 429 | } 430 | } 431 | 432 | /// Error that occurs when a call that attempts to increase 433 | /// the number of items in the FixedSliceVec fails 434 | /// due to insufficient storage capacity. 435 | #[derive(Clone, PartialEq, PartialOrd, Eq, Ord)] 436 | pub struct StorageError(pub T); 437 | 438 | impl core::fmt::Debug for StorageError { 439 | fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> Result<(), core::fmt::Error> { 440 | f.write_str("Push failed because FixedSliceVec was full") 441 | } 442 | } 443 | 444 | /// Error that occurs when a call that attempts to access 445 | /// the FixedSliceVec in a manner that does not respect 446 | /// the current length of the vector, i.e. its current 447 | /// number of initialized items. 448 | #[derive(Clone, PartialEq, PartialOrd, Eq, Ord)] 449 | pub struct IndexError; 450 | 451 | impl core::fmt::Debug for IndexError { 452 | fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> Result<(), core::fmt::Error> { 453 | f.write_str( 454 | "Access to the FixedSliceVec failed because an invalid index or length was provided", 455 | ) 456 | } 457 | } 458 | 459 | impl<'a, T: Sized> From<&'a mut [MaybeUninit]> for FixedSliceVec<'a, T> { 460 | #[inline] 461 | fn from(v: &'a mut [MaybeUninit]) -> Self { 462 | FixedSliceVec { storage: v, len: 0 } 463 | } 464 | } 465 | 466 | impl<'a, T: Sized> Hash for FixedSliceVec<'a, T> 467 | where 468 | T: Hash, 469 | { 470 | #[inline] 471 | fn hash(&self, state: &mut H) { 472 | Hash::hash(&**self, state) 473 | } 474 | } 475 | 476 | impl<'a, T: Sized> PartialEq for FixedSliceVec<'a, T> 477 | where 478 | T: PartialEq, 479 | { 480 | #[inline] 481 | fn eq(&self, other: &Self) -> bool { 482 | **self == **other 483 | } 484 | } 485 | 486 | impl<'a, T: Sized> PartialEq<[T]> for FixedSliceVec<'a, T> 487 | where 488 | T: PartialEq, 489 | { 490 | #[inline] 491 | fn eq(&self, other: &[T]) -> bool { 492 | **self == *other 493 | } 494 | } 495 | 496 | impl<'a, T: Sized> Eq for FixedSliceVec<'a, T> where T: Eq {} 497 | 498 | impl<'a, T: Sized> Borrow<[T]> for FixedSliceVec<'a, T> { 499 | #[inline] 500 | fn borrow(&self) -> &[T] { 501 | self 502 | } 503 | } 504 | 505 | impl<'a, T: Sized> BorrowMut<[T]> for FixedSliceVec<'a, T> { 506 | #[inline] 507 | fn borrow_mut(&mut self) -> &mut [T] { 508 | self 509 | } 510 | } 511 | 512 | impl<'a, T: Sized> AsRef<[T]> for FixedSliceVec<'a, T> { 513 | #[inline] 514 | fn as_ref(&self) -> &[T] { 515 | self 516 | } 517 | } 518 | 519 | impl<'a, T: Sized> AsMut<[T]> for FixedSliceVec<'a, T> { 520 | #[inline] 521 | fn as_mut(&mut self) -> &mut [T] { 522 | self 523 | } 524 | } 525 | 526 | impl<'a, T: Sized> core::fmt::Debug for FixedSliceVec<'a, T> 527 | where 528 | T: core::fmt::Debug, 529 | { 530 | #[inline] 531 | fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result { 532 | (**self).fmt(f) 533 | } 534 | } 535 | 536 | impl<'a, T: Sized> PartialOrd for FixedSliceVec<'a, T> 537 | where 538 | T: PartialOrd, 539 | { 540 | #[inline] 541 | fn partial_cmp(&self, other: &FixedSliceVec<'a, T>) -> Option { 542 | (**self).partial_cmp(other) 543 | } 544 | 545 | #[inline] 546 | fn lt(&self, other: &Self) -> bool { 547 | (**self).lt(other) 548 | } 549 | 550 | #[inline] 551 | fn le(&self, other: &Self) -> bool { 552 | (**self).le(other) 553 | } 554 | 555 | #[inline] 556 | fn gt(&self, other: &Self) -> bool { 557 | (**self).gt(other) 558 | } 559 | 560 | #[inline] 561 | fn ge(&self, other: &Self) -> bool { 562 | (**self).ge(other) 563 | } 564 | } 565 | 566 | impl<'a, T: Sized> Deref for FixedSliceVec<'a, T> { 567 | type Target = [T]; 568 | #[inline] 569 | fn deref(&self) -> &Self::Target { 570 | unsafe { core::slice::from_raw_parts(self.storage.as_ptr() as *const T, self.len) } 571 | } 572 | } 573 | 574 | impl<'a, T: Sized> DerefMut for FixedSliceVec<'a, T> { 575 | #[inline] 576 | fn deref_mut(&mut self) -> &mut [T] { 577 | unsafe { core::slice::from_raw_parts_mut(self.storage.as_mut_ptr() as *mut T, self.len) } 578 | } 579 | } 580 | 581 | /// Adds as many items from the provided iterable as can fit. 582 | /// 583 | /// Gives no indication of how many were extracted or if some 584 | /// could not fit. 585 | /// 586 | /// Use `FixedSliceVec::try_extend` if you require more fine- 587 | /// grained signal about the outcome of attempted extension. 588 | impl<'a, T: Sized> Extend for FixedSliceVec<'a, T> { 589 | fn extend>(&mut self, iter: I) { 590 | let _ = self.try_extend(iter); 591 | } 592 | } 593 | 594 | #[cfg(test)] 595 | mod tests { 596 | use super::*; 597 | 598 | #[test] 599 | fn from_uninit() { 600 | let mut data: [MaybeUninit; 32] = unsafe { MaybeUninit::uninit().assume_init() }; 601 | let mut sv: FixedSliceVec = (&mut data[..]).into(); 602 | assert_eq!(0, sv.len()); 603 | assert_eq!(32, sv.capacity()); 604 | assert!(sv.is_empty()); 605 | let sv_as_slice: &[u8] = &sv; 606 | let empty_slice: &[u8] = &[]; 607 | assert_eq!(empty_slice, sv_as_slice); 608 | assert_eq!(Ok(()), sv.try_push(3)); 609 | assert_eq!(Ok(()), sv.try_push(1)); 610 | assert_eq!(Ok(()), sv.try_push(4)); 611 | let non_empty_slice: &[u8] = &[3u8, 1, 4]; 612 | assert_eq!(non_empty_slice, &sv as &[u8]); 613 | let sv_as_mut_slice: &mut [u8] = &mut sv; 614 | sv_as_mut_slice[1] = 2; 615 | let non_empty_slice: &[u8] = &[3u8, 2, 4]; 616 | assert_eq!(non_empty_slice, &sv as &[u8]); 617 | 618 | sv.clear(); 619 | assert_eq!(0, sv.len()); 620 | assert!(sv.is_empty()); 621 | } 622 | 623 | #[test] 624 | fn happy_path_from_bytes() { 625 | let mut data = [0u8; 31]; 626 | let mut sv: FixedSliceVec = unsafe { FixedSliceVec::from_bytes(&mut data[..]) }; 627 | assert!(sv.is_empty()); 628 | // capacity might be 0 if miri messes with the align-ability of pointers 629 | if sv.capacity() > 0 { 630 | for i in 0..sv.capacity() { 631 | assert_eq!(Ok(()), sv.try_push(i)); 632 | } 633 | } 634 | assert!(sv.is_full()); 635 | } 636 | 637 | #[test] 638 | fn align_captures_suffix_and_prefix() { 639 | let mut data = [ 640 | 3u8, 1, 4, 1, 5, 9, 2, 6, 5, 3, 5, 8, 9, 7, 9, 3, 2, 3, 8, 4, 6, 2, 6, 4, 3, 3, 641 | ]; 642 | let original_len = data.len(); 643 | for i in 0..original_len { 644 | for len in 0..original_len - i { 645 | let storage = &mut data[i..i + len]; 646 | let storage_len = storage.len(); 647 | let (prefix, fixed_slice_vec, suffix): (_, FixedSliceVec, _) = 648 | unsafe { FixedSliceVec::align_from_bytes(storage) }; 649 | assert_eq!( 650 | storage_len, 651 | prefix.len() + 2 * fixed_slice_vec.capacity() + suffix.len() 652 | ); 653 | } 654 | } 655 | } 656 | fn uninit_storage() -> [MaybeUninit; 4] { 657 | unsafe { MaybeUninit::uninit().assume_init() } 658 | } 659 | 660 | #[test] 661 | fn as_ptr_reveals_expected_internal_content() { 662 | let expected = [0u8, 1, 2, 3]; 663 | let mut storage = uninit_storage(); 664 | let mut fsv = FixedSliceVec::from_uninit_bytes(&mut storage[..]); 665 | assert!(fsv.try_extend(expected.iter().copied()).is_ok()); 666 | 667 | let ptr = fsv.as_ptr(); 668 | for i in 0..fsv.len() { 669 | assert_eq!(expected[i], unsafe { (*ptr.add(i)).assume_init() }); 670 | } 671 | 672 | let mut fsv = fsv; 673 | fsv[3] = 99; 674 | assert_eq!(99, unsafe { (*fsv.as_ptr().add(3)).assume_init() }) 675 | } 676 | 677 | #[test] 678 | fn as_mut_ptr_allows_changes_to_internal_content() { 679 | let expected = [0u8, 2, 4, 8]; 680 | let mut storage = uninit_storage(); 681 | let mut fsv: FixedSliceVec = FixedSliceVec::from_uninit_bytes(&mut storage[..]); 682 | assert!(fsv.try_extend(expected.iter().copied()).is_ok()); 683 | 684 | assert_eq!(8, unsafe { fsv.as_mut_ptr().add(3).read().assume_init() }); 685 | unsafe { 686 | fsv.as_mut_ptr().add(3).write(MaybeUninit::new(99)); 687 | } 688 | assert_eq!(99, fsv[3]); 689 | 690 | fsv[1] = 200; 691 | assert_eq!(200, unsafe { fsv.as_mut_ptr().add(1).read().assume_init() }); 692 | } 693 | 694 | #[test] 695 | fn manual_truncate() { 696 | let expected = [0u8, 2, 4, 8]; 697 | let mut storage = uninit_storage(); 698 | let mut fsv = FixedSliceVec::from_uninit_bytes(&mut storage[..]); 699 | assert!(fsv.try_extend(expected.iter().copied()).is_ok()); 700 | 701 | fsv.truncate(100); 702 | assert_eq!(&[0u8, 2, 4, 8], fsv.as_slice()); 703 | fsv.truncate(2); 704 | assert_eq!(&[0u8, 2], fsv.as_slice()); 705 | fsv.truncate(2); 706 | assert_eq!(&[0u8, 2], fsv.as_slice()); 707 | fsv.truncate(0); 708 | assert!(fsv.is_empty()); 709 | } 710 | 711 | #[test] 712 | fn manual_try_remove() { 713 | let expected = [0u8, 2, 4, 8]; 714 | let mut storage = uninit_storage(); 715 | let mut fsv = FixedSliceVec::from_uninit_bytes(&mut storage[..]); 716 | assert!(fsv.try_extend(expected.iter().copied()).is_ok()); 717 | 718 | assert_eq!(Err(IndexError), fsv.try_remove(100)); 719 | assert_eq!(Err(IndexError), fsv.try_remove(4)); 720 | assert_eq!(&[0u8, 2, 4, 8], fsv.as_slice()); 721 | assert_eq!(Ok(2), fsv.try_remove(1)); 722 | assert_eq!(&[0u8, 4, 8], fsv.as_slice()); 723 | } 724 | 725 | #[test] 726 | #[should_panic] 727 | fn manual_swap_remove_outside_range() { 728 | let mut storage = uninit_storage(); 729 | let mut fsv = FixedSliceVec::from_uninit_bytes(&mut storage[..]); 730 | assert!(fsv.try_extend([0u8, 2, 4, 8].iter().copied()).is_ok()); 731 | fsv.swap_remove(100); 732 | } 733 | 734 | #[test] 735 | #[should_panic] 736 | fn manual_swap_remove_empty() { 737 | let mut storage = uninit_storage(); 738 | let mut fsv: FixedSliceVec = FixedSliceVec::from_uninit_bytes(&mut storage[..]); 739 | assert!(fsv.capacity() > 0); 740 | assert_eq!(0, fsv.len()); 741 | fsv.swap_remove(0); 742 | } 743 | 744 | #[test] 745 | fn manual_swap_remove_inside_range() { 746 | let mut storage = uninit_storage(); 747 | let mut fsv = FixedSliceVec::from_uninit_bytes(&mut storage[..]); 748 | assert!(fsv.try_extend([0u8, 2, 4, 8].iter().copied()).is_ok()); 749 | assert_eq!(&[0u8, 2, 4, 8], fsv.as_slice()); 750 | assert_eq!(2, fsv.swap_remove(1)); 751 | assert_eq!(&[0u8, 8, 4], fsv.as_slice()); 752 | assert_eq!(0, fsv.swap_remove(0)); 753 | assert_eq!(&[4u8, 8], fsv.as_slice()); 754 | assert_eq!(4, fsv.swap_remove(0)); 755 | assert_eq!(&[8u8], fsv.as_slice()); 756 | assert_eq!(8, fsv.swap_remove(0)); 757 | assert!(fsv.as_slice().is_empty()); 758 | } 759 | 760 | #[test] 761 | fn manual_try_swap_remove() { 762 | let expected = [0u8, 2, 4, 8]; 763 | let mut storage = uninit_storage(); 764 | let mut fsv = FixedSliceVec::from_uninit_bytes(&mut storage[..]); 765 | assert!(fsv.try_extend(expected.iter().copied()).is_ok()); 766 | 767 | assert_eq!(Err(IndexError), fsv.try_swap_remove(100)); 768 | assert_eq!(Err(IndexError), fsv.try_swap_remove(4)); 769 | assert_eq!(&[0u8, 2, 4, 8], fsv.as_slice()); 770 | assert_eq!(Ok(2), fsv.try_swap_remove(1)); 771 | assert_eq!(&[0u8, 8, 4], fsv.as_slice()); 772 | assert_eq!(Ok(0), fsv.try_swap_remove(0)); 773 | assert_eq!(&[4u8, 8], fsv.as_slice()); 774 | assert_eq!(Ok(4), fsv.try_swap_remove(0)); 775 | assert_eq!(&[8u8], fsv.as_slice()); 776 | assert_eq!(Ok(8), fsv.try_swap_remove(0)); 777 | assert!(fsv.as_slice().is_empty()); 778 | assert_eq!(Err(IndexError), fsv.try_swap_remove(0)); 779 | } 780 | 781 | #[test] 782 | fn try_extend_with_exactly_enough_room() { 783 | let expected = [0u8, 2, 4, 8]; 784 | let mut storage = uninit_storage(); 785 | let mut fsv = FixedSliceVec::from_uninit_bytes(&mut storage[..]); 786 | assert!(fsv.try_extend(expected.iter().copied()).is_ok()); 787 | assert_eq!(&expected[..], &fsv[..]); 788 | } 789 | 790 | #[test] 791 | fn try_extend_with_more_than_enough_room() { 792 | let expected = [0u8, 2, 4, 8]; 793 | let mut storage: [MaybeUninit; 100] = unsafe { MaybeUninit::uninit().assume_init() }; 794 | let mut fsv = FixedSliceVec::from_uninit_bytes(&mut storage[..]); 795 | assert!(fsv.try_extend(expected.iter().copied()).is_ok()); 796 | assert_eq!(&expected[..], &fsv[..]); 797 | } 798 | 799 | #[test] 800 | fn try_extend_with_not_enough_room() { 801 | let expected = [0u8, 2, 4, 8]; 802 | let mut storage: [MaybeUninit; 2] = unsafe { MaybeUninit::uninit().assume_init() }; 803 | let mut fsv = FixedSliceVec::from_uninit_bytes(&mut storage[..]); 804 | let mut out_iter = fsv.try_extend(expected.iter().copied()).unwrap_err(); 805 | assert_eq!(Some(4), out_iter.next()); 806 | assert_eq!(Some(8), out_iter.next()); 807 | assert_eq!(None, out_iter.next()); 808 | assert_eq!(&expected[0..2], &fsv[..]); 809 | } 810 | #[test] 811 | fn extend_with_exactly_enough_room() { 812 | let expected = [0u8, 2, 4, 8]; 813 | let mut storage = uninit_storage(); 814 | let mut fsv = FixedSliceVec::from_uninit_bytes(&mut storage[..]); 815 | fsv.extend(expected.iter().copied()); 816 | assert_eq!(&expected[..], &fsv[..]); 817 | } 818 | 819 | #[test] 820 | fn extend_with_more_than_enough_room() { 821 | let expected = [0u8, 2, 4, 8]; 822 | let mut storage: [MaybeUninit; 100] = unsafe { MaybeUninit::uninit().assume_init() }; 823 | let mut fsv = FixedSliceVec::from_uninit_bytes(&mut storage[..]); 824 | fsv.extend(expected.iter().copied()); 825 | assert_eq!(&expected[..], &fsv[..]); 826 | } 827 | 828 | #[test] 829 | fn extend_with_not_enough_room() { 830 | let expected = [0u8, 2, 4, 8]; 831 | let mut storage: [MaybeUninit; 2] = unsafe { MaybeUninit::uninit().assume_init() }; 832 | let mut fsv = FixedSliceVec::from_uninit_bytes(&mut storage[..]); 833 | fsv.extend(expected.iter().copied()); 834 | assert_eq!(&expected[0..2], &fsv[..]); 835 | } 836 | 837 | #[test] 838 | fn from_uninit_bytes_empty_slice() { 839 | let storage: &mut [MaybeUninit] = &mut []; 840 | let mut fsv: FixedSliceVec = FixedSliceVec::from_uninit_bytes(storage); 841 | assert_eq!(0, fsv.capacity()); 842 | assert_eq!(0, fsv.len()); 843 | assert!(fsv.try_push(31).is_err()); 844 | } 845 | #[test] 846 | fn from_uninit_bytes_smaller_than_item_slice() { 847 | let mut storage: [MaybeUninit; 1] = unsafe { MaybeUninit::uninit().assume_init() }; 848 | let mut fsv: FixedSliceVec = FixedSliceVec::from_uninit_bytes(&mut storage); 849 | assert_eq!(0, fsv.capacity()); 850 | assert_eq!(0, fsv.len()); 851 | assert!(fsv.try_push(31).is_err()); 852 | } 853 | #[test] 854 | fn from_uninit_bytes_larger_than_item_slice() { 855 | let mut storage: [MaybeUninit; 9] = unsafe { MaybeUninit::uninit().assume_init() }; 856 | let mut fsv: FixedSliceVec = FixedSliceVec::from_uninit_bytes(&mut storage); 857 | assert!(fsv.capacity() > 0); 858 | assert_eq!(0, fsv.len()); 859 | assert!(fsv.try_push(31).is_ok()); 860 | assert_eq!(&[31], &fsv[..]); 861 | } 862 | 863 | #[test] 864 | fn equality_sanity_checks() { 865 | let mut storage_a: [MaybeUninit; 2] = unsafe { MaybeUninit::uninit().assume_init() }; 866 | let mut a: FixedSliceVec = FixedSliceVec::from_uninit_bytes(&mut storage_a); 867 | let mut storage_b: [MaybeUninit; 2] = unsafe { MaybeUninit::uninit().assume_init() }; 868 | let mut b: FixedSliceVec = FixedSliceVec::from_uninit_bytes(&mut storage_b); 869 | 870 | assert_eq!(a, b); 871 | assert_eq!(&a, &b[..]); 872 | assert_eq!(&b, &a[..]); 873 | a.push(1u8); 874 | assert_ne!(a, b); 875 | assert_ne!(&a, &b[..]); 876 | assert_ne!(&b, &a[..]); 877 | b.push(1u8); 878 | assert_eq!(a, b); 879 | assert_eq!(&a, &b[..]); 880 | assert_eq!(&b, &a[..]); 881 | } 882 | 883 | #[test] 884 | fn borrow_ish_sanity_checks() { 885 | let mut expected = [0u8, 2, 4, 8]; 886 | let mut storage: [MaybeUninit; 12] = unsafe { MaybeUninit::uninit().assume_init() }; 887 | let mut fsv = FixedSliceVec::from_uninit_bytes(&mut storage[..]); 888 | fsv.extend(expected.iter().copied()); 889 | 890 | assert_eq!(&expected[..], Borrow::<[u8]>::borrow(&fsv)); 891 | assert_eq!(&mut expected[..], BorrowMut::<[u8]>::borrow_mut(&mut fsv)); 892 | assert_eq!(&expected[..], fsv.as_ref()); 893 | assert_eq!(&mut expected[..], fsv.as_mut()); 894 | } 895 | 896 | #[test] 897 | fn comparison_sanity_checks() { 898 | let mut storage_a: [MaybeUninit; 2] = unsafe { MaybeUninit::uninit().assume_init() }; 899 | let mut a: FixedSliceVec = FixedSliceVec::from_uninit_bytes(&mut storage_a); 900 | let mut storage_b: [MaybeUninit; 2] = unsafe { MaybeUninit::uninit().assume_init() }; 901 | let mut b: FixedSliceVec = FixedSliceVec::from_uninit_bytes(&mut storage_b); 902 | use core::cmp::Ordering; 903 | assert_eq!(Some(Ordering::Equal), a.partial_cmp(&b)); 904 | b.push(1); 905 | assert!(a.lt(&b)); 906 | assert!(b.gt(&a)); 907 | a.push(1); 908 | assert!(a.ge(&b)); 909 | assert!(a.le(&b)); 910 | a[0] = 2; 911 | assert!(a.gt(&b)); 912 | assert!(b.lt(&a)); 913 | } 914 | 915 | #[test] 916 | fn insertion_sanity_checks() { 917 | // Opposite order element addition check 918 | let mut storage_a: [MaybeUninit; 4] = unsafe { MaybeUninit::uninit().assume_init() }; 919 | let mut a: FixedSliceVec = FixedSliceVec::from_uninit_bytes(&mut storage_a); 920 | let mut storage_b: [MaybeUninit; 4] = unsafe { MaybeUninit::uninit().assume_init() }; 921 | let mut b: FixedSliceVec = FixedSliceVec::from_uninit_bytes(&mut storage_b); 922 | assert_eq!(a.as_slice(), b.as_slice(), "Equal sets of 0 elements"); 923 | a.insert(0usize, 1); 924 | a.insert(0usize, 2); 925 | a.insert(0usize, 3); 926 | a.insert(0usize, 4); 927 | b.push(4); 928 | b.push(3); 929 | b.push(2); 930 | b.push(1); 931 | assert_eq!( 932 | a.as_slice(), 933 | b.as_slice(), 934 | "Equal sets of 4 elements, added in opposite order" 935 | ); 936 | assert_eq!(a.try_insert(0, 0), b.try_push(0)); 937 | assert_eq!( 938 | a.as_slice(), 939 | &[4u8, 3u8, 2u8, 1u8], 940 | "Insert should not modify until required capacity is verified." 941 | ); 942 | assert_eq!( 943 | b.as_slice(), 944 | &[4u8, 3u8, 2u8, 1u8], 945 | "Push should not modify until required capacity is verified." 946 | ); 947 | 948 | // Mixed insertion order check 949 | a.clear(); 950 | assert!( 951 | a.try_insert(1usize, 55).is_err(), 952 | "Given `n` elements and index `0` to `n-1` the maximum insert index is `n` (push)" 953 | ); 954 | a.insert(0usize, 4); 955 | a.insert(1usize, 2); 956 | a.insert(1usize, 3); 957 | a.insert(3usize, 1); 958 | assert_eq!( 959 | a.as_slice(), 960 | &[4u8, 3u8, 2u8, 1u8], 961 | "Alternate insert order should yeild same result." 962 | ); 963 | 964 | // Zero sized buffer check 965 | let mut storage_c: [MaybeUninit; 0] = unsafe { MaybeUninit::uninit().assume_init() }; 966 | let mut c: FixedSliceVec = FixedSliceVec::from_uninit_bytes(&mut storage_c); 967 | assert!( 968 | c.try_insert(0usize, 1).is_err(), 969 | "Zero sized buffer should fail on insert" 970 | ); 971 | 972 | // Zero remaining capacity check 973 | let mut storage_d: [MaybeUninit; 1] = unsafe { MaybeUninit::uninit().assume_init() }; 974 | let mut d: FixedSliceVec = FixedSliceVec::from_uninit_bytes(&mut storage_d); 975 | d.push(1); 976 | assert!( 977 | d.try_insert(0usize, 2).is_err(), 978 | "Zero remaining capacity should fail an insert" 979 | ); 980 | } 981 | } 982 | -------------------------------------------------------------------------------- /tests/comparability_tests.rs: -------------------------------------------------------------------------------- 1 | use arrayvec::ArrayVec; 2 | use fixed_slice_vec::*; 3 | use std::mem::MaybeUninit; 4 | use std::panic::AssertUnwindSafe; 5 | use std::sync::{ 6 | atomic::{AtomicUsize, Ordering}, 7 | Arc, 8 | }; 9 | 10 | #[derive(Clone)] 11 | struct DropCountingItem { 12 | drop_count: Arc, 13 | } 14 | 15 | impl Drop for DropCountingItem { 16 | fn drop(&mut self) { 17 | self.drop_count.fetch_add(1, Ordering::SeqCst); 18 | } 19 | } 20 | 21 | trait VecLike { 22 | type Item: Sized; 23 | fn try_push(&mut self, item: Self::Item) -> Result<(), ()>; 24 | fn push(&mut self, item: Self::Item) { 25 | self.try_push(item).unwrap() 26 | } 27 | 28 | fn try_insert(&mut self, index: usize, item: Self::Item) -> Result<(), ()>; 29 | fn insert(&mut self, index: usize, item: Self::Item) { 30 | self.try_insert(index, item).unwrap() 31 | } 32 | 33 | fn pop(&mut self) -> Option; 34 | fn clear(&mut self); 35 | fn capacity(&self) -> usize; 36 | fn as_slice(&self) -> &[Self::Item]; 37 | fn as_mut_slice(&mut self) -> &mut [Self::Item]; 38 | 39 | fn is_empty(&self) -> bool { 40 | self.len() == 0 41 | } 42 | fn len(&self) -> usize { 43 | self.as_slice().len() 44 | } 45 | fn truncate(&mut self, len: usize); 46 | fn try_remove(&mut self, index: usize) -> Result; 47 | fn try_swap_remove(&mut self, index: usize) -> Result; 48 | } 49 | 50 | impl VecLike for Vec { 51 | type Item = T; 52 | 53 | fn try_push(&mut self, item: Self::Item) -> Result<(), ()> { 54 | Vec::push(self, item); 55 | Ok(()) 56 | } 57 | 58 | fn push(&mut self, item: Self::Item) { 59 | Vec::push(self, item) 60 | } 61 | 62 | fn try_insert(&mut self, index: usize, item: Self::Item) -> Result<(), ()> { 63 | Vec::insert(self, index, item); 64 | Ok(()) 65 | } 66 | 67 | fn insert(&mut self, index: usize, item: Self::Item) { 68 | Vec::insert(self, index, item) 69 | } 70 | 71 | fn pop(&mut self) -> Option { 72 | Vec::pop(self) 73 | } 74 | 75 | fn clear(&mut self) { 76 | Vec::clear(self) 77 | } 78 | 79 | fn capacity(&self) -> usize { 80 | Vec::capacity(self) 81 | } 82 | 83 | fn as_slice(&self) -> &[Self::Item] { 84 | self 85 | } 86 | 87 | fn as_mut_slice(&mut self) -> &mut [Self::Item] { 88 | self 89 | } 90 | 91 | fn is_empty(&self) -> bool { 92 | Vec::is_empty(self) 93 | } 94 | 95 | fn len(&self) -> usize { 96 | Vec::len(self) 97 | } 98 | 99 | fn truncate(&mut self, len: usize) { 100 | Vec::truncate(self, len) 101 | } 102 | 103 | fn try_remove(&mut self, index: usize) -> Result { 104 | std::panic::catch_unwind(AssertUnwindSafe(|| Vec::remove(self, index))).map_err(|_| ()) 105 | } 106 | 107 | fn try_swap_remove(&mut self, index: usize) -> Result { 108 | std::panic::catch_unwind(AssertUnwindSafe(|| Vec::swap_remove(self, index))).map_err(|_| ()) 109 | } 110 | } 111 | 112 | impl<'a, T> VecLike for &'a mut Vec { 113 | type Item = T; 114 | 115 | fn try_push(&mut self, item: Self::Item) -> Result<(), ()> { 116 | Vec::push(self, item); 117 | Ok(()) 118 | } 119 | 120 | fn push(&mut self, item: Self::Item) { 121 | Vec::push(self, item) 122 | } 123 | 124 | fn try_insert(&mut self, index: usize, item: Self::Item) -> Result<(), ()> { 125 | Vec::insert(self, index, item); 126 | Ok(()) 127 | } 128 | 129 | fn insert(&mut self, index: usize, item: Self::Item) { 130 | Vec::insert(self, index, item) 131 | } 132 | 133 | fn pop(&mut self) -> Option { 134 | Vec::pop(self) 135 | } 136 | 137 | fn clear(&mut self) { 138 | Vec::clear(self) 139 | } 140 | 141 | fn capacity(&self) -> usize { 142 | Vec::capacity(self) 143 | } 144 | 145 | fn as_slice(&self) -> &[Self::Item] { 146 | self 147 | } 148 | 149 | fn as_mut_slice(&mut self) -> &mut [Self::Item] { 150 | self 151 | } 152 | 153 | fn is_empty(&self) -> bool { 154 | Vec::is_empty(self) 155 | } 156 | 157 | fn len(&self) -> usize { 158 | Vec::len(self) 159 | } 160 | 161 | fn truncate(&mut self, len: usize) { 162 | Vec::truncate(self, len) 163 | } 164 | 165 | fn try_remove(&mut self, index: usize) -> Result { 166 | std::panic::catch_unwind(AssertUnwindSafe(|| Vec::remove(self, index))).map_err(|_| ()) 167 | } 168 | 169 | fn try_swap_remove(&mut self, index: usize) -> Result { 170 | std::panic::catch_unwind(AssertUnwindSafe(|| Vec::swap_remove(self, index))).map_err(|_| ()) 171 | } 172 | } 173 | 174 | impl<'a, T> VecLike for FixedSliceVec<'a, T> { 175 | type Item = T; 176 | 177 | fn try_push(&mut self, item: Self::Item) -> Result<(), ()> { 178 | self.try_push(item).map_err(|_| ()) 179 | } 180 | fn push(&mut self, item: Self::Item) { 181 | self.push(item); 182 | } 183 | 184 | fn try_insert(&mut self, index: usize, item: Self::Item) -> Result<(), ()> { 185 | self.try_insert(index, item).map_err(|_| ()) 186 | } 187 | fn insert(&mut self, index: usize, item: Self::Item) { 188 | self.insert(index, item) 189 | } 190 | 191 | fn pop(&mut self) -> Option { 192 | self.pop() 193 | } 194 | 195 | fn clear(&mut self) { 196 | self.clear(); 197 | } 198 | 199 | fn capacity(&self) -> usize { 200 | self.capacity() 201 | } 202 | 203 | fn as_slice(&self) -> &[Self::Item] { 204 | self 205 | } 206 | 207 | fn as_mut_slice(&mut self) -> &mut [Self::Item] { 208 | self 209 | } 210 | 211 | fn truncate(&mut self, len: usize) { 212 | FixedSliceVec::truncate(self, len) 213 | } 214 | 215 | fn try_remove(&mut self, index: usize) -> Result { 216 | FixedSliceVec::try_remove(self, index).map_err(|_| ()) 217 | } 218 | 219 | fn try_swap_remove(&mut self, index: usize) -> Result { 220 | FixedSliceVec::try_swap_remove(self, index).map_err(|_| ()) 221 | } 222 | } 223 | 224 | impl<'a, T> VecLike for ArrayVec<[T; 32]> { 225 | type Item = T; 226 | 227 | fn try_push(&mut self, item: Self::Item) -> Result<(), ()> { 228 | ArrayVec::try_push(self, item).map_err(|_| ()) 229 | } 230 | 231 | fn push(&mut self, item: Self::Item) { 232 | ArrayVec::push(self, item) 233 | } 234 | 235 | fn try_insert(&mut self, index: usize, item: Self::Item) -> Result<(), ()> { 236 | ArrayVec::try_insert(self, index, item).map_err(|_| ()) 237 | } 238 | 239 | fn insert(&mut self, index: usize, item: Self::Item) { 240 | ArrayVec::insert(self, index, item) 241 | } 242 | 243 | fn pop(&mut self) -> Option { 244 | ArrayVec::pop(self) 245 | } 246 | 247 | fn clear(&mut self) { 248 | ArrayVec::clear(self) 249 | } 250 | 251 | fn capacity(&self) -> usize { 252 | ArrayVec::capacity(self) 253 | } 254 | 255 | fn as_slice(&self) -> &[Self::Item] { 256 | ArrayVec::as_slice(self) 257 | } 258 | 259 | fn as_mut_slice(&mut self) -> &mut [Self::Item] { 260 | ArrayVec::as_mut_slice(self) 261 | } 262 | 263 | fn is_empty(&self) -> bool { 264 | ArrayVec::len(self) == 0 265 | } 266 | 267 | fn len(&self) -> usize { 268 | ArrayVec::len(self) 269 | } 270 | 271 | fn truncate(&mut self, len: usize) { 272 | ArrayVec::truncate(self, len) 273 | } 274 | 275 | fn try_remove(&mut self, index: usize) -> Result { 276 | ArrayVec::pop_at(self, index).ok_or_else(|| ()) 277 | } 278 | 279 | fn try_swap_remove(&mut self, index: usize) -> Result { 280 | ArrayVec::swap_pop(self, index).ok_or_else(|| ()) 281 | } 282 | } 283 | 284 | fn assert_vec_like_drops_items_when_dropped>(v: V) { 285 | let count = Arc::new(AtomicUsize::new(0)); 286 | { 287 | let mut v = v; 288 | v.push(DropCountingItem { 289 | drop_count: count.clone(), 290 | }); 291 | assert_eq!(0, count.load(Ordering::SeqCst)); 292 | } 293 | assert_eq!(1, count.load(Ordering::SeqCst)); 294 | } 295 | 296 | #[test] 297 | fn drops_items_when_dropped() { 298 | assert_vec_like_drops_items_when_dropped(Vec::new()); 299 | 300 | let mut backing: [MaybeUninit; 10] = 301 | unsafe { MaybeUninit::uninit().assume_init() }; 302 | let sv: FixedSliceVec<_> = FixedSliceVec::new(&mut backing[..]); 303 | assert_eq!(10, VecLike::capacity(&sv)); 304 | assert_eq!(0, sv.len()); 305 | assert_vec_like_drops_items_when_dropped(sv); 306 | } 307 | 308 | #[test] 309 | fn drops_items_when_truncate() { 310 | assert_vec_like_drops_items_when_truncate(Vec::new()); 311 | 312 | let mut backing: [MaybeUninit; 10] = 313 | unsafe { MaybeUninit::uninit().assume_init() }; 314 | let sv: FixedSliceVec<_> = FixedSliceVec::new(&mut backing[..]); 315 | assert_eq!(10, VecLike::capacity(&sv)); 316 | assert_eq!(0, sv.len()); 317 | assert_vec_like_drops_items_when_truncate(sv); 318 | } 319 | 320 | fn assert_vec_like_drops_items_when_truncate>(v: V) { 321 | let count = Arc::new(AtomicUsize::new(0)); 322 | let item_a = DropCountingItem { 323 | drop_count: count.clone(), 324 | }; 325 | let item_b = DropCountingItem { 326 | drop_count: count.clone(), 327 | }; 328 | { 329 | let mut v = v; 330 | v.push(item_a); 331 | v.push(item_b); 332 | assert_eq!(0, count.load(Ordering::SeqCst)); 333 | v.truncate(1); 334 | assert_eq!(1, count.load(Ordering::SeqCst)); 335 | } 336 | assert_eq!(2, count.load(Ordering::SeqCst)); 337 | } 338 | 339 | fn assert_vec_like_drops_items_when_clear>(v: V) { 340 | let count = Arc::new(AtomicUsize::new(0)); 341 | let item = DropCountingItem { 342 | drop_count: count.clone(), 343 | }; 344 | { 345 | let mut v = v; 346 | v.push(item); 347 | assert_eq!(0, count.load(Ordering::SeqCst)); 348 | v.clear(); 349 | assert_eq!(1, count.load(Ordering::SeqCst)); 350 | } 351 | assert_eq!(1, count.load(Ordering::SeqCst)); 352 | } 353 | 354 | #[test] 355 | fn drops_items_when_clear() { 356 | assert_vec_like_drops_items_when_clear(Vec::new()); 357 | 358 | let mut backing: [MaybeUninit; 10] = 359 | unsafe { MaybeUninit::uninit().assume_init() }; 360 | let sv: FixedSliceVec<_> = FixedSliceVec::new(&mut backing[..]); 361 | assert_vec_like_drops_items_when_clear(sv); 362 | } 363 | 364 | fn assert_vec_like_drops_items_replaced_via_mut_slice>(v: V) { 365 | let count_a = Arc::new(AtomicUsize::new(0)); 366 | let count_b = Arc::new(AtomicUsize::new(0)); 367 | { 368 | let mut v = v; 369 | v.push(DropCountingItem { 370 | drop_count: count_a.clone(), 371 | }); 372 | assert_eq!(0, count_a.load(Ordering::SeqCst)); 373 | let v_ref: &mut [DropCountingItem] = v.as_mut_slice(); 374 | assert_eq!(0, count_a.load(Ordering::SeqCst)); 375 | v_ref[0] = DropCountingItem { 376 | drop_count: count_b.clone(), 377 | }; 378 | assert_eq!(1, count_a.load(Ordering::SeqCst)); 379 | assert_eq!(0, count_b.load(Ordering::SeqCst)); 380 | { 381 | let _item_b = v.pop(); 382 | assert_eq!(0, count_b.load(Ordering::SeqCst)); 383 | } 384 | assert_eq!(1, count_b.load(Ordering::SeqCst)); 385 | } 386 | assert_eq!(1, count_a.load(Ordering::SeqCst)); 387 | assert_eq!(1, count_b.load(Ordering::SeqCst)); 388 | } 389 | 390 | #[test] 391 | fn drops_items_replaced_via_mut_slice() { 392 | assert_vec_like_drops_items_replaced_via_mut_slice(Vec::new()); 393 | 394 | let mut backing: [MaybeUninit; 10] = 395 | unsafe { MaybeUninit::uninit().assume_init() }; 396 | let sv: FixedSliceVec<_> = FixedSliceVec::new(&mut backing[..]); 397 | assert_vec_like_drops_items_replaced_via_mut_slice(sv); 398 | } 399 | 400 | pub mod vec_like_operations { 401 | use super::*; 402 | use proptest::prelude::*; 403 | #[derive(Debug, Clone)] 404 | pub enum VecLikeOp { 405 | Push(T), 406 | Insert(I, T), 407 | Pop, 408 | Clear, 409 | Truncate(I), 410 | Remove(I), 411 | SwapRemove(I), 412 | } 413 | 414 | fn arbitrary_vec_like_op( 415 | max_expected_capacity: usize, 416 | ) -> impl Strategy> { 417 | // Weighted to avoid clearing so often that we only rarely encounter 418 | // border conditions 419 | prop_oneof! [ 420 | 30 => any::<(usize, u16)>().prop_map(|(ind, v)| VecLikeOp::Insert(ind, v)), 421 | 20 => any::().prop_map(|v| VecLikeOp::Push(v)), 422 | 10 => Just(VecLikeOp::Pop), 423 | 4 => (0..(max_expected_capacity*2)).prop_map(|v| VecLikeOp::Remove(v)), 424 | 4 => (0..(max_expected_capacity*2)).prop_map(|v| VecLikeOp::SwapRemove(v)), 425 | 4 => (0..(max_expected_capacity*2)).prop_map(|v| VecLikeOp::Truncate(v)), 426 | 1 => Just(VecLikeOp::Clear), 427 | ] 428 | } 429 | 430 | fn prop_assert_equivalent<'a, T>( 431 | other_vec: &mut dyn VecLike, 432 | fixed_slice_vec: &mut FixedSliceVec<'a, T>, 433 | ) -> Result<(), TestCaseError> 434 | where 435 | T: std::fmt::Debug, 436 | T: PartialEq, 437 | { 438 | prop_assert_eq!( 439 | other_vec.as_slice(), 440 | fixed_slice_vec.as_slice(), 441 | "Slice contents" 442 | ); 443 | prop_assert_eq!( 444 | other_vec.as_mut_slice(), 445 | fixed_slice_vec.as_mut_slice(), 446 | "Mutable slice contents" 447 | ); 448 | prop_assert_eq!(other_vec.len(), fixed_slice_vec.len(), "Lengths"); 449 | prop_assert_eq!( 450 | other_vec.is_empty(), 451 | fixed_slice_vec.is_empty(), 452 | "Empty statuses" 453 | ); 454 | Ok(()) 455 | } 456 | 457 | fn assert_alike_operations( 458 | other_vec: &mut dyn VecLike, 459 | operations: Vec>, 460 | mut storage_bytes: Vec>, 461 | ) -> Result<(), TestCaseError> { 462 | let mut fs_vec: FixedSliceVec = FixedSliceVec::from_uninit_bytes(&mut storage_bytes); 463 | for op in operations { 464 | match op { 465 | VecLikeOp::Push(v) => { 466 | let fs_result = fs_vec.try_push(v); 467 | if let Err(e) = fs_result { 468 | prop_assert!(fs_vec.is_full(), "FixedSliceVec should only reject pushes when full. Failed pushing {:?}", e); 469 | } else { 470 | if let Err(e) = other_vec.try_push(v) { 471 | prop_assert_eq!(other_vec.capacity(), other_vec.len(), "Other VecLike implementations should only reject when full. Failed pushing {:?}", e); 472 | // Roll back the value just added so we don't diverge simply because 473 | // of different capacities. 474 | assert_eq!( 475 | Some(v), 476 | fs_vec.pop(), 477 | "Ought to have popped back what we just pushed" 478 | ); 479 | } 480 | } 481 | } 482 | VecLikeOp::Insert(i, v) => { 483 | let fs_result = fs_vec.try_insert(i, v); 484 | if let Err(e) = fs_result { 485 | let insert_failure_expected = 486 | (other_vec.capacity() == other_vec.len()) || (i > other_vec.len()); 487 | prop_assert!(insert_failure_expected, "FixedSliceVec should only reject when full or insertion index outside valid range. Failed inserting {:?} at {:?}", e, i); 488 | } else { 489 | if let Err(e) = other_vec.try_insert(i, v) { 490 | let insert_failure_expected = 491 | (other_vec.capacity() == other_vec.len()) || (i > other_vec.len()); 492 | prop_assert!(insert_failure_expected, "Other VecLike implementations should only reject when full or insertion index outside valid range. Failed inserting {:?} at {:?}", e, i); 493 | // Roll back the value just added so we don't diverge simply because 494 | // of different capacities. 495 | assert_eq!( 496 | Ok(v), 497 | fs_vec.try_remove(i), 498 | "Ought to have popped back what we just inserted" 499 | ); 500 | } 501 | } 502 | } 503 | VecLikeOp::Clear => { 504 | fs_vec.clear(); 505 | other_vec.clear(); 506 | } 507 | VecLikeOp::Pop => { 508 | let fs_result = fs_vec.pop(); 509 | let other_result = other_vec.pop(); 510 | prop_assert_eq!( 511 | other_result, 512 | fs_result, 513 | "Returned values from `pop` should be the same" 514 | ); 515 | } 516 | VecLikeOp::Truncate(truncate_len) => { 517 | let prior_fs_length = fs_vec.len(); 518 | let prior_other_length = other_vec.len(); 519 | prop_assert_eq!( 520 | prior_fs_length, 521 | prior_other_length, 522 | "The 2 vecs had out-of-sync starting lengths" 523 | ); 524 | fs_vec.truncate(truncate_len); 525 | other_vec.truncate(truncate_len); 526 | let posterior_fs_length = fs_vec.len(); 527 | let posterior_other_length = other_vec.len(); 528 | if truncate_len <= prior_fs_length { 529 | prop_assert_eq!( 530 | posterior_fs_length, 531 | truncate_len, 532 | "fsv did not truncate to the target len" 533 | ); 534 | prop_assert_eq!( 535 | posterior_other_length, 536 | truncate_len, 537 | "other did not truncate to the target len" 538 | ); 539 | } 540 | } 541 | VecLikeOp::Remove(index) => { 542 | let fs_result = VecLike::try_remove(&mut fs_vec, index); 543 | let other_result = VecLike::try_remove(other_vec, index); 544 | prop_assert_eq!( 545 | other_result, 546 | fs_result, 547 | "Returned values from `try_remove` should be the same" 548 | ); 549 | } 550 | VecLikeOp::SwapRemove(index) => { 551 | let fs_result = VecLike::try_swap_remove(&mut fs_vec, index); 552 | let other_result = VecLike::try_swap_remove(other_vec, index); 553 | prop_assert_eq!( 554 | other_result, 555 | fs_result, 556 | "Returned values from `try_swap_remove` should be the same" 557 | ); 558 | } 559 | } 560 | prop_assert_equivalent(other_vec, &mut fs_vec)?; 561 | } 562 | Ok(()) 563 | } 564 | 565 | proptest! { 566 | #[test] 567 | #[cfg_attr(miri, ignore)] 568 | fn compare_vec_like_operations_against_std( 569 | operations in proptest::collection::vec(arbitrary_vec_like_op(512), 1..1000), 570 | storage_bytes in proptest::collection::vec(Just(MaybeUninit::new(0u8)), 0..1024) 571 | ) { 572 | let mut std_vec = Vec::new(); 573 | assert_alike_operations(&mut std_vec, operations, storage_bytes)?; 574 | } 575 | #[test] 576 | #[cfg_attr(miri, ignore)] 577 | fn compare_vec_like_operations_against_array_vec( 578 | operations in proptest::collection::vec(arbitrary_vec_like_op(512), 1..1000), 579 | storage_bytes in proptest::collection::vec(Just(MaybeUninit::new(0u8)), 0..1024) 580 | ) { 581 | let mut av_vec: ArrayVec<[u16; 32]> = ArrayVec::new(); 582 | assert_alike_operations(&mut av_vec, operations, storage_bytes)?; 583 | } 584 | } 585 | } 586 | -------------------------------------------------------------------------------- /tests/std_required_tests.rs: -------------------------------------------------------------------------------- 1 | //! Tests that require access to the standard library, 2 | //! e.g. for catching panics 3 | 4 | use fixed_slice_vec::{FixedSliceVec, IndexError, StorageError}; 5 | use proptest::std_facade::HashMap; 6 | use std::mem::MaybeUninit; 7 | use std::panic::AssertUnwindSafe; 8 | use std::sync::atomic::{AtomicBool, Ordering}; 9 | use std::sync::{Arc, Mutex}; 10 | 11 | fn uninit_storage() -> [MaybeUninit; 4] { 12 | unsafe { MaybeUninit::uninit().assume_init() } 13 | } 14 | 15 | #[test] 16 | fn manual_remove() { 17 | let expected = [0u8, 2, 4, 8]; 18 | let mut storage = uninit_storage(); 19 | let mut fsv = FixedSliceVec::from_uninit_bytes(&mut storage[..]); 20 | assert!(fsv.try_extend(expected.iter().copied()).is_ok()); 21 | 22 | assert_eq!(2, fsv.remove(1)); 23 | assert_eq!(&[0u8, 4, 8], fsv.as_slice()); 24 | 25 | let unwind = std::panic::catch_unwind(AssertUnwindSafe(|| fsv.remove(100))); 26 | assert!(unwind.is_err()); 27 | } 28 | 29 | #[test] 30 | fn manual_push() { 31 | let mut storage: [MaybeUninit; 16] = unsafe { MaybeUninit::uninit().assume_init() }; 32 | let mut fsv: FixedSliceVec = FixedSliceVec::from_uninit_bytes(&mut storage[..]); 33 | fsv.push(314); 34 | assert_eq!(&[314u32], fsv.as_slice()); 35 | 36 | let unwind = std::panic::catch_unwind(AssertUnwindSafe(|| { 37 | for i in 0..16 { 38 | fsv.push(i) 39 | } 40 | })); 41 | assert!(unwind.is_err()); 42 | } 43 | 44 | #[test] 45 | fn manual_try_swap_remove() { 46 | let expected = [0u8, 2, 4, 8]; 47 | let mut storage = uninit_storage(); 48 | let mut fsv = FixedSliceVec::from_uninit_bytes(&mut storage[..]); 49 | assert!(fsv.try_extend(expected.iter().copied()).is_ok()); 50 | 51 | let unwind = std::panic::catch_unwind(AssertUnwindSafe(|| fsv.remove(100))); 52 | assert!(unwind.is_err()); 53 | let unwind = std::panic::catch_unwind(AssertUnwindSafe(|| fsv.remove(4))); 54 | assert!(unwind.is_err()); 55 | assert_eq!(&[0u8, 2, 4, 8], fsv.as_slice()); 56 | assert_eq!(Ok(2), fsv.try_swap_remove(1)); 57 | assert_eq!(&[0u8, 8, 4], fsv.as_slice()); 58 | } 59 | 60 | #[test] 61 | fn hashing_successful() { 62 | let expected = [0u8, 2, 4, 8]; 63 | let mut storage = uninit_storage(); 64 | let mut fsv = FixedSliceVec::from_uninit_bytes(&mut storage[..]); 65 | assert!(fsv.try_extend(expected.iter().copied()).is_ok()); 66 | 67 | let mut map = HashMap::new(); 68 | map.insert(fsv, "foo"); 69 | } 70 | 71 | #[test] 72 | fn formatting_successful() { 73 | let index_error = format!("{:?}", IndexError); 74 | assert!(index_error.len() > 0); 75 | let storage_error = format!("{:?}", StorageError("foo")); 76 | assert!(storage_error.len() > 0); 77 | assert!(!storage_error.contains("foo")); 78 | 79 | let expected = [0u8, 2, 4, 8]; 80 | let mut storage = uninit_storage(); 81 | let mut fsv = FixedSliceVec::from_uninit_bytes(&mut storage[..]); 82 | assert!(fsv.try_extend(expected.iter().copied()).is_ok()); 83 | assert_eq!("[0, 2, 4, 8]", format!("{:?}", fsv)) 84 | } 85 | 86 | struct TrueOnDrop(Arc); 87 | impl Drop for TrueOnDrop { 88 | fn drop(&mut self) { 89 | self.0.as_ref().store(true, Ordering::SeqCst); 90 | } 91 | } 92 | 93 | #[test] 94 | fn embed_does_not_run_drops_atomic() { 95 | let flip = Arc::new(AtomicBool::new(false)); 96 | let flip_clone = flip.clone(); 97 | let mut storage: [u8; 16] = [0u8; 16]; 98 | let emb = unsafe { 99 | fixed_slice_vec::single::embed(&mut storage[..], move |_leftovers| { 100 | Result::::Ok(TrueOnDrop(flip_clone)) 101 | }) 102 | .unwrap() 103 | }; 104 | 105 | assert!(!flip.load(Ordering::SeqCst)); 106 | assert!(!emb.0.load(Ordering::SeqCst)); 107 | 108 | // Manually clean up the Arc to avoid alloc-related-leak 109 | unsafe { 110 | let ptr: *const TrueOnDrop = emb; 111 | let extracted = ptr.read(); 112 | std::mem::drop(extracted); 113 | } 114 | } 115 | #[test] 116 | fn embed_uninit_does_not_run_drops_atomic() { 117 | let flip = Arc::new(AtomicBool::new(false)); 118 | let flip_clone = flip.clone(); 119 | let mut storage: [MaybeUninit; 16] = unsafe { MaybeUninit::uninit().assume_init() }; 120 | let emb = fixed_slice_vec::single::embed_uninit(&mut storage[..], move |_leftovers| { 121 | Result::::Ok(TrueOnDrop(flip_clone)) 122 | }) 123 | .unwrap(); 124 | 125 | assert!(!flip.load(Ordering::SeqCst)); 126 | assert!(!emb.0.load(Ordering::SeqCst)); 127 | 128 | // Manually clean up the Arc to avoid alloc-related-leak 129 | unsafe { 130 | let ptr: *const TrueOnDrop = emb; 131 | let extracted = ptr.read(); 132 | std::mem::drop(extracted); 133 | } 134 | } 135 | 136 | #[test] 137 | fn fsv_does_not_run_drops_on_push_atomic() { 138 | let flip = Arc::new(AtomicBool::new(false)); 139 | let mut storage: [MaybeUninit; 16] = unsafe { MaybeUninit::uninit().assume_init() }; 140 | { 141 | let mut fsv: FixedSliceVec = FixedSliceVec::from_uninit_bytes(&mut storage); 142 | let flip_clone = flip.clone(); 143 | fsv.try_push(TrueOnDrop(flip_clone)).unwrap(); 144 | assert!(!flip.load(Ordering::SeqCst)); 145 | } 146 | assert!(flip.load(Ordering::SeqCst)); 147 | } 148 | 149 | struct PanicDropHelper { 150 | drop_counter: Arc>, 151 | panic_on_drop: Arc>, 152 | } 153 | impl Drop for PanicDropHelper { 154 | fn drop(&mut self) { 155 | if let Ok(mut drop_counter) = self.drop_counter.lock() { 156 | *drop_counter = drop_counter.saturating_add(1); 157 | } 158 | let should_panic = if let Ok(panic_on_drop) = self.panic_on_drop.lock() { 159 | *panic_on_drop 160 | } else { 161 | false 162 | }; 163 | if should_panic { 164 | panic!("As you wish"); 165 | } 166 | } 167 | } 168 | 169 | #[test] 170 | fn double_drop_averted_despite_panic_during_clear() { 171 | // Detect the case when a panic in the middle of a `clear` call 172 | // may leave the FSV in an uncertain state which may cause double-frees 173 | // during subsequent calls to `clear` 174 | let drop_counters = vec![ 175 | Arc::new(Mutex::new(0)), 176 | Arc::new(Mutex::new(0)), 177 | Arc::new(Mutex::new(0)), 178 | ]; 179 | let panic_on_drop_instructions = vec![ 180 | Arc::new(Mutex::new(false)), 181 | Arc::new(Mutex::new(true)), 182 | Arc::new(Mutex::new(false)), 183 | ]; 184 | let mut storage: [MaybeUninit; 1024] = unsafe { MaybeUninit::uninit().assume_init() }; 185 | let mut fsv: FixedSliceVec = FixedSliceVec::from_uninit_bytes(&mut storage); 186 | for (drop_counter, panic_on_drop) in drop_counters.iter().zip(panic_on_drop_instructions.iter()) 187 | { 188 | fsv.push(PanicDropHelper { 189 | drop_counter: drop_counter.clone(), 190 | panic_on_drop: panic_on_drop.clone(), 191 | }); 192 | } 193 | let r = std::panic::catch_unwind(AssertUnwindSafe(|| { 194 | fsv.clear(); 195 | })); 196 | assert!(r.is_err()); 197 | 198 | let found_n_drops: Vec = drop_counters.iter().map(|dc| *dc.lock().unwrap()).collect(); 199 | assert!(found_n_drops.iter().sum::() <= 3); 200 | 201 | assert!(fsv.is_empty()); 202 | 203 | for panic_on_drop in panic_on_drop_instructions { 204 | let mut pod = panic_on_drop.lock().unwrap(); 205 | *pod = false; 206 | } 207 | 208 | fsv.clear(); 209 | assert!(found_n_drops.iter().sum::() <= 3); 210 | 211 | assert!(fsv.is_empty()); 212 | } 213 | #[test] 214 | fn double_drop_averted_despite_panic_during_truncate() { 215 | // Detect the case when a panic in the middle of a `truncate` call 216 | // may leave the FSV in an uncertain state which may cause double-frees 217 | // during subsequent calls to `truncate` 218 | let drop_counters = vec![ 219 | Arc::new(Mutex::new(0)), 220 | Arc::new(Mutex::new(0)), 221 | Arc::new(Mutex::new(0)), 222 | ]; 223 | let panic_on_drop_instructions = vec![ 224 | Arc::new(Mutex::new(false)), 225 | Arc::new(Mutex::new(true)), 226 | Arc::new(Mutex::new(false)), 227 | ]; 228 | let mut storage: [MaybeUninit; 1024] = unsafe { MaybeUninit::uninit().assume_init() }; 229 | let mut fsv: FixedSliceVec = FixedSliceVec::from_uninit_bytes(&mut storage); 230 | for (drop_counter, panic_on_drop) in drop_counters.iter().zip(panic_on_drop_instructions.iter()) 231 | { 232 | fsv.push(PanicDropHelper { 233 | drop_counter: drop_counter.clone(), 234 | panic_on_drop: panic_on_drop.clone(), 235 | }); 236 | } 237 | let r = std::panic::catch_unwind(AssertUnwindSafe(|| { 238 | fsv.truncate(1); 239 | })); 240 | assert!(r.is_err()); 241 | 242 | let found_n_drops: Vec = drop_counters.iter().map(|dc| *dc.lock().unwrap()).collect(); 243 | assert!(found_n_drops.iter().sum::() <= 2); 244 | 245 | assert_eq!(1, fsv.len()); 246 | 247 | for panic_on_drop in panic_on_drop_instructions { 248 | let mut pod = panic_on_drop.lock().unwrap(); 249 | *pod = false; 250 | } 251 | 252 | fsv.truncate(1); 253 | assert!(found_n_drops.iter().sum::() <= 2); 254 | assert_eq!(1, fsv.len()); 255 | 256 | fsv.truncate(0); 257 | assert!(fsv.is_empty()); 258 | assert!(found_n_drops.iter().sum::() <= 3); 259 | } 260 | --------------------------------------------------------------------------------