├── .cirrus.yml ├── .gitignore ├── Cargo.toml ├── Changes.md ├── LICENSE-APACHE ├── LICENSE-MIT ├── LICENSE-ZLIB ├── Readme.md ├── examples ├── huffman-buffer.rs └── min-slice.rs ├── release_checks └── src ├── array.rs ├── int.rs ├── lib.rs ├── mem.rs ├── readme.rs └── tag.rs /.cirrus.yml: -------------------------------------------------------------------------------- 1 | main_task: 2 | container: 3 | image: rust:latest 4 | matrix: 5 | env: 6 | FEATURES: '' 7 | FEATURES: 'alloc' 8 | cargo_cache: 9 | folder: $CARGO_HOME/registry 10 | fingerprint_script: cargo update && cat Cargo.lock 11 | build_script: cargo build --features="$FEATURES" 12 | test_script: cargo test --features="$FEATURES" 13 | before_cache_script: rm -rf $CARGO_HOME/registry/index 14 | 15 | clean_task: 16 | container: 17 | image: rust:latest 18 | script: 19 | - rustup component add rustfmt 20 | - cargo fmt -- --check 21 | 22 | clippy_task: 23 | container: 24 | image: rustlang/rust:nightly 25 | # Allowed to fail with missing clippy, or other issues by newly introduced 26 | # lints that would break API stability (which happens sometimes). 27 | allow_failures: true 28 | script: 29 | - rustup component add clippy 30 | - cargo clippy --all-targets --all-features -- -D warnings 31 | 32 | nightly_task: 33 | container: 34 | image: rustlang/rust:nightly 35 | cargo_cache: 36 | folder: $CARGO_HOME/registry 37 | fingerprint_script: cargo update && cat Cargo.lock 38 | build_script: cargo build --all-features 39 | test_script: cargo test --all-features 40 | before_cache_script: rm -rf $CARGO_HOME/registry/index 41 | 42 | release_task: 43 | only_if: $CIRRUS_BRANCH =~ 'release-.*' 44 | container: 45 | image: rust:latest 46 | script: ./release_checks 47 | 48 | doc_task: 49 | container: 50 | image: rustlang/rust:nightly 51 | script: cargo doc --no-deps --document-private-items --all-features 52 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | Cargo.lock 3 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "index-ext" 3 | description = "Index slices with arbitrary ints and as arrays." 4 | license = "Apache-2.0 OR MIT OR Zlib" 5 | version = "1.0.0" 6 | authors = ["Andreas Molzer "] 7 | edition = "2018" 8 | rust-version = "1.61" 9 | 10 | readme = "Readme.md" 11 | documentation = "https://docs.rs/index-ext" 12 | homepage = "https://github.com/HeroicKatora/index-ext" 13 | repository = "https://github.com/HeroicKatora/index-ext" 14 | 15 | categories = ["data-structures", "embedded", "no-std", "rust-patterns"] 16 | keywords = ["index", "extension", "int-index", "array-index"] 17 | 18 | [dependencies] 19 | generativity = "1" 20 | 21 | [dev-dependencies] 22 | libc = "0.2" 23 | 24 | [features] 25 | alloc = [] 26 | 27 | [[example]] 28 | name = "huffman-buffer" 29 | required-features = ["alloc"] 30 | 31 | [package.metadata.docs.rs] 32 | all-features = true 33 | -------------------------------------------------------------------------------- /Changes.md: -------------------------------------------------------------------------------- 1 | ## v1.0.0-beta.0 2 | 3 | The const-generics related types and functions no longer require the `nightly` 4 | feature and are now always enabled. This implies an MSRV of `1.52.0`. 5 | 6 | The `Ref`/`Mut` is replaced with references to `Slice`, an in-place wrapper of 7 | the internal slice. 8 | 9 | Added `Boxed` versions for tagging a box as fulfilling the length requirements 10 | of a slice relative to the length invariant associated with a tag. 11 | 12 | Added interoperability with the `generativity` crate. 13 | 14 | Added module `mem`, containing layout compatible transparent wrappers around 15 | all primitive types that guarantee also being a valid `usize`. 16 | 17 | ## v0.0.2 18 | 19 | Added module `tags`, a generativity based type system for pre-checked slice 20 | indices that has no runtime overhead compared to `get_unchecked` but requires 21 | some statically known types. 22 | 23 | ## v0.0.1 24 | 25 | Initial version 26 | -------------------------------------------------------------------------------- /LICENSE-APACHE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /LICENSE-MIT: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 Andreas Molzer aka. HeroicKatora 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /LICENSE-ZLIB: -------------------------------------------------------------------------------- 1 | Copyright (c) 2020 Andreas Molzer aka. HeroicKatora 2 | 3 | This software is provided 'as-is', without any express or implied warranty. In 4 | no event will the authors be held liable for any damages arising from the use 5 | of this software. 6 | 7 | Permission is granted to anyone to use this software for any purpose, including 8 | commercial applications, and to alter it and redistribute it freely, subject to 9 | the following restrictions: 10 | 11 | 1. The origin of this software must not be misrepresented; you must not claim 12 | that you wrote the original software. If you use this software in a product, an 13 | acknowledgment in the product documentation would be appreciated but is not 14 | required. 15 | 16 | 2. Altered source versions must be plainly marked as such, and must not be 17 | misrepresented as being the original software. 18 | 19 | 3. This notice may not be removed or altered from any source distribution. 20 | -------------------------------------------------------------------------------- /Readme.md: -------------------------------------------------------------------------------- 1 | # index-ext 2 | 3 | A crate for flexible indexing, improving correctness and clarifying intent. 4 | 5 | ## Automatic index type conversion 6 | 7 | This crate makes it ergonomic to use arbitrary integer types as mathematical 8 | indices. This is especially important for libraries where indices are dictated 9 | by an external standard. Another reason could be platform or performance 10 | requirements due to which `usize` is the wrong choice. With the types and trait 11 | provided here, this just works for smaller and larger integer types than 12 | `usize`. 13 | 14 | ```rust 15 | use index_ext::{Intex, SliceIntExt}; 16 | let buffer = [0; 256]; 17 | assert_eq!(buffer[Intex(255_u8)], 0); 18 | assert_eq!(buffer[Intex(255_i32)], 0); 19 | assert_eq!(buffer.get_int(-1_i8), None); 20 | assert_eq!(buffer.get_int(u128::max_value()), None); 21 | ``` 22 | 23 | ## Index into a slice to produce an array 24 | 25 | Const generics promise to provide even more possibilities. Currently, when one 26 | wants to reference a statically sized array within a dynamic slice then best 27 | choices are not the most ergonomic ones. On very recent nightly Rust we can 28 | leverage parameter deduction and const generics to design an index type that 29 | combines the best aspects. 30 | 31 | ```rust 32 | use index_ext::ArrayPrefix; 33 | let rgba = [0; 4]; 34 | let rgb: [u8; 3] = rgba[ArrayPrefix]; 35 | let [r, g, b] = &rgba[ArrayPrefix]; 36 | ``` 37 | 38 | ## Statically checked indices 39 | 40 | The concept of tags, a type identifying a unique slice length, allows one to 41 | prove through the type system that some integer is a valid index for a slice. 42 | There are two ways to use it safely, by borrowing the original slice and 43 | generative lifetimes or by using compile time constants, and one way to 44 | unsafely use arbitrary types. 45 | 46 | ```rust 47 | use index_ext::tag; 48 | 49 | tag::with_ref(&[0, 1, 2, 3][..], |slice, len| { 50 | // Index construction is checked/fallible.. 51 | let idx = len.into_len().index(2).unwrap(); 52 | 53 | // But use is NOT. The method get_safe does no runtime checks. 54 | assert_eq!(*slice.get_safe(idx), 2); 55 | }); 56 | ``` 57 | 58 | This looks less impressive than it is because any short example is also caught 59 | by the value range analysis of the optimizer. However, you can safely store 60 | these indices in structures and pass them across function boundaries without 61 | fail and you get a _guarantee_ that the access check is elided. See the Huffman 62 | example for a use case with real differences. 63 | 64 | ## License 65 | 66 | Triple licensed under any of Apache-2.0, MIT, or zlib terms. 67 | -------------------------------------------------------------------------------- /examples/huffman-buffer.rs: -------------------------------------------------------------------------------- 1 | /// Compare two normal styles of implementing tree traversal. 2 | /// 3 | /// This uses the static `Constant` style, a dependent version with dynamic 4 | /// buffer sizes might also be possible but requires non-`'static` structs. 5 | /// 6 | /// This implements a semi-realistic style for Huffman coding with 8-bit node 7 | /// arity. We have a tree in memory by storing the parent of all nodes and its 8 | /// relative index, which is also its output symbol. A symbol is encoded by 9 | /// following its path to the root and concatenating all bytes on the way. This 10 | /// is done in three styles, each with 1k warm up iterations and then 1M 11 | /// iterations summing their execution time. 12 | /// 13 | /// Firstly, using safe Rust code and only the standard library. This does 14 | /// many of the bounds checks at runtime. Judging from the generated 15 | /// assembly, llvm is unable to remember (or check) that indices stored in 16 | /// `parent` are in-bounds of the arrays. Hence when loading them it treats 17 | /// them as an opaque value and does the usual checks against the length. 18 | /// 19 | /// The second one uses unsafe Rust code, that one should conventionally 20 | /// avoid to write, asserting to the compiler that we've not stored any 21 | /// invalid parent indices. But this fact is not validated in any way and 22 | /// requires quite a lot of trust in not changing the array sizes. However, 23 | /// it is shown to be faster—at least on all machines I've tested it on. 24 | /// 25 | /// And finally one last style that uses the library. It encodes the allowed 26 | /// range of indices into that special `Idx<_, Tag>` type, and they are 27 | /// checked at construction. But when loading them from the `parent` array 28 | /// we do not need to rely on llvm's ability to see their value range so we 29 | /// have effectively moved the index check of the access to compile time. 30 | /// This retains the performance of the unsafe style with the additional 31 | /// safety against buffer size changes. 32 | /// 33 | /// The one thing to look for is the code for `code_of`/`code_of_unchecked`. 34 | /// The arrays parents and bytes are accessed with the `[idx]` operator in 35 | /// the style, with `get_unchecked` in the second and with `get_safe` in the 36 | /// last, otherwise their code is identical. 37 | /// 38 | use index_ext::tag::{Boxed, Constant, ConstantSource, ExactSize, Idx}; 39 | 40 | const BUFFER_SIZE: usize = 4096; 41 | struct BufferSize4K; 42 | 43 | impl ConstantSource for BufferSize4K { 44 | const LEN: usize = BUFFER_SIZE; 45 | } 46 | 47 | const LEN: ExactSize> = Constant::EXACT_SIZE; 48 | 49 | type ParentId = Idx>; 50 | 51 | /// A 8-byte Huffman encoding tree. 52 | /// Each entry points to its is ensured to point into the `parents` buffer. 53 | struct HuffmanTree { 54 | parents: Boxed>, 55 | bytes: Boxed>, 56 | } 57 | 58 | fn main() { 59 | let huffman = HuffmanTree::new(); 60 | let compare = Comparison::new(); 61 | 62 | let codes: Vec<_> = (0..BUFFER_SIZE).collect(); 63 | 64 | // Try to make the numbers slightly more predictable? Dynamic frequency 65 | // scaling (due to heat etc.) will ruin these. 66 | #[cfg(target_family = "unix")] 67 | unsafe { 68 | let mut set: libc::cpu_set_t = core::mem::zeroed(); 69 | libc::CPU_ZERO(&mut set); 70 | libc::CPU_SET(0, &mut set); 71 | libc::sched_setaffinity(0, 1, &set); 72 | } 73 | 74 | // The classic, checked method. 75 | for _ in 0..1_000 { 76 | for &code in &codes { 77 | let _ = compare.code_of(code); 78 | } 79 | } 80 | let start = std::time::Instant::now(); 81 | let mut side_effect = 0; 82 | for _ in 0..1_000_000 { 83 | for &code in &codes { 84 | side_effect |= compare.code_of(code); 85 | } 86 | } 87 | let duration = std::time::Instant::now() 88 | .duration_since(start) 89 | .as_secs_f32(); 90 | println!( 91 | "With dynamically checked indices (safe): {}s ({})", 92 | duration, side_effect 93 | ); 94 | 95 | // The unchecked, unsafe method. 96 | for _ in 0..1_000 { 97 | for &code in &codes { 98 | let _ = compare.code_of_unchecked(code); 99 | } 100 | } 101 | let start = std::time::Instant::now(); 102 | let mut side_effect = 0; 103 | for _ in 0..1_000_000 { 104 | for &code in &codes { 105 | side_effect |= compare.code_of_unchecked(code); 106 | } 107 | } 108 | let duration = std::time::Instant::now() 109 | .duration_since(start) 110 | .as_secs_f32(); 111 | println!( 112 | "With dynamically unchecked indices (ad-hoc unsafe): {}s ({})", 113 | duration, side_effect 114 | ); 115 | 116 | // The safe, type tag checked method of this library. 117 | let codes: Vec<_> = (0..BUFFER_SIZE) 118 | .map(|i| LEN.into_len().index(i).unwrap()) 119 | .collect(); 120 | 121 | for _ in 0..1_000 { 122 | for &code in &codes { 123 | let _ = huffman.code_of(code); 124 | } 125 | } 126 | let start = std::time::Instant::now(); 127 | let mut side_effect = 0; 128 | for _ in 0..1_000_000 { 129 | for &code in &codes { 130 | side_effect |= huffman.code_of(code); 131 | } 132 | } 133 | let duration = std::time::Instant::now() 134 | .duration_since(start) 135 | .as_secs_f32(); 136 | println!( 137 | "With dependently checked indices (this crate): {}s ({})", 138 | duration, side_effect 139 | ); 140 | } 141 | 142 | /// An alternative, with standard checked indexing. 143 | struct Comparison { 144 | parents: Box<[usize; BUFFER_SIZE]>, 145 | bytes: Box<[u8; BUFFER_SIZE]>, 146 | } 147 | 148 | impl HuffmanTree { 149 | pub fn new() -> Self { 150 | let mut p: Vec<_> = generate_parent_indices() 151 | .map(|idx| LEN.into_len().index(idx).unwrap()) 152 | .collect(); 153 | 154 | // Zero-extend. 155 | let zero = LEN.into_len().index(0).unwrap(); 156 | p.resize(BUFFER_SIZE, zero); 157 | let p = p.into_boxed_slice(); 158 | 159 | let mut b = vec![0; BUFFER_SIZE].into_boxed_slice(); 160 | for (p, b) in generate_bytes().zip(&mut b[..]) { 161 | *b = p; 162 | } 163 | 164 | HuffmanTree { 165 | parents: Boxed::new(p, LEN).unwrap_or_else(|_| panic!("Bad length of buffer")), 166 | bytes: Boxed::new(b, LEN).unwrap_or_else(|_| panic!("Bad length of buffer")), 167 | } 168 | } 169 | 170 | fn code_of(&self, start: ParentId) -> u64 { 171 | let mut enc: u64 = 0; 172 | let mut code = start; 173 | while { 174 | enc = (enc << 8) | u64::from(*self.bytes.get_safe(code)); 175 | let more = code.into_inner() > 255; 176 | code = *self.parents.get_safe(code); 177 | more 178 | } {} 179 | enc 180 | } 181 | } 182 | 183 | impl Comparison { 184 | pub fn new() -> Self { 185 | let mut c = Comparison { 186 | parents: Box::new([0; BUFFER_SIZE]), 187 | bytes: Box::new([0; BUFFER_SIZE]), 188 | }; 189 | 190 | for (p, b) in generate_parent_indices().zip(&mut c.parents[..]) { 191 | *b = p; 192 | } 193 | 194 | for (p, b) in generate_bytes().zip(&mut c.bytes[..]) { 195 | *b = p; 196 | } 197 | 198 | c 199 | } 200 | 201 | fn code_of(&self, start: usize) -> u64 { 202 | let mut enc: u64 = 0; 203 | let mut code = start; 204 | while { 205 | enc = (enc << 8) | u64::from(self.bytes[code]); 206 | let more = code > 255; 207 | code = self.parents[code]; 208 | more 209 | } {} 210 | enc 211 | } 212 | 213 | fn code_of_unchecked(&self, start: usize) -> u64 { 214 | assert!(start < BUFFER_SIZE); 215 | unsafe { 216 | let mut enc: u64 = 0; 217 | let mut code = start; 218 | while { 219 | enc = (enc << 8) | u64::from(*self.bytes.get_unchecked(code)); 220 | let more = code > 255; 221 | code = *self.parents.get_unchecked(code); 222 | more 223 | } {} 224 | enc 225 | } 226 | } 227 | } 228 | 229 | fn generate_parent_indices() -> impl Iterator { 230 | std::iter::repeat(0) 231 | .take(256) 232 | .chain((0..3).map(|i| std::iter::repeat(i).take(256)).flatten()) 233 | .chain({ 234 | (0..4) 235 | .map(|i| std::iter::repeat(256 + i).take(256)) 236 | .flatten() 237 | }) 238 | .chain({ 239 | (0..8) 240 | .map(|i| std::iter::repeat(1024 + i).take(256)) 241 | .flatten() 242 | }) 243 | } 244 | 245 | fn generate_bytes() -> impl Iterator { 246 | std::iter::repeat((0..=255).into_iter()).flatten() 247 | } 248 | -------------------------------------------------------------------------------- /examples/min-slice.rs: -------------------------------------------------------------------------------- 1 | use index_ext::tag::{Constant, ConstantSource, Slice}; 2 | 3 | // This method accepts only slices at least 10 elements long. 4 | pub fn foo1(a: &mut Slice>) { 5 | let idx = Constant::::EXACT_SIZE.into_len().range_to_self(); 6 | // And can iterate over them without panicking. 7 | for i in a.get_safe_mut(idx) { 8 | *i = 0; 9 | } 10 | } 11 | 12 | // All of the overhead we have since it's not builtin.. 13 | pub struct Min10; 14 | impl ConstantSource for Min10 { 15 | const LEN: usize = 10; 16 | } 17 | 18 | fn main() { 19 | let mut buffer = [0u8; 12]; 20 | let buffer = Slice::new_mut(&mut buffer[..], Constant::::EXACT_SIZE).unwrap(); 21 | foo1(buffer); 22 | } 23 | -------------------------------------------------------------------------------- /release_checks: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | determine_new_version() { 3 | grep "version = " Cargo.toml | sed -Ee 's/version = "(.*)"/\1/' | head -1 4 | } 5 | 6 | determine_crate_name() { 7 | grep "name = " Cargo.toml | sed -Ee 's/name = "(.*)"/\1/' | head -1 8 | } 9 | 10 | check_published_version() { 11 | cirrus_agent="${CIRRUS_CI/?*/(Cirrus-CI)}" 12 | final_agent="Release-Script/1.0 ${cirrus_agent:-(local)} (for $crate) (author:HeroicKatora)" 13 | echo $final_agent 14 | # Does the api information start with: '{"errors":' 15 | [[ $(wget -U "$final_agent" --content-on-error "https://crates.io/api/v1/crates/$crate/$new_version" -qO -) == "{\"errors\":"* ]] || { 16 | published_commit=$(wget -U "$final_agent" "https://crates.io/api/v1/crates/$crate/$new_version/download" -qO - | tar xOzf - "$crate-$new_version/.cargo_vcs_info.json") 17 | published_commit=$(sed 's/\"sha1\": \"\(.*\)\"/\1/;T0;p;:0;d' <<< "$published_commit") 18 | echo "Already published at" "$published_commit" 19 | } 20 | } 21 | 22 | git_considered_clean() { 23 | [[ -z $(git status -s) ]] 24 | } 25 | 26 | count_wip_marker() { 27 | # WIP alone is not a marker 28 | [[ -z $(grep "\[WIP\]" Changes.md Readme.md) ]] 29 | } 30 | 31 | check_release_changes() { 32 | [[ -z $(grep "# v$new_version" Changes.md) ]] 33 | } 34 | 35 | make_git_tag() { 36 | tag_edit_msg="../.git/TAG_MSG_$(uuidgen)" 37 | touch "$tag_edit_msg" 38 | function cleanup() { 39 | rm "$tag_edit_msg" 40 | } 41 | trap cleanup EXIT 42 | # Extract the verion specific section from Changes.md 43 | # Delete lines until $new_version header 44 | # Delete lines starting from the next header 45 | # Delete all empty lines at the start 46 | # Use as the initial message for a signed tag, but open edit anyways 47 | echo $(pwd) 48 | sed -e '0,/'"$new_version"'/d;/\#/,$d;/./,$!d' Changes.md >> "$tag_edit_msg" 49 | echo >> "$tag_edit_msg" 50 | # Sign the package content by including its hash in the tag 51 | sha256sum "../target/package/${crate}-${new_version}.crate" >> "$tag_edit_msg" 52 | git tag -s -F "$tag_edit_msg" -e "${crate}-v${new_version}" $published_commit 53 | } 54 | 55 | is_force="" 56 | do_tag="" 57 | 58 | for param in $@ 59 | do 60 | case "$param" in 61 | -f) is_force="-f";; 62 | --tag) do_tag="yes";; 63 | --help) ;& 64 | -h) { cat << EOF 65 | usage: release [-f] [-h|--help] 66 | 67 | Automates checks and tagging of new releases. Encourages a workflow where 68 | planned changes are integrated into readme and migration documentation early, 69 | with WIP markers to help produce complete logs. 70 | 71 | -f Force usage of version, even if such a tag already exists. 72 | -h, --help Display this help 73 | A semantic version number matching [0-9a-zA-Z.-]* 74 | 75 | EOF 76 | exit 1; } ;; 77 | esac 78 | done 79 | 80 | new_version="$(determine_new_version)" 81 | crate="$(determine_crate_name)" 82 | 83 | # check it is a sane version number 84 | [[ -z $(grep -vE '[0-9a-zA-Z.-]*' <<< "$new_version" ) ]] || { echo "Fail: Check version number: ${new_version}"; exit 1; } 85 | 86 | # check we identified the crate 87 | [[ -z "$crate" ]] && { echo "Couldn't determine crate name"; exit 1; } 88 | 89 | # Check that the working dir is clean. May comment this out if it produces problems. 90 | git_considered_clean || { echo "Fail: Working directory is not clean"; exit 1; } 91 | 92 | # Check that for every author, we have at least name or mail recorded in Contributors.txt 93 | # For each commit author, checks that either his/her name or respective mail 94 | # address appears in the contributors file. Note that a .mailcap file could be 95 | # introduced to canonicalize these names in the output of git-shortlog already. 96 | # Since this needs GNU parallel, the check is optional. 97 | if [[ -f Contributors.txt ]]; then 98 | if which parallel 2>/dev/null; then 99 | { (git shortlog -se | parallel -C '\t|<' grep -Fq -e '{2}' -e '\<{3}' Contributors.txt) || { echo "Fail: contributor not listed"; exit 1; }; } 100 | else 101 | { echo "Checking contributors needs GNU parallel, please make sure manually." 1>&2; } 102 | fi 103 | fi 104 | 105 | check_published_version || { echo "Version $new_version appears already published"; exit 1; } 106 | 107 | # Check there are no more [WIP] markers in Migrate and Readme 108 | count_wip_marker || { echo "Fail: Work in progress in documentation"; exit 1; } 109 | 110 | # Find a matching header in the changelog 111 | check_release_changes && { echo "Fail: No changelog regarding this release"; exit 1; } 112 | 113 | # Packaging works. Note: does not publish the version. 114 | cargo package || { echo "Fail: cargo could not package successfully"; exit 1; } 115 | 116 | [[ -z $do_tag ]] || make_git_tag 117 | -------------------------------------------------------------------------------- /src/array.rs: -------------------------------------------------------------------------------- 1 | //! Index types that produce arrays. 2 | //! 3 | //! The default rust index types, that is `usize` and those constructed with special range syntax, 4 | //! produce slices. That's not necessarily bad as designing them with `const` in mind would have 5 | //! delay their introduction and have problems of coherency. The ergonomic overhead however muddles 6 | //! the intent and requires writing a never failing, but semantically fallible conversion. 7 | //! 8 | //! With the introduction of `const` generic parameters we can design a better solution with which 9 | //! the length of the range is statically determined and which consequently can return a proper 10 | //! array reference instead of a dynamically sized slice. Even better, as this happens in the type 11 | //! system, some parameter values can be deduced where the compiler can match with another array 12 | //! type! 13 | //! 14 | //! Having to decide on the length introduces new complications that are usually put off to the 15 | //! actual execution of indexing. If the given indices are inconsistent, i.e. the end is smaller 16 | //! than the start or the end of an inclusive range is the maximum possible index, then there is no 17 | //! possible type to represent the output. We will not solve this dilemma at the moment, and only 18 | //! define the simple indices which is precisely `RangeTo`. 19 | use core::ops::{Index, IndexMut}; 20 | 21 | /// A marker struct for statically sized range to (`..n`). 22 | /// 23 | /// This can make use of parameter deduction in many cases, for example if assigning the slice to a 24 | /// right hand side array pattern. 25 | /// 26 | /// ``` 27 | /// use index_ext::array::ArrayPrefix; 28 | /// # let buf = &[0u8; 3][..]; 29 | /// let [r, g, b] = buf[ArrayPrefix]; 30 | /// ``` 31 | /// 32 | /// To slice off an array in the middle of a slice, utilize the common pattern of indexing twice. 33 | /// 34 | /// ``` 35 | /// use index_ext::array::ArrayPrefix; 36 | /// # let buf = &[0u8; 3][..]; 37 | /// let [u, v] = buf[1..][ArrayPrefix]; 38 | /// ``` 39 | pub struct ArrayPrefix; 40 | 41 | impl Index> for [T] { 42 | type Output = [T; N]; 43 | fn index(&self, _: ArrayPrefix<{ N }>) -> &[T; N] { 44 | let slice = &self[..N]; 45 | unsafe { 46 | // SAFETY: the layout of slices and arrays of the same length are the same. We'd like 47 | // to use TryFrom/TryInto here but that currently would require the relaxed bounds on 48 | // array impls. 49 | &*(slice.as_ptr() as *const [T; N]) 50 | } 51 | } 52 | } 53 | 54 | impl IndexMut> for [T] { 55 | fn index_mut(&mut self, _: ArrayPrefix<{ N }>) -> &mut [T; N] { 56 | let slice = &mut self[..N]; 57 | unsafe { 58 | // SAFETY: the layout of slices and arrays of the same length are the same. We'd like 59 | // to use TryFrom/TryInto here but that currently would require the relaxed bounds on 60 | // array impls. 61 | &mut *(slice.as_mut_ptr() as *mut [T; N]) 62 | } 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /src/int.rs: -------------------------------------------------------------------------------- 1 | //! Defines wrappers around standard integers to use the as indices. 2 | use core::convert::TryInto; 3 | use core::hint::unreachable_unchecked; 4 | use core::num::TryFromIntError; 5 | /// Defines helper types for more integer indices. 6 | /// 7 | /// There are helpers for adapting indices to implement the standard `ops::Index`/`ops::IndexMut` 8 | /// or the crate-wide [`IntSliceIndex`] respectively. 9 | /// 10 | /// * [`TryIndex`] uses `TryInto` to convert an type into an index, slightly making them 11 | /// more convenient to use where the error conditions have been checked through external means or 12 | /// where such panicking is permissible. 13 | /// * [`Intex`] wraps an implementor of [`IntSliceIndex`] to turn it into an implementor of 14 | /// `ops::Index` and `ops::IndexMut` as well. 15 | /// 16 | /// [`Intex`]: struct.Intex.html 17 | /// [`IntSliceIndex`]: ../trait.IntSliceIndex.html 18 | /// [`TryIndex`]: struct.TryIndex.html 19 | use core::ops::{Range, RangeFrom, RangeTo}; 20 | use core::slice::SliceIndex; 21 | 22 | use self::sealed::{IndexSealed, IntoIntIndex}; 23 | 24 | /// An extension trait allowing slices to be indexed by everything convertible to `usize`. 25 | pub trait SliceIntExt: sealed::SealedSliceIntExt { 26 | /// Return a reference to an element or subslice with an integer index, or `None` if out of 27 | /// bounds. 28 | /// 29 | /// This works like [`slice::get`] but allows arbitrary integers to be used as indices. It will 30 | /// first try to convert them to an `usize`. For some types (`u8` and `u16`) this can never 31 | /// fail while other types may refer to negative indices or are out-of-range. These cases are 32 | /// treated as if the index was out-of-bounds due to the slice being too short. 33 | /// 34 | /// ## Examples 35 | /// 36 | /// ``` 37 | /// # use index_ext::SliceIntExt; 38 | /// let v = [10, 40, 30]; 39 | /// assert_eq!(Some(&40), v.get_int(1u64)); 40 | /// assert_eq!(Some(&[10, 40][..]), v.get_int(0u8..2)); 41 | /// assert_eq!(None, v.get_int(3u8)); 42 | /// assert_eq!(None, v.get_int(0u8..4)); 43 | /// assert_eq!(None, v.get_int(-1i8)); 44 | /// ``` 45 | /// 46 | /// [`slice::get`]: https://doc.rust-lang.org/stable/std/primitive.slice.html#method.get 47 | fn get_int(&self, idx: T) -> Option<&'_ >::Output> 48 | where 49 | T: IntSliceIndex; 50 | 51 | /// Return a mutable reference to an element or subslice with an integer index, or `None` if 52 | /// out of bounds. 53 | /// 54 | /// This works like [`slice::get_mut`]. 55 | /// 56 | /// ## Examples 57 | /// 58 | /// ``` 59 | /// # use index_ext::SliceIntExt; 60 | /// let x = &mut [0, 1, 2]; 61 | /// 62 | /// if let Some(elem) = x.get_int_mut(1u8) { 63 | /// *elem = 42; 64 | /// } 65 | /// assert_eq!(x, &[0, 42, 2]); 66 | /// ``` 67 | /// 68 | /// [`slice::get_mut`]: https://doc.rust-lang.org/stable/std/primitive.slice.html#method.get_mut 69 | fn get_int_mut( 70 | &mut self, 71 | idx: T, 72 | ) -> Option<&'_ mut >::Output> 73 | where 74 | T: IntSliceIndex; 75 | 76 | /// Returns a reference to an element or subslice without doing bounds checking. 77 | /// 78 | /// ## Safety 79 | /// 80 | /// Like [`slice::get_unchecked`], calling this method with an out of bounds index is undefined 81 | /// behaviour. _This includes indices for which conversion to a `usize` fails._ 82 | /// 83 | /// [`slice::get_unchecked`]: https://doc.rust-lang.org/stable/std/primitive.slice.html#method.get_unchecked 84 | /// 85 | /// ## Examples 86 | /// ``` 87 | /// # use index_ext::SliceIntExt; 88 | /// let x = &[1, 2, 4]; 89 | /// 90 | /// unsafe { 91 | /// assert_eq!(x.get_int_unchecked(1i8), &2); 92 | /// } 93 | /// ``` 94 | unsafe fn get_int_unchecked(&self, idx: T) -> &'_ >::Output 95 | where 96 | T: IntSliceIndex; 97 | 98 | /// Returns a mutable reference to an element or subslice without doing bounds checking. 99 | /// 100 | /// ## Safety 101 | /// 102 | /// Like [`slice::get_unchecked_mut`], calling this method with an out of bounds index is undefined 103 | /// behaviour. _This includes indices for which conversion to a `usize` fails._ 104 | /// 105 | /// ## Examples 106 | /// 107 | /// ``` 108 | /// # use index_ext::SliceIntExt; 109 | /// let x = &mut [1, 2, 4]; 110 | /// 111 | /// unsafe { 112 | /// let elem = x.get_int_unchecked_mut(1u64); 113 | /// *elem = 13; 114 | /// } 115 | /// 116 | /// assert_eq!(x, &[1, 13, 4]); 117 | /// ``` 118 | /// 119 | /// [`slice::get_unchecked_mut`]: https://doc.rust-lang.org/stable/std/primitive.slice.html#method.get_unchecked_mut 120 | unsafe fn get_int_unchecked_mut( 121 | &mut self, 122 | idx: T, 123 | ) -> &'_ mut >::Output 124 | where 125 | T: IntSliceIndex; 126 | } 127 | 128 | /// A trait for mathematical integer based indices. 129 | /// 130 | /// Any integer can be used as a fallible index where a machine word can be used by first trying to 131 | /// convert it into a `usize` and then indexing with the original method. From the point of the 132 | /// user, the effect is not much different. If `10usize` is out-of-bounds then so is any other 133 | /// integer representing the number `10`, no matter the allowed magnitude of its type. The same 134 | /// holds for integers that permit negative indices. 135 | /// 136 | /// The output type of the indexing operation is an element or a slice respectively. 137 | /// 138 | /// This trait enables the generic [`SliceIntExt::get_int`] method. 139 | /// 140 | /// [`SliceIntExt::get_int`]: trait.Intex.html#fn.get_int 141 | pub trait IntSliceIndex: sealed::SealedSliceIndex {} 142 | 143 | /// Sealed traits for making `Intex` work as an index, without exposing too much. 144 | /// 145 | /// ## Navigating the jungle of traits 146 | /// The main point here is to properly seal the traits. Parts of this are meant to be adopted by 147 | /// `core` at some point, this prevents some unwanted usage. Also note that user defined types 148 | /// convertible with `TryFromIntError` _should_ require slightly more ceremony. 149 | /// 150 | /// So the `IntSliceIndex` is a parallel of `core::slice::SliceIndex` and inherited from the 151 | /// exposed `crate::IntSliceIndex`. It also contains the interface which we use internally. We 152 | /// can't define unstable methods, and methods would be inherited despite the hidden sealed trait. 153 | /// 154 | /// ``` 155 | /// mod sealed { 156 | /// pub trait Seal { 157 | /// fn can_be_observed(&self); 158 | /// } 159 | /// } 160 | /// 161 | /// trait Public: sealed::Seal {} 162 | /// 163 | /// fn with_public(t: &T) { 164 | /// t.can_be_observed(); 165 | /// } 166 | /// ``` 167 | /// 168 | /// To work around this issue, we use two sealed traits with the same symbols. As neither can be 169 | /// named the necessary disambiguation can not be performed in a downstream crate. 170 | pub(crate) mod sealed { 171 | use core::num::TryFromIntError; 172 | 173 | /// A trait abstracting slice independent index behaviour, `ops::Index` can use on this. 174 | /// 175 | /// This is for two reasons. The panic message is improved by mentioning the original inputs. 176 | /// But this requires the additional bounds of `Copy`, which is not available for `Range` due 177 | /// to historical issues. By not exposing this we can always relax this later when, and if, 178 | /// specialization becomes available to stable Rust. 179 | pub trait IndexSealed { 180 | /// Punts the `Copy` bound to the implementor. 181 | fn copy(&self) -> Self; 182 | #[cold] 183 | fn panic_msg(limit: usize, idx: Self) -> !; 184 | } 185 | 186 | /// Provide one canonical conversion to an index. 187 | /// 188 | /// We use this converter to provide the methods of `IntSliceIndex` in the macro expanded 189 | /// implementation. 190 | pub trait IntoIntIndex { 191 | type IntoIndex; 192 | fn index(self) -> Result; 193 | } 194 | 195 | /// Defines actual indexing on a potentially unsized type. 196 | /// 197 | /// This is sealed as well, as it contains the otherwise exposed `Index` item whose bounds we 198 | /// may later want to adjust. 199 | pub trait IntSliceIndex: Sized { 200 | type Output: ?Sized; 201 | fn get(self, slice: &T) -> Option<&Self::Output>; 202 | fn get_mut(self, slice: &mut T) -> Option<&mut Self::Output>; 203 | unsafe fn get_unchecked(self, slice: &T) -> &Self::Output; 204 | unsafe fn get_unchecked_mut(self, slice: &mut T) -> &mut Self::Output; 205 | fn index(self, slice: &T) -> &Self::Output; 206 | fn index_mut(self, slice: &mut T) -> &mut Self::Output; 207 | } 208 | 209 | /// Seals the `Intex` extension trait. 210 | /// The methods added there are intended to be like inherent methods on the respective 211 | /// implementors which means additional implementors are not intended. 212 | pub trait SealedSliceIntExt {} 213 | 214 | /// Stops downstream from using the `IntSliceIndex` methods and associate type by having a 215 | /// redundant pair of the same definitions. Methods do not have the same result type as this 216 | /// does not influence type deduction and makes it clear that _we_ should never call them. 217 | /// Hence, all methods provided here are actually unreachable. 218 | pub trait SealedSliceIndex: IntSliceIndex { 219 | type Output: ?Sized; 220 | fn get(self, _: &T) -> ! { 221 | unreachable!() 222 | } 223 | fn get_mut(self, _: &mut T) -> ! { 224 | unreachable!() 225 | } 226 | unsafe fn get_unchecked(self, _: &T) -> ! { 227 | unreachable!() 228 | } 229 | unsafe fn get_unchecked_mut(self, _: &mut T) -> ! { 230 | unreachable!() 231 | } 232 | fn index(self, _: &T) -> ! { 233 | unreachable!() 234 | } 235 | fn index_mut(self, _: &mut T) -> ! { 236 | unreachable!() 237 | } 238 | } 239 | 240 | impl> SealedSliceIndex for T { 241 | type Output = >::Output; 242 | } 243 | } 244 | 245 | impl sealed::SealedSliceIntExt for [U] {} 246 | 247 | impl SliceIntExt for [U] { 248 | fn get_int(&self, idx: T) -> Option<&'_ >::Output> 249 | where 250 | T: IntSliceIndex, 251 | { 252 | >::get(idx, self) 253 | } 254 | 255 | fn get_int_mut( 256 | &mut self, 257 | idx: T, 258 | ) -> Option<&'_ mut >::Output> 259 | where 260 | T: IntSliceIndex, 261 | { 262 | >::get_mut(idx, self) 263 | } 264 | 265 | unsafe fn get_int_unchecked(&self, idx: T) -> &'_ >::Output 266 | where 267 | T: IntSliceIndex, 268 | { 269 | // Safety: Propagates the requirements from the caller that this is a valid index. 270 | unsafe { >::get_unchecked(idx, self) } 271 | } 272 | 273 | unsafe fn get_int_unchecked_mut( 274 | &mut self, 275 | idx: T, 276 | ) -> &'_ mut >::Output 277 | where 278 | T: IntSliceIndex, 279 | { 280 | // Safety: Propagates the requirements from the caller that this is a valid index. 281 | unsafe { >::get_unchecked_mut(idx, self) } 282 | } 283 | } 284 | 285 | impl sealed::SealedSliceIntExt for str {} 286 | 287 | impl SliceIntExt for str { 288 | fn get_int(&self, idx: T) -> Option<&'_ >::Output> 289 | where 290 | T: IntSliceIndex, 291 | { 292 | >::get(idx, self) 293 | } 294 | 295 | fn get_int_mut( 296 | &mut self, 297 | idx: T, 298 | ) -> Option<&'_ mut >::Output> 299 | where 300 | T: IntSliceIndex, 301 | { 302 | >::get_mut(idx, self) 303 | } 304 | 305 | unsafe fn get_int_unchecked(&self, idx: T) -> &'_ >::Output 306 | where 307 | T: IntSliceIndex, 308 | { 309 | // Safety: Propagates the requirements from the caller that this is a valid index. 310 | unsafe { >::get_unchecked(idx, self) } 311 | } 312 | 313 | unsafe fn get_int_unchecked_mut( 314 | &mut self, 315 | idx: T, 316 | ) -> &'_ mut >::Output 317 | where 318 | T: IntSliceIndex, 319 | { 320 | // Safety: Propagates the requirements from the caller that this is a valid index. 321 | unsafe { >::get_unchecked_mut(idx, self) } 322 | } 323 | } 324 | 325 | /// An indexing adaptor for `TryInto`. 326 | /// 327 | /// This transparent wrapper allows any type to function as an index as long as it is fallibly 328 | /// convertible to a `usize`. An indexing operation is successful if the conversion succeeds and 329 | /// the resulting index is in bounds. Contrary to the simple integer types, the implementation of 330 | /// `get_unchecked` methods will _not_ unsafely assume that the conversion itself can't fail, only 331 | /// that the resulting index is in-bounds. 332 | /// 333 | /// Separating this from the main `IndexType` solves a coherence problem that would occurs when 334 | /// instantiating it with ranges: The standard library is permitted to add new impls of 335 | /// `TryInto`, for example even for `Range`. Hence, these two impls would overlap 336 | /// but we would like the first to have another return type than the second. The indirection 337 | /// over this type means that our impls are only generic for `TryIndex` instead and do not 338 | /// overlap. 339 | #[repr(transparent)] 340 | #[derive(Clone, Copy, Debug, Default, PartialEq, Eq, PartialOrd, Ord, Hash)] 341 | pub struct TryIndex(pub T); 342 | 343 | impl TryIndex 344 | where 345 | T: TryInto, 346 | T::Error: Into, 347 | { 348 | fn into_int_index(self) -> usize { 349 | match self.0.try_into() { 350 | Ok(idx) => idx, 351 | Err(error) => panic!("Invalid index, {}", error.into()), 352 | } 353 | } 354 | } 355 | 356 | impl IntoIntIndex for TryIndex 357 | where 358 | T: TryInto, 359 | T::Error: Into, 360 | { 361 | type IntoIndex = usize; 362 | fn index(self) -> Result { 363 | self.0.try_into().map_err(Into::into) 364 | } 365 | } 366 | 367 | impl sealed::IntSliceIndex<[U]> for TryIndex 368 | where 369 | T: TryInto, 370 | T::Error: Into, 371 | { 372 | type Output = U; 373 | fn get(self, slice: &[U]) -> Option<&Self::Output> { 374 | match IntoIntIndex::index(self) { 375 | Ok(idx) => slice.get(idx), 376 | Err(_) => None, 377 | } 378 | } 379 | fn get_mut(self, slice: &mut [U]) -> Option<&mut Self::Output> { 380 | match IntoIntIndex::index(self) { 381 | Ok(idx) => slice.get_mut(idx), 382 | Err(_) => None, 383 | } 384 | } 385 | unsafe fn get_unchecked(self, slice: &[U]) -> &Self::Output { 386 | // Explicitly do __NOT__ make the conversion itself unchecked. 387 | let idx = self.into_int_index(); 388 | unsafe { slice.get_unchecked(idx) } 389 | } 390 | unsafe fn get_unchecked_mut(self, slice: &mut [U]) -> &mut Self::Output { 391 | // Explicitly do __NOT__ make the conversion itself unchecked. 392 | let idx = self.into_int_index(); 393 | unsafe { slice.get_unchecked_mut(idx) } 394 | } 395 | fn index(self, slice: &[U]) -> &Self::Output { 396 | &slice[self.into_int_index()] 397 | } 398 | fn index_mut(self, slice: &mut [U]) -> &mut Self::Output { 399 | &mut slice[self.into_int_index()] 400 | } 401 | } 402 | 403 | impl IntSliceIndex<[U]> for TryIndex 404 | where 405 | T: TryInto, 406 | T::Error: Into, 407 | { 408 | } 409 | 410 | impl core::ops::Index> for [U] 411 | where 412 | T: TryInto + IndexSealed, 413 | T::Error: Into, 414 | { 415 | type Output = U; 416 | fn index(&self, idx: TryIndex) -> &U { 417 | sealed::IntSliceIndex::index(idx, self) 418 | } 419 | } 420 | 421 | impl core::ops::IndexMut> for [U] 422 | where 423 | T: TryInto + IndexSealed, 424 | T::Error: Into, 425 | { 426 | fn index_mut(&mut self, idx: TryIndex) -> &mut Self::Output { 427 | sealed::IntSliceIndex::index_mut(idx, self) 428 | } 429 | } 430 | 431 | /// An adaptor for `ops::Index` that uses this crate's `IntSliceIndex` instead of the standard one. 432 | /// 433 | /// This struct can be used to index a slice with an arbitrary integer type, using the standard 434 | /// indexing syntax. It is also constructed by the [`Intex`] method exported in the crate root. The 435 | /// indexing operation will first try to convert the number of a `usize` index and then do the 436 | /// usual indexing. 437 | /// 438 | /// [`Intex`]: ../fn.Intex.html 439 | /// 440 | /// ```rust 441 | /// use index_ext::int::Intex; 442 | /// let val = [0u8; 2][Intex(1u32)]; 443 | /// ``` 444 | /// 445 | /// This is a transparent wrapper. 446 | #[repr(transparent)] 447 | #[derive(Clone, Copy, Debug, Default, PartialEq, Eq, PartialOrd, Ord, Hash)] 448 | pub struct Intex(pub T); 449 | 450 | impl core::ops::Index> for [U] 451 | where 452 | T: IntSliceIndex<[U]> + IndexSealed, 453 | { 454 | type Output = >::Output; 455 | 456 | fn index(&self, idx: Intex) -> &Self::Output { 457 | >::index(idx.0, self) 458 | } 459 | } 460 | 461 | impl core::ops::IndexMut> for [U] 462 | where 463 | T: IntSliceIndex<[U]> + IndexSealed, 464 | { 465 | fn index_mut(&mut self, idx: Intex) -> &mut Self::Output { 466 | >::index_mut(idx.0, self) 467 | } 468 | } 469 | 470 | // Core implementations for the basede types. We implement the `IntoIntIndex` trait for generic 471 | // types so we can reuse them in the concrete macro-derived impls of the sealed IntSliceIndex 472 | // trait. 473 | 474 | impl> sealed::IntoIntIndex for Range 475 | where 476 | TryFromIntError: From, 477 | { 478 | type IntoIndex = Range; 479 | fn index(self) -> Result, TryFromIntError> { 480 | let Range { start, end } = self; 481 | let start: usize = start.try_into()?; 482 | let end: usize = end.try_into()?; 483 | Ok(start..end) 484 | } 485 | } 486 | 487 | impl> sealed::IntoIntIndex for RangeTo 488 | where 489 | TryFromIntError: From, 490 | { 491 | type IntoIndex = RangeTo; 492 | fn index(self) -> Result, TryFromIntError> { 493 | let end: usize = self.end.try_into()?; 494 | Ok(..end) 495 | } 496 | } 497 | 498 | impl> sealed::IntoIntIndex for RangeFrom 499 | where 500 | TryFromIntError: From, 501 | { 502 | type IntoIndex = RangeFrom; 503 | fn index(self) -> Result, TryFromIntError> { 504 | let start: usize = self.start.try_into()?; 505 | Ok(start..) 506 | } 507 | } 508 | 509 | macro_rules! slice_index { 510 | ($($t:ty),*) => { 511 | $(slice_index!(@$t);)* 512 | }; 513 | (@IntSliceIndex<[U]> for $t:ty: with IntoIntIndex) => { 514 | impl sealed::IntSliceIndex<[U]> for $t { 515 | type Output = <::IntoIndex as SliceIndex<[U]>>::Output; 516 | fn get(self, slice: &[U]) -> Option<&Self::Output> { 517 | match IntoIntIndex::index(self) { 518 | Ok(idx) => slice.get(idx), 519 | Err(_) => None, 520 | } 521 | } 522 | fn get_mut(self, slice: &mut [U]) -> Option<&mut Self::Output> { 523 | match IntoIntIndex::index(self) { 524 | Ok(idx) => slice.get_mut(idx), 525 | Err(_) => None, 526 | } 527 | } 528 | unsafe fn get_unchecked(self, slice: &[U]) -> &Self::Output { 529 | match IntoIntIndex::index(self) { 530 | // Safety: the caller promises that the index is valid. 531 | Ok(idx) => unsafe { slice.get_unchecked(idx) }, 532 | // Safety: the caller promises that the index is valid. 533 | // This implies that it is in-bounds of the `usize` type. 534 | Err(_) => unsafe { unreachable_unchecked() }, 535 | } 536 | } 537 | unsafe fn get_unchecked_mut(self, slice: &mut [U]) -> &mut Self::Output { 538 | match IntoIntIndex::index(self) { 539 | // Safety: the caller promises that the index is valid. 540 | Ok(idx) => unsafe { slice.get_unchecked_mut(idx) }, 541 | // Safety: the caller promises that the index is valid. 542 | // This implies that it is in-bounds of the `usize` type. 543 | Err(_) => unsafe { unreachable_unchecked() }, 544 | } 545 | } 546 | fn index(self, slice: &[U]) -> &Self::Output { 547 | match sealed::IntSliceIndex::get(IndexSealed::copy(&self), slice) { 548 | Some(output) => output, 549 | None => IndexSealed::panic_msg(slice.len(), self), 550 | } 551 | } 552 | fn index_mut(self, slice: &mut [U]) -> &mut Self::Output { 553 | let len = slice.len(); 554 | match sealed::IntSliceIndex::get_mut(IndexSealed::copy(&self), slice) { 555 | Some(output) => output, 556 | None => IndexSealed::panic_msg(len, self), 557 | } 558 | } 559 | } 560 | }; 561 | (@IntSliceIndex for $t:ty: with IntoIntIndex) => { 562 | impl sealed::IntSliceIndex for $t { 563 | type Output = <::IntoIndex as SliceIndex>::Output; 564 | fn get(self, slice: &str) -> Option<&Self::Output> { 565 | match IntoIntIndex::index(self) { 566 | Ok(idx) => slice.get(idx), 567 | Err(_) => None, 568 | } 569 | } 570 | fn get_mut(self, slice: &mut str) -> Option<&mut Self::Output> { 571 | match IntoIntIndex::index(self) { 572 | Ok(idx) => slice.get_mut(idx), 573 | Err(_) => None, 574 | } 575 | } 576 | unsafe fn get_unchecked(self, slice: &str) -> &Self::Output { 577 | match IntoIntIndex::index(self) { 578 | // Safety: the caller promises that the index is valid. 579 | Ok(idx) => unsafe { slice.get_unchecked(idx) }, 580 | // Safety: the caller promises that the index is valid. 581 | // This implies that it is in-bounds of the `usize` type. 582 | Err(_) => unsafe { unreachable_unchecked() }, 583 | } 584 | } 585 | unsafe fn get_unchecked_mut(self, slice: &mut str) -> &mut Self::Output { 586 | match IntoIntIndex::index(self) { 587 | // Safety: the caller promises that the index is valid. 588 | Ok(idx) => unsafe { slice.get_unchecked_mut(idx) }, 589 | // Safety: the caller promises that the index is valid. 590 | // This implies that it is in-bounds of the `usize` type. 591 | Err(_) => unsafe { unreachable_unchecked() }, 592 | } 593 | } 594 | fn index(self, slice: &str) -> &Self::Output { 595 | match sealed::IntSliceIndex::get(IndexSealed::copy(&self), slice) { 596 | Some(output) => output, 597 | None => IndexSealed::panic_msg(slice.len(), self), 598 | } 599 | } 600 | fn index_mut(self, slice: &mut str) -> &mut Self::Output { 601 | let len = slice.len(); 602 | match sealed::IntSliceIndex::get_mut(IndexSealed::copy(&self), slice) { 603 | Some(output) => output, 604 | None => IndexSealed::panic_msg(len, self), 605 | } 606 | } 607 | } 608 | }; 609 | (@$t:ty) => { 610 | impl sealed::IntoIntIndex for $t { 611 | type IntoIndex = usize; 612 | // Clippy warns within here: 613 | // > warning: question mark operator is useless here 614 | // 615 | // This is a 'false-positive' since this question mark not useless in all macro invocation. 616 | // For `$t = usize` we have `Err=Infallible` instead. 617 | #[allow(clippy::needless_question_mark)] 618 | fn index(self) -> Result { 619 | Ok(self.try_into()?) 620 | } 621 | } 622 | 623 | impl sealed::IndexSealed for $t { 624 | #[inline(always)] 625 | fn copy(&self) -> Self { *self } 626 | #[cold] 627 | fn panic_msg(len: usize, index: Self) -> ! { 628 | panic!("index {} out of range for slice of length {}", index, len) 629 | } 630 | } 631 | 632 | impl sealed::IndexSealed for Range<$t> { 633 | #[inline(always)] 634 | fn copy(&self) -> Self { Range { .. *self } } 635 | #[cold] 636 | fn panic_msg(len: usize, index: Self) -> ! { 637 | panic!("index {} out of range for slice of length {}", index.end, len) 638 | } 639 | } 640 | 641 | impl sealed::IndexSealed for RangeFrom<$t> { 642 | #[inline(always)] 643 | fn copy(&self) -> Self { RangeFrom { .. *self } } 644 | #[cold] 645 | fn panic_msg(len: usize, index: Self) -> ! { 646 | panic!("index {} out of range for slice of length {}", index.start, len) 647 | } 648 | } 649 | 650 | impl sealed::IndexSealed for RangeTo<$t> { 651 | #[inline(always)] 652 | fn copy(&self) -> Self { RangeTo { .. *self } } 653 | #[cold] 654 | fn panic_msg(len: usize, index: Self) -> ! { 655 | panic!("index {} out of range for slice of length {}", index.end, len) 656 | } 657 | } 658 | 659 | slice_index!(@IntSliceIndex<[U]> for $t: with IntoIntIndex); 660 | slice_index!(@IntSliceIndex<[U]> for Range<$t>: with IntoIntIndex); 661 | slice_index!(@IntSliceIndex<[U]> for RangeTo<$t>: with IntoIntIndex); 662 | slice_index!(@IntSliceIndex<[U]> for RangeFrom<$t>: with IntoIntIndex); 663 | slice_index!(@IntSliceIndex for Range<$t>: with IntoIntIndex); 664 | slice_index!(@IntSliceIndex for RangeTo<$t>: with IntoIntIndex); 665 | slice_index!(@IntSliceIndex for RangeFrom<$t>: with IntoIntIndex); 666 | 667 | impl IntSliceIndex<[U]> for $t {} 668 | impl IntSliceIndex<[U]> for Range<$t> {} 669 | impl IntSliceIndex<[U]> for RangeTo<$t> {} 670 | impl IntSliceIndex<[U]> for RangeFrom<$t> {} 671 | 672 | impl IntSliceIndex for Range<$t> {} 673 | impl IntSliceIndex for RangeTo<$t> {} 674 | impl IntSliceIndex for RangeFrom<$t> {} 675 | } } 676 | 677 | slice_index!(u8, i8, u16, i16, u32, i32, u64, i64, u128, i128, usize, isize); 678 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | //! Adds more index types, improving correctness and clarifying intent. 2 | //! 3 | //! The crate is separated by modules which each roughly explore one particular index-related idea. 4 | //! As an overview, the main modules are: 5 | //! 6 | //! - In the [`mod@array`] module, a solution for repetitive try-into-unwrapping in the process of 7 | //! interpreting slices as fixed-width arrays is presented. 8 | //! - In the [`int`] module, we explore numerical types with fallible conversion to indices on use, 9 | //! interpreting a failure as out-of-bounds. 10 | //! - In the [`mem`] module, the efficient representation for the type intersection between 11 | //! integers and pointer sized indices is presented as a set of concrete types. 12 | //! - In the [`tag`] module, we apply fancy type-level mechanisms to move the bounds check inherent 13 | //! in safe slice accesses from the usage site to the construction of the index. See in 14 | //! particular the Huffman example that demonstrates the optimization potential. 15 | //! 16 | //! Read the [`readme`] for some examples. In short: 17 | //! 18 | //! ``` 19 | //! use index_ext::{Intex, SliceIntExt}; 20 | //! 21 | //! let fine = [0u8; 2][Intex(1u32)]; 22 | //! let also = [0u8; 2][Intex(1u128)]; 23 | //! 24 | //! assert_eq!([0u8; 2].get_int(u128::max_value()), None); 25 | //! ``` 26 | //! 27 | //! ## Unfinished features 28 | //! 29 | //! The marker WIP means it is worked on, Planned that it might be worked on due to intrigue of the 30 | //! author, and Idea itself is still unevaluated (looking for a usecase, for instance). 31 | //! 32 | //! `Planned`: An index type `CharAt(n: usize)` that dereferences to the characters of a string 33 | //! _around_ a particular position, represented by a string wrapper that allows converting into a 34 | //! `char`. In contrast to the standard operator, this would only panic for out-of-bounds 35 | //! coordinates and thus match the main expectation with regards to `len`. 36 | //! 37 | //! `Idea`: Note that a generic `PrefixChars`, retrieving the first N characters, would 38 | //! not be constant time which may be surprising if used in index position. 39 | //! 40 | //! `Idea`: An index type `InsertWith` for `HashMap` and `BTreeMap` that will construct an 41 | //! element when an entry is missing, similar to C++, and thus be a panic free alternative. _Maybe_ 42 | //! we could index a `Vec<_>` with this type as well, extending as necessary, but this would again 43 | //! not be constant time. The problem here is the super trait relationship `IndexMut: Index` which 44 | //! might lead to many potential misuses. Also the common alternative of `entry.or_insert` is both 45 | //! simple an robust already. 46 | //! 47 | //! `Idea`: An adapter `OrEmpty` that uses `get` internally and substitutes an empty slice instead 48 | //! of panicking. Now this is maybe both clear and cute, I'd actually see some use here. It's 49 | //! unclear for which types to provide it and if there's a SemVer risk. 50 | //! 51 | //! ## Design notes 52 | //! 53 | //! The extension traits offered here have a slight ergonomic problem compared to being included in 54 | //! the standard library. Its `ops::Index` impls on slices are provided by the `SliceIndex` trait. 55 | //! Since this is a nightly trait _and_ sealed by design we can not use it. However, we also can 56 | //! not use a generic impl for all `T: crate::SliceIndex<[U]>` as this is forbidden by coherence 57 | //! rules for foreign types. We thus utilize two kinds of indexing: Implement the Index trait 58 | //! directly for all concrete applicable types and provide a single newtype which acts as a proxy 59 | //! for the otherwise unconstrained type parameter of the generic impl. If the types were added to 60 | //! `core` then this indirection would not be necessary and ergonomics would improve. 61 | #![no_std] 62 | #![deny(clippy::missing_safety_doc)] 63 | #![deny(missing_docs)] 64 | #![deny(unsafe_op_in_unsafe_fn)] 65 | 66 | #[cfg(feature = "alloc")] 67 | extern crate alloc; 68 | 69 | pub mod array; 70 | pub mod int; 71 | pub mod mem; 72 | pub mod tag; 73 | 74 | pub use array::ArrayPrefix; 75 | pub use int::SliceIntExt; 76 | 77 | /// Convert an arbitrary integer into an index. 78 | /// 79 | /// This method simply constructs an inner transparent wrapper struct `Intex` but can be used as an 80 | /// alternative which is imported with the same name, and at the same time, as the trait. 81 | #[allow(non_snake_case)] 82 | pub fn Intex(idx: T) -> int::Intex { 83 | int::Intex(idx) 84 | } 85 | 86 | #[cfg(doc)] 87 | macro_rules! doctest_readme { 88 | { $content:expr } => { 89 | /// A rendered version of the Readme file, documentation purpose only. 90 | /// 91 | #[doc = $content] pub mod readme {} 92 | } 93 | } 94 | 95 | #[cfg(doc)] 96 | doctest_readme!(include_str!("../Readme.md")); 97 | 98 | #[cfg(test)] 99 | mod test { 100 | use super::{Intex, SliceIntExt}; 101 | 102 | #[test] 103 | #[should_panic = "100"] 104 | fn panics_with_length_u32() { 105 | [0u8; 0][Intex(100u32)]; 106 | } 107 | 108 | #[test] 109 | #[should_panic = "100"] 110 | fn panics_with_length_u8() { 111 | [0u8; 0][Intex(100u8)]; 112 | } 113 | 114 | #[test] 115 | #[should_panic = "-1"] 116 | fn panics_with_length_i8() { 117 | [0u8; 0][Intex(-1i8)]; 118 | } 119 | 120 | #[test] 121 | #[should_panic = "100000000000000000000000000000000000000"] 122 | fn panics_with_length_u128() { 123 | [0u8; 0][Intex(100_000_000_000_000_000_000_000_000_000_000_000_000u128)]; 124 | } 125 | 126 | #[test] 127 | fn index_with_all() { 128 | let slice = [0u8; 10]; 129 | macro_rules! assert_slice_success { 130 | (@$slice:path, $exp:expr) => { 131 | assert!($slice.get_int($exp).is_some()); 132 | }; 133 | ($slice:path: $($exp:expr),*) => { 134 | $(assert_slice_success!(@$slice, $exp);)* 135 | } 136 | } 137 | 138 | macro_rules! assert_slice_fail { 139 | (@$slice:path, $exp:expr) => { 140 | assert_eq!($slice.get_int($exp), None); 141 | }; 142 | ($slice:path: $($exp:expr),*) => { 143 | $(assert_slice_fail!(@$slice, $exp);)* 144 | } 145 | } 146 | 147 | assert_slice_success!(slice: 0u8, 0i8, 0u16, 0i16, 0u32, 0i32, 0u64, 0i64); 148 | assert_slice_success!(slice: ..10u8, ..10i8, ..10u16, ..10i16, ..10u32, ..10i32, ..10u64, ..10i64); 149 | assert_slice_success!(slice: 0..10u8, 0..10i8, 0..10u16, 0..10i16, 0..10u32, 0..10i32, 0..10u64, 0..10i64); 150 | assert_slice_success!(slice: 10u8.., 10i8.., 10u16.., 10i16.., 10u32.., 10i32.., 10u64.., 10i64..); 151 | 152 | assert_slice_fail!(slice: -1i8, -1i16, -1i32, -1i64); 153 | } 154 | 155 | #[test] 156 | fn unchecked() { 157 | let mut slice = [0u8, 1, 2, 3]; 158 | macro_rules! assert_slice_eq { 159 | (@$slice:path, $idx:expr, $exp:expr) => { 160 | assert_eq!($slice[Intex($idx)], $exp); 161 | assert_eq!(&mut $slice[Intex($idx)], $exp); 162 | 163 | unsafe { 164 | assert_eq!($slice.get_int_unchecked($idx), $exp); 165 | assert_eq!($slice.get_int_unchecked_mut($idx), $exp); 166 | } 167 | }; 168 | ($slice:path[idx], $result:expr, for idx in [$($idx:expr),*]) => { 169 | $(assert_slice_eq!(@$slice, $idx, $result);)* 170 | } 171 | } 172 | 173 | assert_slice_eq!(slice[idx], [1, 2], 174 | for idx in [1u8..3, 1i8..3, 1u16..3, 1i16..3, 1u32..3, 1i32..3, 1u64..3, 1i64..3]); 175 | } 176 | 177 | #[test] 178 | fn str_indices() { 179 | let text = "What if ascii still has it?"; 180 | assert_eq!(text.get_int(8u8..13), Some("ascii")); 181 | } 182 | } 183 | -------------------------------------------------------------------------------- /src/mem.rs: -------------------------------------------------------------------------------- 1 | //! Integer wrappers that are constrained to the range of `usize` or `isize`. 2 | //! 3 | //! This module defines a number of wrappers around underlying basic integer types which ensure 4 | //! that the value fits both within `usize` (respectively `isize`) as well as the basic integer 5 | //! type, without loss of data. 6 | //! 7 | //! Additionally, the types are optimized for size. That is, the types occupy the minimum size of 8 | //! both the underlying integer and the respective pointer sized alternative. This ensure that 9 | //! platform-compatible code is easier to write without wasting memory for the data representation 10 | //! in the process. 11 | //! 12 | //! # Usage 13 | //! 14 | //! Imagine some interface defining indices to be in the range of a `u64`. A 32-bit platform 15 | //! implementing this interface may be torn between using `u64` for the intent, as well as its own 16 | //! `isize` for the smaller representation. The corresponding type from this module can combine 17 | //! both properties. A demonstration by types beyond the size of most platforms: 18 | //! 19 | //! ``` 20 | //! use core::mem::size_of; 21 | //! use index_ext::mem::Imem128; 22 | //! 23 | //! assert!(size_of::() <= size_of::()); 24 | //! assert!(size_of::() <= size_of::()); 25 | //! ``` 26 | //! 27 | //! Keep in mind the types are most useful for representing values in and relative to your own 28 | //! program's memory. In the interface you would generally prefer to declare argument as the 29 | //! specified platform independent underlying integer (e.g. `u64` above). In these cases, there is 30 | //! however a security risk associated with delaying the (fallible) conversions to your platform's 31 | //! capabilities. Firstly, implicit loss of precision when using `as` may occur on some 32 | //! distributions but not others which may result in incomplete test coverage missing a bug. 33 | //! Secondly, utilizing fallible conversion at the site of use creates many error paths. You might 34 | //! prefer converting to `Umem64` immediately. 35 | //! 36 | //! Consider the case of a matrix where dimensions are stored as `u32` for compatibility reasons. 37 | //! We would now like to allocate a buffer for it which requires calculating the number of elements 38 | //! as a `u64` and then convert to `usize`. However, no matter in which number type you intend to 39 | //! store the result you lose semantic meaning because there is no 'proof' attached to the value 40 | //! that it will also fit into the other value range. 41 | //! 42 | //! ``` 43 | //! use index_ext::mem::Umem64; 44 | //! 45 | //! struct Matrix { 46 | //! width: u32, 47 | //! height: u32, 48 | //! } 49 | //! 50 | //! # fn fake() -> Option<()> { 51 | //! # let mat = Matrix { width: 0, height: 0 }; 52 | //! let elements = u64::from(mat.width) * u64::from(mat.height); 53 | //! let length: Umem64 = Umem64::new(elements)?; 54 | //! 55 | //! let matrix = vec![0; length.get()]; 56 | //! # Some(()) } 57 | //! ``` 58 | struct Choice; 59 | 60 | trait Impl { 61 | type Inner; 62 | } 63 | 64 | macro_rules! lossless_integer { 65 | ( 66 | $(#[$attr:meta])* 67 | $sizet:ident $str_name:literal 68 | pub struct $name:ident ($under:ty) 69 | ) => { 70 | impl Impl<$name> for Choice { 71 | type Inner = $under; 72 | } 73 | impl Impl<$name> for Choice { 74 | type Inner = $sizet; 75 | } 76 | 77 | $(#[$attr])* 78 | #[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] 79 | #[repr(transparent)] 80 | pub struct $name( 81 | < 82 | Choice<{core::mem::size_of::<$under>() < core::mem::size_of::<$sizet>()}> 83 | as Impl<$name> 84 | >::Inner 85 | ); 86 | 87 | impl $name { 88 | /// Wrap an integer if its value can be losslessly converted to ` 89 | #[doc = $str_name] 90 | /// `. 91 | pub fn new(val: $under) -> Option { 92 | if <$sizet as core::convert::TryFrom<_>>::try_from(val).is_ok() { 93 | // Potentially no-op, potentially a cast. The macro and type deduction makes 94 | // sure either $under or $sizet is utilized as the result. Both are correct by 95 | // the input argument and the `try_from`. 96 | Some($name(val as _)) 97 | } else { 98 | None 99 | } 100 | } 101 | 102 | /// Get the ` 103 | #[doc = $str_name] 104 | /// ` value of the integer. 105 | pub fn get(self) -> $sizet { 106 | self.0 as $sizet 107 | } 108 | 109 | /// Get the inner value in the original type. 110 | pub fn into_inner(self) -> $under { 111 | self.0 as $under 112 | } 113 | } 114 | 115 | impl From<$name> for $sizet { 116 | fn from(val: $name) -> $sizet { 117 | val.get() 118 | } 119 | } 120 | 121 | impl From<$name> for $under { 122 | fn from(val: $name) -> $under { 123 | val.into_inner() 124 | } 125 | } 126 | 127 | // Note: clippy says implementing `ne` is not necessary. We'll see about that if any 128 | // performance complaints reach the repository. 129 | impl PartialEq<$under> for $name { 130 | fn eq(&self, other: &$under) -> bool { 131 | self.into_inner() == *other 132 | } 133 | } 134 | impl PartialEq<$name> for $under { 135 | fn eq(&self, other: &$name) -> bool { 136 | *self == other.into_inner() 137 | } 138 | } 139 | 140 | impl PartialEq<$sizet> for $name { 141 | fn eq(&self, other: &$sizet) -> bool { 142 | self.get() == *other 143 | } 144 | } 145 | impl PartialEq<$name> for $sizet { 146 | fn eq(&self, other: &$name) -> bool { 147 | *self == other.get() 148 | } 149 | } 150 | 151 | impl PartialOrd<$under> for $name { 152 | fn partial_cmp(&self, other: &$under) -> Option { 153 | self.into_inner().partial_cmp(other) 154 | } 155 | } 156 | impl PartialOrd<$name> for $under { 157 | fn partial_cmp(&self, other: &$name) -> Option { 158 | self.partial_cmp(&other.into_inner()) 159 | } 160 | } 161 | 162 | impl PartialOrd<$sizet> for $name { 163 | fn partial_cmp(&self, other: &$sizet) -> Option { 164 | self.get().partial_cmp(other) 165 | } 166 | } 167 | impl PartialOrd<$name> for $sizet { 168 | fn partial_cmp(&self, other: &$name) -> Option { 169 | self.partial_cmp(&other.get()) 170 | } 171 | } 172 | 173 | impl core::fmt::Display for $name { 174 | fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result { 175 | // Uses the pointer type, avoiding any non-register-sized arguments to display and 176 | // code gen for that. This one is more likely to be needed already. 177 | self.get().fmt(f) 178 | } 179 | } 180 | }; 181 | } 182 | 183 | macro_rules! integer_mem { 184 | ($(#[$attr:meta])* pub struct $name:ident ($under:ty)) => { 185 | lossless_integer!($(#[$attr])* 186 | usize "usize" 187 | pub struct $name ($under)); 188 | 189 | impl core::ops::Index<$name> for [T] { 190 | type Output = T; 191 | fn index(&self, idx: $name) -> &Self::Output { 192 | &self[idx.get()] 193 | } 194 | } 195 | } 196 | } 197 | 198 | macro_rules! integer_diff { 199 | ($(#[$attr:meta])* pub struct $name:ident ($under:ty)) => { 200 | lossless_integer!($(#[$attr])* 201 | isize "isize" 202 | pub struct $name ($under)); 203 | } 204 | } 205 | 206 | integer_mem!( 207 | /// An i8 that is also in the value range of a usize. 208 | pub struct Imem8(i8) 209 | ); 210 | 211 | integer_mem!( 212 | /// A u8 that is also in the value range of a usize. 213 | pub struct Umem8(u8) 214 | ); 215 | 216 | integer_mem!( 217 | /// An i16 that is also in the value range of a usize. 218 | pub struct Imem16(i16) 219 | ); 220 | 221 | integer_mem!( 222 | /// A u16 that is also in the value range of a usize. 223 | pub struct Umem16(u16) 224 | ); 225 | 226 | integer_mem!( 227 | /// An i32 that is also in the value range of a usize. 228 | pub struct Imem32(i32) 229 | ); 230 | 231 | integer_mem!( 232 | /// A u32 that is also in the value range of a usize. 233 | pub struct Umem32(u32) 234 | ); 235 | 236 | integer_mem!( 237 | /// An i64 that is also in the value range of a usize. 238 | pub struct Imem64(i64) 239 | ); 240 | 241 | integer_mem!( 242 | /// A u64 that is also in the value range of a usize. 243 | pub struct Umem64(u64) 244 | ); 245 | 246 | integer_mem!( 247 | /// An i128 that is also in the value range of a usize. 248 | pub struct Imem128(i128) 249 | ); 250 | 251 | integer_mem!( 252 | /// A u128 that is also in the value range of a usize. 253 | pub struct Umem128(u128) 254 | ); 255 | 256 | integer_mem!( 257 | /// An isize that is also in the value range of a usize. 258 | pub struct Imem(isize) 259 | ); 260 | 261 | integer_diff!( 262 | /// An i8 that is also in the value range of an isize. 263 | pub struct Idiff8(i8) 264 | ); 265 | 266 | integer_diff!( 267 | /// A u8 that is also in the value range of an isize. 268 | pub struct Udiff8(u8) 269 | ); 270 | 271 | integer_diff!( 272 | /// An i16 that is also in the value range of an isize. 273 | pub struct Idiff16(i16) 274 | ); 275 | 276 | integer_diff!( 277 | /// A u16 that is also in the value range of an isize. 278 | pub struct Udiff16(u16) 279 | ); 280 | 281 | integer_diff!( 282 | /// An i32 that is also in the value range of an isize. 283 | pub struct Idiff32(i32) 284 | ); 285 | 286 | integer_diff!( 287 | /// A u32 that is also in the value range of an isize. 288 | pub struct Udiff32(u32) 289 | ); 290 | 291 | integer_diff!( 292 | /// An i64 that is also in the value range of an isize. 293 | pub struct Idiff64(i64) 294 | ); 295 | 296 | integer_diff!( 297 | /// A u64 that is also in the value range of an isize. 298 | pub struct Udiff64(u64) 299 | ); 300 | 301 | integer_diff!( 302 | /// A usize that is also in the value range of an isize. 303 | pub struct Udiff(usize) 304 | ); 305 | 306 | integer_diff!( 307 | /// A i128 that is also in the value range of an isize. 308 | pub struct Idiff128(i128) 309 | ); 310 | 311 | integer_diff!( 312 | /// A u128 that is also in the value range of an isize. 313 | pub struct Udiff128(u128) 314 | ); 315 | 316 | #[test] 317 | fn mem_128_operations() { 318 | let x = Umem128::new(16).unwrap(); 319 | // Test: refl-`Eq`. 320 | assert!(x == x); 321 | // Test: refl-`Ord`. 322 | assert!(x <= x); 323 | 324 | // Test: `Ord` for underlying type. 325 | assert!(x <= 16u128); 326 | assert!(16u128 <= x); 327 | // Test: `Eq` for underlying type. 328 | assert!(x == 16u128); 329 | assert!(16u128 == x); 330 | 331 | // Test: `Ord` for usize. 332 | assert!(x <= 16usize); 333 | assert!(16usize <= x); 334 | // Test: `Eq` for usize. 335 | assert!(x == 16usize); 336 | assert!(16usize == x); 337 | } 338 | 339 | #[cfg(feature = "alloc")] 340 | #[test] 341 | fn fmt_128() { 342 | let x = Umem128::new(16).unwrap(); 343 | assert!(alloc::format!("{x}") == "16"); 344 | } 345 | -------------------------------------------------------------------------------- /src/readme.rs: -------------------------------------------------------------------------------- 1 | //! A rendered version of the Readme file, documentation purpose only. 2 | //! 3 | doctest_readme!(include_str!("../Readme.md")); 4 | -------------------------------------------------------------------------------- /src/tag.rs: -------------------------------------------------------------------------------- 1 | //! Statically elide bounds checks with the type system. 2 | //! 3 | //! ## Rough mechanism 4 | //! 5 | //! The main idea is to use types as a compile time tag to identify a particular length of a slice 6 | //! without storing this bound in the instances associated with the constraint. For this, we 7 | //! utilize strategies such as [generativity] or concrete compile time tags. 8 | //! 9 | //! [generativity]: https://docs.rs/generativity/latest/generativity/ 10 | //! 11 | //! This allows us to construct wrapper types for indices and slices that guarantees its accesses 12 | //! to be in-bounds when combined with any other value with the same tag. Critically, these indices 13 | //! and slices can be stored and acted on independent of each other and without introducing borrow 14 | //! coupling between them. The type system _guarantees_ a bounds-check free code path for indexing 15 | //! into the slices by adding an indexing operator which unconditionally performs the unsafe 16 | //! access. 17 | //! 18 | //! This works particularly well for programs with fixed size buffers, i.e. kernels, bootloaders, 19 | //! embedded, high-assurance programs. If you encapsulate the `ExactSize` instance containing the 20 | //! authoritative source of the associated length then you can have a very high confidence in have 21 | //! ran appropriate access and bounds checks before accesses. 22 | //! 23 | //! ## Built-in Tag types 24 | //! 25 | //! There are a couple of different methods for creating tag types with such associations: 26 | //! 27 | //! 1. As a compile time constant. The [`Constant`] and [`Const`] offer different ways of defining 28 | //! the associated length as a fixed number. The former let's you give it a type as a name while 29 | //! the latter is based on generic parameters. 30 | //! 31 | //! 2. The generative way. The [`Generative`] type is unique for every created instance, by having 32 | //! a unique lifetime parameter. That is, you can not choose its lifetime parameters freely. 33 | //! Instead, to create an instance you write a function prepared to handle arbitrary lifetime 34 | //! and the library hands an instance to you. This makes the exact lifetime opaque to you and 35 | //! the compiler which forces all non-local code to assume that it is indeed unique and it can 36 | //! not be unified with any other. We associate such a [`Generative`] instance with the length 37 | //! of the one slice provided during its construction. 38 | //! 39 | //! 3. And finally one might come up with an internal naming scheme where types are used to express 40 | //! unique bounds. This requires some unsafe code and the programmers guarantee of uniqueness of 41 | //! values but permits the combination of runtime values with `'static` lifetime of the tag. 42 | //! 43 | //! Each tag relates to _one_ specific length of slices, without encoding that value itself into 44 | //! values but rather this is a 'hidden' variable. Usage of the tag guarantees a separation between 45 | //! slices associated with the tag and all indices. This allows transitive reasoning: All [`Slice`] 46 | //! with that exact same tag are at least as long as all the sizes in [`Prefix`] structs of the 47 | //! same lifetime and strictly for [`Idx`]. Note that it even allows passing information on a 48 | //! slice's length across function boundaries by explicitly mentioning the same tag such as a 49 | //! generative lifetime twice. 50 | //! 51 | //! Use [`with_ref`] and [`with_mut`] as main entry functions or one the constructors on the type 52 | //! [`Generative`]. Note the interaction with the [`generativity`] crate which provides a macro that 53 | //! doesn't influence code flow, instead of requiring a closure wrapper like the methods given in 54 | //! this crate. 55 | //! 56 | //! [`generativity`]: https://docs.rs/generativity/ 57 | //! 58 | //! [`with_ref`]: fn.with_ref.html 59 | //! [`with_mut`]: fn.with_mut.html 60 | //! 61 | //! The interface also permits some mutable operations on indices. These operations are restricted 62 | //! to some that can not make the new value exceed the bound of the hidden variable (such as: 63 | //! monotonically decreasing operations). 64 | //! 65 | //! The module even provides an 'algebra' for type level tags themselves. you can dynamically prove 66 | //! two tags to be equivalent, comparable, etc, by asserting that of the _hidden_ variables 67 | //! associated with each tag. This does not necessarily require the hidden value to be concrete but 68 | //! it is most powerful when you locally haven an [`ExactSize`] representing that hidden value. 69 | //! Then you can leverage these facts (which are also encoded as zero-sized types) to substitute 70 | //! tags in different manner. See the [`TagLessEq`] and [`TagEq`] types as well as the many 71 | //! combinators on [`ExactSize`], [`Prefix`], and [`Idx`]. 72 | //! 73 | //! ## Checked constant bounds 74 | //! 75 | //! Alternatively we can choose other unique type instances. By that we mean that for any 76 | //! particular type exactly _one_ value must be used to construct [`ExactSize`]. One possible way 77 | //! is if this is simply a constant which is implemented by the `Constant` wrapper and its 78 | //! [`ConstantSource`] trait. For example one may define: 79 | //! 80 | //! ``` 81 | //! use index_ext::tag::{Constant, ConstantSource, ExactSize}; 82 | //! 83 | //! const BUFFER_SIZE: usize = 4096; 84 | //! struct BufferSize4K; 85 | //! 86 | //! impl ConstantSource for BufferSize4K { 87 | //! const LEN: usize = BUFFER_SIZE; 88 | //! } 89 | //! 90 | //! const LEN: ExactSize> = Constant::EXACT_SIZE; 91 | //! ``` 92 | use core::marker::PhantomData; 93 | use core::num::NonZeroUsize; 94 | use core::ops::{Range, RangeFrom, RangeTo}; 95 | 96 | /// A type suitable for tagging length, indices, and containers. 97 | /// 98 | /// # Safety 99 | /// 100 | /// The manner in which new [`ExactSize`] instances of types implementing this trait can be created 101 | /// is an invariant of each individual type. It must **not** be allowed to subvert the invariants 102 | /// imposed by any other type implementing this trait. In particular you mustn't create an instance 103 | /// that allows coercing a `ExactSize` into `ExactSize` where you don't control both 104 | /// these types. Note that this restriction MUST hold for every possible coercion allowed by the 105 | /// language. There are no inherently safe constructors for `ExactSize` but each tag type might 106 | /// define some. 107 | /// 108 | /// The type must further promise to be zero-sized and have an alignment of exactly `1`. This 109 | /// allows tags to be virtually added to other referenced objects in-place. 110 | pub unsafe trait Tag: Copy {} 111 | 112 | /// A generative lifetime. 113 | /// 114 | /// This is a simple implementor of `Tag` that allows a _local_ but entirely safe and macro-free 115 | /// use of check indices. The compiler introduces new lifetimes and the design of these types 116 | /// ensure that no other object with the same can be created. 117 | #[derive(Clone, Copy)] 118 | pub struct Generative<'lt> { 119 | /// An invariant lifetime. 120 | generated: PhantomData<&'lt fn(&'lt [()])>, 121 | } 122 | 123 | /// SAFETY: This is invariant over the lifetime. There are no other coercions. 124 | /// See 125 | unsafe impl Tag for Generative<'_> {} 126 | 127 | /// A unique tag, 'named' by its type parameter. 128 | /// 129 | /// Note that this type is safe to construct, but it is not safe to tag a slice or index with it. 130 | /// The user is responsible for ensuring the uniqueness of the type parameter, which is necessary 131 | /// for the soundness of wrapping an index or slice. 132 | pub struct TagNamed { 133 | phantom: PhantomData, 134 | } 135 | 136 | /// Enter a region for soundly indexing a slice without bounds checks. 137 | /// 138 | /// Prefer [`Generative::with_ref`] if possible. 139 | /// 140 | /// The supplied function gets a freshly constructed pair of corresponding slice reference and 141 | /// length tag. It has no control over the exact lifetime used for the tag. 142 | pub fn with_ref<'slice, T, U>( 143 | slice: &'slice [T], 144 | f: impl for<'r> FnOnce(&'slice Slice>, ExactSize>) -> U, 145 | ) -> U { 146 | // We can not use `Generative::with_ref` here due to a lifetime conflict. If this was defined 147 | // in this body then it would not outlive `'slice`. 148 | let len = ExactSize { 149 | inner: Prefix { 150 | len: slice.len(), 151 | tag: Generative { 152 | generated: PhantomData, 153 | }, 154 | }, 155 | }; 156 | 157 | let slice = unsafe { Slice::new_unchecked(slice, len.inner.tag) }; 158 | 159 | f(slice, len) 160 | } 161 | 162 | /// Enter a region for soundly indexing a mutable slice without bounds checks. 163 | /// 164 | /// Prefer [`Generative::with_mut`] if possible. 165 | /// 166 | /// The supplied function gets a freshly constructed pair of corresponding slice reference and 167 | /// length tag. It has no control over the exact lifetime used for the tag. 168 | pub fn with_mut<'slice, T, U>( 169 | slice: &'slice mut [T], 170 | f: impl for<'r> FnOnce(&'slice mut Slice>, ExactSize>) -> U, 171 | ) -> U { 172 | // We can not use `Generative::with_mut` here due to a lifetime conflict. If this was defined 173 | // in this body then it would not outlive `'slice`. 174 | let len = ExactSize { 175 | inner: Prefix { 176 | len: slice.len(), 177 | tag: Generative { 178 | generated: PhantomData, 179 | }, 180 | }, 181 | }; 182 | 183 | let slice = unsafe { Slice::new_unchecked_mut(slice, len.inner.tag) }; 184 | 185 | f(slice, len) 186 | } 187 | 188 | /// The length of a particular slice (or a number of slices). 189 | /// 190 | /// The encapsulated length field is guaranteed to be at most the length of each of the slices with 191 | /// the exact same tag. In other words, all indices _strictly smaller_ than this number are 192 | /// safe. 193 | /// 194 | /// This allows this instance to construct indices that are validated to be able to soundly 195 | /// access the slices without required any particular slice instance. In particular, the construct 196 | /// might happen by a numerical algorithm independent of the slices and across method bounds where 197 | /// the compiler's optimizer and inline pass is no longer aware of the connection and would 198 | /// otherwise insert another check when the slice is indexed later. 199 | #[derive(Clone, Copy)] 200 | pub struct Prefix { 201 | len: usize, 202 | tag: Tag, 203 | } 204 | 205 | /// A number that overestimates the guaranteed size of a number of slices. 206 | /// 207 | /// This is the counter part of [`Prefix`]. It encapsulates a field that is guaranteed to be at least 208 | /// the size of all indices with the exact same tag. In other words, all slices at least as long 209 | /// as this number are safe to be accessed by indices. 210 | #[derive(Clone, Copy)] 211 | pub struct Capacity { 212 | len: usize, 213 | tag: Tag, 214 | } 215 | 216 | /// The length of a non-empty slice. 217 | #[derive(Clone, Copy)] 218 | pub struct NonZeroLen { 219 | len: NonZeroUsize, 220 | tag: Tag, 221 | } 222 | 223 | /// The _exact_ length separating slices and indices for a tag. 224 | /// 225 | /// This serves as an constructor basis for 'importing' lengths and slices that are not previously 226 | /// connected through [`with_ref`] or equivalent constructors. This is also useful for cases where 227 | /// you want to create some bounds prior to the slice being available, or for creating bounds of 228 | /// custom tags. 229 | #[derive(Clone, Copy)] 230 | pub struct ExactSize { 231 | inner: Prefix, 232 | } 233 | 234 | /// A proof that the length if A is smaller or equal to B. 235 | /// 236 | /// This guarantees that indices of `A` can also be used in `B`. 237 | #[derive(Clone, Copy)] 238 | pub struct TagLessEq { 239 | a: TagA, 240 | b: TagB, 241 | } 242 | 243 | /// A proof that two tags refer to equal lengths. 244 | /// 245 | /// This guarantees that indices of `A` and `B` can be used interchangeably. 246 | #[derive(Clone, Copy)] 247 | pub struct TagEq { 248 | a: TagA, 249 | b: TagB, 250 | } 251 | 252 | /// A slice with a unique type tag. 253 | /// 254 | /// You can only construct this via the tag semantics associated with the type parameter, `Tag`. 255 | /// For instance see [`with_ref`], [`Generative::with_ref`], [`Const::with_ref`]. 256 | /// 257 | pub struct Slice { 258 | #[allow(dead_code)] 259 | tag: Tag, 260 | slice: [T], 261 | } 262 | 263 | /// An owned, allocated slice with a checked length. 264 | #[cfg(feature = "alloc")] 265 | pub struct Boxed { 266 | inner: alloc::boxed::Box<[T]>, 267 | tag: Tag, 268 | } 269 | 270 | /// A type that names a constant buffer size. 271 | /// 272 | /// See the module level documentation. 273 | pub trait ConstantSource { 274 | /// The chosen length separating indices and slices. 275 | const LEN: usize; 276 | } 277 | 278 | /// A tag using a `ConstantSource`. 279 | /// 280 | /// The only safe way to construct an `ExactSize` is by copying the associated constant which 281 | /// expresses the length indicated in the trait impl. This implies that the value is unique. 282 | /// (Disregarding unsound rustc issues that allow duplicate trait impls). 283 | pub struct Constant(PhantomData T>); 284 | 285 | unsafe impl Tag for Constant {} 286 | 287 | /// A tag using a const generic length parameter. 288 | /// 289 | /// The only safe way to construct an `ExactSize` is by copying the associated constant which 290 | /// expresses the length indicated in the trait impl. This implies that the value is unique. 291 | /// 292 | /// # Usage 293 | /// 294 | /// ``` 295 | /// use index_ext::tag::{Const, Slice}; 296 | /// 297 | /// let size = Const::<8>::EXACT_SIZE; 298 | /// 299 | /// let data = [0, 1, 2, 3, 4, 5, 6, 7]; 300 | /// let slice = Slice::new(&data[..], size).unwrap(); 301 | /// 302 | /// let prefix = size 303 | /// .into_len() 304 | /// .truncate(4) 305 | /// .range_to_self(); 306 | /// 307 | /// let prefix = &slice[prefix]; 308 | /// assert_eq!(prefix, [0, 1, 2, 3]); 309 | /// ``` 310 | #[derive(Clone, Copy)] 311 | pub struct Const; 312 | 313 | unsafe impl Tag for Const {} 314 | 315 | /// A valid index for all slices of the same length. 316 | /// 317 | /// While this has a generic parameter, you can only instantiate this type for specific types 318 | /// through one of the constructors of a corresponding [`Prefix]` struct. 319 | /// 320 | /// [`Prefix`]: struct.Prefix.html 321 | #[derive(Clone, Copy)] 322 | pub struct Idx { 323 | idx: I, 324 | /// An invariant lifetime. 325 | tag: Tag, 326 | } 327 | 328 | /// An allocation of bounded indices that can be retrieved with a bound. 329 | /// 330 | /// The usefulness comes from the fact that there is not tag on the type but instead one is 331 | /// assigned when retrieving the contents. In particular you don't need a unique type to construct 332 | /// this container. 333 | #[cfg(feature = "alloc")] 334 | pub struct IdxBox { 335 | indices: alloc::boxed::Box<[Idx]>, 336 | /// The dynamic bound of indices. 337 | exact_size: usize, 338 | } 339 | 340 | impl Prefix { 341 | /// Interpret this with the tag of a set of potentially longer slices. 342 | /// 343 | /// The proof of inequality was performed in any of the possible constructors that allow the 344 | /// instance of `TagLessEq` to exist in the first place. 345 | pub fn with_tag(self, less: TagLessEq) -> Prefix { 346 | let len = self.len; 347 | let tag = less.b; 348 | Prefix { len, tag } 349 | } 350 | 351 | /// Returns the stored length. 352 | #[must_use = "Is a no-op. Use the returned length."] 353 | pub fn get(self) -> usize { 354 | self.len 355 | } 356 | 357 | /// Construct an index to a single element. 358 | /// 359 | /// This method return `Some` when the index is smaller than the length. 360 | #[must_use = "Returns a new index"] 361 | pub fn index(self, idx: usize) -> Option> { 362 | if idx < self.len { 363 | Some(Idx { idx, tag: self.tag }) 364 | } else { 365 | None 366 | } 367 | } 368 | 369 | /// Construct an index to a range of element. 370 | /// 371 | /// This method return `Some` when the indices are ordered and `to` does not exceed the length. 372 | #[must_use = "Returns a new index"] 373 | pub fn range(self, from: usize, to: usize) -> Option, T>> { 374 | if from <= to && to <= self.len { 375 | Some(Idx { 376 | idx: from..to, 377 | tag: self.tag, 378 | }) 379 | } else { 380 | None 381 | } 382 | } 383 | 384 | /// Construct an index to a range from an element. 385 | /// 386 | /// This method return `Some` when `from` does not exceed the length. 387 | #[must_use = "Returns a new index"] 388 | pub fn range_from(self, from: usize) -> Option, T>> { 389 | if from <= self.len { 390 | Some(Idx { 391 | idx: from.., 392 | tag: self.tag, 393 | }) 394 | } else { 395 | None 396 | } 397 | } 398 | 399 | /// Construct an index to a range starting at this length. 400 | /// 401 | /// This method might return an index for an empty range. 402 | #[must_use = "Returns a new index"] 403 | pub fn range_from_self(self) -> Idx, T> { 404 | Idx { 405 | idx: self.len.., 406 | tag: self.tag, 407 | } 408 | } 409 | 410 | /// Construct an index to a range up to an element. 411 | /// 412 | /// This method return `Some` when `to` does not exceed the length. 413 | #[must_use = "Returns a new index"] 414 | pub fn range_to(self, to: usize) -> Option, T>> { 415 | if to <= self.len { 416 | Some(Idx { 417 | idx: ..to, 418 | tag: self.tag, 419 | }) 420 | } else { 421 | None 422 | } 423 | } 424 | 425 | /// Construct an index to a range up, exclusive, to this length. 426 | /// 427 | /// This method might return an index for an empty range. 428 | #[must_use = "Returns a new index"] 429 | pub fn range_to_self(self) -> Idx, T> { 430 | Idx { 431 | idx: ..self.len, 432 | tag: self.tag, 433 | } 434 | } 435 | 436 | /// Construct an index referring to the unordered range from one element to another. 437 | /// 438 | /// This method might return an empty range. The order of arguments does not matter. 439 | #[must_use = "Returns a new index"] 440 | pub fn range_between(self, other: Self) -> Idx, T> { 441 | Idx { 442 | idx: self.len.min(other.len)..self.len.max(other.len), 443 | tag: self.tag, 444 | } 445 | } 446 | 447 | /// Construct an index to all elements. 448 | /// 449 | /// This method exists mostly for completeness sake. There is no bounds check when accessing a 450 | /// complete slice with `..`. 451 | #[must_use = "Returns a new index"] 452 | pub fn range_full(self) -> Idx { 453 | Idx { 454 | idx: .., 455 | tag: self.tag, 456 | } 457 | } 458 | 459 | /// Create a smaller length. 460 | #[must_use = "Returns a new index"] 461 | pub fn saturating_sub(self, sub: usize) -> Self { 462 | Prefix { 463 | len: self.len.saturating_sub(sub), 464 | tag: self.tag, 465 | } 466 | } 467 | 468 | /// Bound the length from above. 469 | #[must_use = "Returns a new index"] 470 | pub fn truncate(self, min: usize) -> Self { 471 | Prefix { 472 | len: self.len.min(min), 473 | tag: self.tag, 474 | } 475 | } 476 | } 477 | 478 | impl Capacity { 479 | /// Interpret this with the tag of a set of potentially shorter slices. 480 | /// 481 | /// The proof of inequality was performed in any of the possible constructors that allow the 482 | /// instance of `TagLessEq` to exist in the first place. 483 | pub fn with_tag(self, less: TagLessEq) -> Capacity { 484 | let len = self.len; 485 | let tag = less.a; 486 | Capacity { len, tag } 487 | } 488 | 489 | /// Returns the stored length. 490 | #[must_use = "Is a no-op. Use the returned length."] 491 | pub fn get(self) -> usize { 492 | self.len 493 | } 494 | 495 | /// Create a larger capacity. 496 | #[must_use = "Returns a new capacity"] 497 | pub fn saturating_add(self, add: usize) -> Self { 498 | Capacity { 499 | len: self.len.saturating_add(add), 500 | tag: self.tag, 501 | } 502 | } 503 | 504 | /// Bound the length from below. 505 | #[must_use = "Returns a new capacity"] 506 | pub fn ensure(self, min: usize) -> Self { 507 | Capacity { 508 | len: self.len.max(min), 509 | tag: self.tag, 510 | } 511 | } 512 | } 513 | 514 | impl NonZeroLen { 515 | /// Construct the length of a non-empty slice. 516 | pub fn new(complete: Prefix) -> Option { 517 | let len = NonZeroUsize::new(complete.len)?; 518 | Some(NonZeroLen { 519 | len, 520 | tag: complete.tag, 521 | }) 522 | } 523 | 524 | /// Interpret this with the tag of a potentially longer slice. 525 | /// 526 | /// The proof of inequality was performed in any of the possible constructors that allow the 527 | /// instance of `TagLessEq` to exist in the first place. 528 | pub fn with_tag(self, less: TagLessEq) -> NonZeroLen { 529 | let len = self.len; 530 | let tag = less.b; 531 | NonZeroLen { len, tag } 532 | } 533 | 534 | /// Construct an index to the first element of a non-empty slice. 535 | #[must_use = "Returns a new index"] 536 | pub fn first(self) -> Idx { 537 | Idx { 538 | idx: 0, 539 | tag: self.tag, 540 | } 541 | } 542 | 543 | /// Construct an index to the last element of a non-empty slice. 544 | #[must_use = "Returns a new index"] 545 | pub fn last(self) -> Idx { 546 | Idx { 547 | idx: self.len.get() - 1, 548 | tag: self.tag, 549 | } 550 | } 551 | 552 | /// Construct the corresponding potentially empty length representation. 553 | #[must_use = "Returns a new index"] 554 | pub fn into_len(self) -> Prefix { 555 | Prefix { 556 | len: self.len.get(), 557 | tag: self.tag, 558 | } 559 | } 560 | 561 | /// Get the non-zero length. 562 | #[must_use = "Is a no-op. Use the returned length."] 563 | pub fn get(self) -> NonZeroUsize { 564 | self.len 565 | } 566 | } 567 | 568 | /// The const methods for `ExactSize`. 569 | /// 570 | /// Since trait bounds are not currently usable on stable the selection is limited. **Note**: It is 571 | /// of importance to soundness that it is not possible to construct an instance without the `Tag` 572 | /// bound. Otherwise, one might coerce _into_ an `ExactSize` with an improper tag. This is not 573 | /// likely to be possible but nevertheless the `Tag` does not require it to be impossible. 574 | impl ExactSize { 575 | /// Construct a new bound between yet-to-create indices and slices. 576 | /// 577 | /// # Safety 578 | /// 579 | /// All `ExactSize` instances with the same tag type must also have the same `len` field. 580 | pub const unsafe fn new_untagged(len: usize, tag: T) -> Self { 581 | ExactSize { 582 | inner: Prefix { len, tag }, 583 | } 584 | } 585 | 586 | /// Construct a new bound from a length. 587 | /// 588 | /// # Safety 589 | /// 590 | /// You _must_ ensure that no slice with this same tag can be shorter than `len`. In particular 591 | /// there mustn't be any other `ExactSize` with a differing length. 592 | /// 593 | /// `T` should be a type implementing `Tag` but this can not be expressed with `const fn` atm. 594 | pub const unsafe fn from_len_untagged(bound: Prefix) -> Self { 595 | ExactSize { inner: bound } 596 | } 597 | 598 | /// Returns the length. 599 | #[must_use = "Is a no-op. Use the returned length."] 600 | pub const fn get(&self) -> usize { 601 | self.inner.len 602 | } 603 | } 604 | 605 | impl<'lt> Generative<'lt> { 606 | /// Construct a size with a generative guard and explicit length. 607 | /// 608 | /// The `Guard` instance is a token that verifies that no other instance with that particular 609 | /// lifetime exists. It is thus not possible to safely construct a second `ExactSize` with the 610 | /// same tag but a different length. This uniquely ties the value `len` to that lifetime. 611 | pub fn with_len(len: usize, token: generativity::Guard<'lt>) -> ExactSize { 612 | ExactSize::with_guard(len, token) 613 | } 614 | 615 | /// Consume a generativity token to associated a lifetime with the slice's length. 616 | /// 617 | /// This isn't fundamentally different from using [`Self::with_len`] and [`Slice::new`], and 618 | /// you might want to see those documentations, but it clarifies that this combination is 619 | /// infallible. 620 | /// 621 | /// This essentially shares the generative uniqueness of the lifetime among all values relying 622 | /// on the length predicate of the same slice. 623 | /// 624 | /// # Usage 625 | /// 626 | /// This allows you to do the same as [`with_ref`] but ad-hoc within a function body without 627 | /// introducing any new scope. 628 | /// 629 | /// ``` 630 | /// use generativity::make_guard; 631 | /// use index_ext::tag::Generative; 632 | /// 633 | /// let data = (0..117).collect::>(); 634 | /// make_guard!(guard); 635 | /// let (slice, size) = Generative::with_ref(&data, guard); 636 | /// let index = size.into_len().range_to_self(); 637 | /// 638 | /// // … Later, no bounds check here. 639 | /// let data = &slice[index]; 640 | /// ``` 641 | pub fn with_ref<'slice, T>( 642 | slice: &'slice [T], 643 | token: generativity::Guard<'lt>, 644 | ) -> (&'slice Slice, ExactSize) { 645 | let size = ExactSize::with_guard(slice.len(), token); 646 | // Safety: This tag is associated with the exact length of the slice in the line above 647 | // which is less or equal to the length of the slice. 648 | let ref_ = unsafe { Slice::new_unchecked(slice, size.inner.tag) }; 649 | (ref_, size) 650 | } 651 | 652 | /// Consume a generativity token to associated a lifetime with the mutable slice's length. 653 | /// 654 | /// This isn't fundamentally different from using [`Self::with_len`] and [`Slice::new_mut`], 655 | /// and you might want to see those documentations, but it clarifies that this combination is 656 | /// infallible. 657 | /// 658 | /// This essentially shares the generative uniqueness of the lifetime among all values relying 659 | /// on the length predicate of the same slice. 660 | /// 661 | /// # Usage 662 | /// 663 | /// This allows you to do the same as [`with_mut`] but ad-hoc within a function body without 664 | /// introducing any new scope. 665 | /// 666 | /// ``` 667 | /// use generativity::make_guard; 668 | /// use index_ext::tag::Generative; 669 | /// 670 | /// let mut data = (0..117).collect::>(); 671 | /// make_guard!(guard); 672 | /// let (mut slice, size) = Generative::with_mut(&mut data, guard); 673 | /// let index = size.into_len().range_to_self(); 674 | /// 675 | /// // … Later, no bounds check here. 676 | /// let data = &mut slice[index]; 677 | pub fn with_mut<'slice, T>( 678 | slice: &'slice mut [T], 679 | token: generativity::Guard<'lt>, 680 | ) -> (&'slice mut Slice, ExactSize) { 681 | let size = ExactSize::with_guard(slice.len(), token); 682 | // Safety: This tag is associated with the exact length of the slice in the line above 683 | // which is less or equal to the length of the slice. 684 | let ref_ = unsafe { Slice::new_unchecked_mut(slice, size.inner.tag) }; 685 | (ref_, size) 686 | } 687 | } 688 | 689 | impl<'lt> ExactSize> { 690 | /// Construct a size with a generative guard. 691 | /// 692 | /// The `Guard` instance is a token that verifies that no other instance with that particular 693 | /// lifetime exists. It is thus not possible to safely construct a second `ExactSize` with the 694 | /// same tag but a different length. This uniquely ties the value `len` to that lifetime. 695 | /// 696 | /// FIXME: make this `const fn` which requires `PhantomData` to be allowed in const 697 | /// context (a small subset of #57563). 698 | pub fn with_guard(len: usize, _: generativity::Guard<'lt>) -> Self { 699 | ExactSize { 700 | inner: Prefix { 701 | len, 702 | tag: Generative { 703 | generated: PhantomData, 704 | }, 705 | }, 706 | } 707 | } 708 | } 709 | 710 | impl ExactSize { 711 | /// Construct a new bound between yet-to-create indices and slices. 712 | /// 713 | /// # Safety 714 | /// 715 | /// All `ExactSize` instances with the same tag type must also have the same `len` field. 716 | pub unsafe fn new(len: usize, tag: T) -> Self { 717 | // Safety: Propagates the exact same safety requirements. 718 | unsafe { Self::new_untagged(len, tag) } 719 | } 720 | 721 | /// Construct a new bound from a length. 722 | /// 723 | /// # Safety 724 | /// 725 | /// You _must_ ensure that no slice with this same tag can be shorter than `len`. In particular 726 | /// there mustn't be any other `ExactSize` with a differing length. 727 | pub unsafe fn from_len(len: Prefix) -> Self { 728 | // Safety: Propagates a subset of safety requirements. 729 | unsafe { Self::from_len_untagged(len) } 730 | } 731 | 732 | /// Construct a new bound from a capacity. 733 | /// 734 | /// # Safety 735 | /// 736 | /// You _must_ ensure that no index with this same tag can be above `cap`. In particular there 737 | /// mustn't be any other `ExactSize` with a differing length but the same tag type. 738 | pub unsafe fn from_capacity(cap: Capacity) -> Self { 739 | // Safety: Propagates a subset of safety requirements. 740 | unsafe { Self::new_untagged(cap.len, cap.tag) } 741 | } 742 | 743 | /// Interpret this with the tag of an equal sized slice. 744 | /// 745 | /// The proof of equality was performed in any of the possible constructors that allow the 746 | /// instance of `TagEq` to exist in the first place. 747 | pub fn with_tag(self, equality: TagEq) -> ExactSize { 748 | let len = self.inner.len; 749 | let tag = equality.b; 750 | ExactSize { 751 | inner: Prefix { len, tag }, 752 | } 753 | } 754 | 755 | /// Convert this into a simple `Prefix` without changing the length. 756 | /// 757 | /// The `Prefix` is only required to be _not longer_ than all slices but not required to have the 758 | /// exact separating size. As such, one can not use it to infer that some particular slice is 759 | /// long enough to be allowed. This is not safely reversible. 760 | #[must_use = "Returns a new index"] 761 | pub fn into_len(self) -> Prefix { 762 | self.inner 763 | } 764 | 765 | /// Convert this into a simple `Capacity` without changing the length. 766 | /// 767 | /// The `Capacity` is only required to be _not shorter_ than all slices but not required to 768 | /// have the exact separating size. As such, one can use it only to infer that some particular 769 | /// slice is long enough to be allowed. This is not safely reversible. 770 | #[must_use = "Returns a new index"] 771 | pub fn into_capacity(self) -> Capacity { 772 | Capacity { 773 | len: self.inner.len, 774 | tag: self.inner.tag, 775 | } 776 | } 777 | 778 | /// Construct a new bound from an pair of Prefix and Capacity with the same value. 779 | /// 780 | /// Note that the invariant of `ExactSize` is that all `Prefix` are guaranteed to be at most the 781 | /// size and all `Capacity` are guaranteed to be at least the size. The only possible overlap 782 | /// between the two is the exact length, which we can dynamically check. 783 | pub fn with_matching_pair(len: Prefix, cap: Capacity) -> Option { 784 | if len.get() == cap.get() { 785 | Some(ExactSize { 786 | inner: Prefix { 787 | len: len.get(), 788 | tag: len.tag, 789 | }, 790 | }) 791 | } else { 792 | None 793 | } 794 | } 795 | } 796 | 797 | impl TagEq { 798 | /// Construct the reflexive proof. 799 | pub fn reflexive(tag: A) -> Self { 800 | TagEq { a: tag, b: tag } 801 | } 802 | } 803 | 804 | impl TagEq { 805 | /// Create an equality from evidence `a <= b <= a`. 806 | pub fn new(lhs: TagLessEq, _: TagLessEq) -> Self { 807 | TagEq { a: lhs.a, b: lhs.b } 808 | } 809 | 810 | /// Swap the two tags, `a = b` iff `b = a`. 811 | pub fn transpose(self) -> TagEq { 812 | TagEq { 813 | a: self.b, 814 | b: self.a, 815 | } 816 | } 817 | 818 | /// Relax this into a less or equal relation. 819 | pub fn into_le(self) -> TagLessEq { 820 | TagLessEq { 821 | a: self.a, 822 | b: self.b, 823 | } 824 | } 825 | } 826 | 827 | impl TagLessEq { 828 | /// Construct the reflexive proof. 829 | pub fn reflexive(tag: A) -> Self { 830 | TagLessEq { a: tag, b: tag } 831 | } 832 | } 833 | 834 | impl TagLessEq { 835 | /// Construct the proof from the sizes of A and B. 836 | pub fn with_sizes(a: ExactSize, b: ExactSize) -> Option { 837 | if a.get() <= b.get() { 838 | Some(TagLessEq { 839 | a: a.inner.tag, 840 | b: b.inner.tag, 841 | }) 842 | } else { 843 | None 844 | } 845 | } 846 | 847 | /// Construct the proof from a pair of bounds for A and B. 848 | /// 849 | /// The `Capacity` upper bounds all indices applicable to A, and the exact size. The `Prefix` 850 | /// lower bounds all lengths and the exact size. 851 | /// 852 | /// This returns `Some` when the lower bound for B is not smaller than the upper bound for A. 853 | pub fn with_pair(a: Capacity, b: Prefix) -> Option { 854 | if b.get() >= a.get() { 855 | Some(TagLessEq { a: a.tag, b: b.tag }) 856 | } else { 857 | None 858 | } 859 | } 860 | } 861 | 862 | impl TagNamed { 863 | /// Create a new named tag. 864 | /// 865 | /// The instance is only to be encouraged to only use types private to your crate or module, 866 | /// this method immediately *forgets* the instance which is currently required for `const`ness. 867 | pub const fn new(t: T) -> Self { 868 | // Const-fn does not allow dropping values. We don't want (and can't have) `T: Copy` so we 869 | // need to statically prove this to rustc by actually removing the drop call. 870 | let _ = core::mem::ManuallyDrop::new(t); 871 | TagNamed { 872 | phantom: PhantomData, 873 | } 874 | } 875 | } 876 | 877 | unsafe impl Tag for TagNamed {} 878 | 879 | impl From> for Prefix { 880 | fn from(from: NonZeroLen) -> Self { 881 | Prefix { 882 | len: from.len.get(), 883 | tag: from.tag, 884 | } 885 | } 886 | } 887 | 888 | impl Idx { 889 | /// Get the inner index. 890 | pub fn into_inner(self) -> I { 891 | self.idx 892 | } 893 | 894 | /// Interpret this as an index into a larger slice. 895 | pub fn with_tag(self, larger: TagLessEq) -> Idx { 896 | Idx { 897 | idx: self.idx, 898 | tag: larger.b, 899 | } 900 | } 901 | } 902 | 903 | impl Idx { 904 | /// Create a smaller index. 905 | #[must_use = "Returns a new index"] 906 | pub fn saturating_sub(self, sub: usize) -> Self { 907 | Idx { 908 | idx: self.idx.saturating_sub(sub), 909 | tag: self.tag, 910 | } 911 | } 912 | 913 | /// Bound the index from above. 914 | #[must_use = "Returns a new index"] 915 | pub fn truncate(self, min: usize) -> Self { 916 | Idx { 917 | idx: self.idx.min(min), 918 | tag: self.tag, 919 | } 920 | } 921 | 922 | /// Return the range that contains this element. 923 | #[must_use = "Returns a new index"] 924 | pub fn into_range(self) -> Idx, T> { 925 | Idx { 926 | idx: self.idx..self.idx + 1, 927 | tag: self.tag, 928 | } 929 | } 930 | 931 | /// Get a length up-to, not including this index. 932 | #[must_use = "Returns a new index"] 933 | pub fn into_len(self) -> Prefix { 934 | Prefix { 935 | len: self.idx, 936 | tag: self.tag, 937 | } 938 | } 939 | 940 | /// Get the length beyond this index. 941 | /// 942 | /// Unlike turning it into a range and using its end, this guarantees that the end is non-zero 943 | /// as it knows the range not to be empty. 944 | #[must_use = "Returns a new index"] 945 | pub fn into_end(self) -> NonZeroLen { 946 | NonZeroLen { 947 | len: NonZeroUsize::new(self.idx + 1).unwrap(), 948 | tag: self.tag, 949 | } 950 | } 951 | } 952 | 953 | impl Idx, T> { 954 | /// Get a length up-to, not including this index. 955 | #[must_use = "Returns a new index"] 956 | pub fn into_end(self) -> Prefix { 957 | Prefix { 958 | len: self.idx.end, 959 | tag: self.tag, 960 | } 961 | } 962 | 963 | /// Construct an index starting at an element. 964 | /// 965 | /// This method return `Some` when `from` does not exceed the end index. 966 | #[must_use = "Returns a new index"] 967 | pub fn range_from(self, from: Prefix) -> Option, T>> { 968 | if from.len <= self.idx.end { 969 | Some(Idx { 970 | idx: from.len..self.idx.end, 971 | tag: self.tag, 972 | }) 973 | } else { 974 | None 975 | } 976 | } 977 | } 978 | 979 | impl Idx, T> { 980 | /// Get a length up-to, not including this index. 981 | #[must_use = "Returns a new index"] 982 | pub fn into_start(self) -> Prefix { 983 | Prefix { 984 | len: self.idx.start, 985 | tag: self.tag, 986 | } 987 | } 988 | 989 | /// Construct an index up to at an element. 990 | /// 991 | /// This method return `Some` when `to` does not exceed the end index. 992 | #[must_use = "Returns a new index"] 993 | pub fn range_to(self, to: Prefix) -> Option, T>> { 994 | if to.len >= self.idx.start { 995 | Some(Idx { 996 | idx: self.idx.start..to.len, 997 | tag: self.tag, 998 | }) 999 | } else { 1000 | None 1001 | } 1002 | } 1003 | } 1004 | 1005 | impl Idx, T> { 1006 | /// Get a length up-to, not including this index. 1007 | #[must_use = "Returns a new index"] 1008 | pub fn into_start(self) -> Prefix { 1009 | Prefix { 1010 | len: self.idx.start, 1011 | tag: self.tag, 1012 | } 1013 | } 1014 | 1015 | /// Get a length up-to, not including this index. 1016 | #[must_use = "Returns a new index"] 1017 | pub fn into_end(self) -> Prefix { 1018 | Prefix { 1019 | len: self.idx.end, 1020 | tag: self.tag, 1021 | } 1022 | } 1023 | } 1024 | 1025 | #[allow(unused_unsafe)] 1026 | impl Slice { 1027 | /// Try to wrap a slice into a safe index wrapper. 1028 | /// 1029 | /// Returns `Some(_)` if the slice is at least as long as the `size` requires, otherwise 1030 | /// returns `None`. 1031 | pub fn new(slice: &[E], size: ExactSize) -> Option<&'_ Self> { 1032 | if slice.len() >= size.into_len().get() { 1033 | Some(unsafe { Self::new_unchecked(slice, size.inner.tag) }) 1034 | } else { 1035 | None 1036 | } 1037 | } 1038 | 1039 | /// Try to wrap a mutable slice into a safe index wrapper. 1040 | /// 1041 | /// Returns `Some(_)` if the slice is at least as long as the `size` requires, otherwise 1042 | /// returns `None`. 1043 | pub fn new_mut(slice: &mut [E], size: ExactSize) -> Option<&'_ mut Self> { 1044 | if slice.len() >= size.into_len().get() { 1045 | Some(unsafe { Self::new_unchecked_mut(slice, size.inner.tag) }) 1046 | } else { 1047 | None 1048 | } 1049 | } 1050 | 1051 | /// Unsafely wrap a slice into an index wrapper. 1052 | /// 1053 | /// # Safety 1054 | /// 1055 | /// The caller must uphold that the _exact size_ associated with the type `Tag` (see 1056 | /// [`ExactSize::new_untagged`]) is at most as large as the length of this slice. 1057 | pub unsafe fn new_unchecked(slice: &[E], _: T) -> &'_ Self { 1058 | // SAFETY: by T: Tag we know that T is 1-ZST which makes Self have the same layout as [E]. 1059 | // Further, tag is evidence that T is inhabited and T: Copy implies T: !Drop. 1060 | // Finally, the tag is valid for the slice's length by assumption of relying on the caller. 1061 | unsafe { &*(slice as *const [E] as *const Self) } 1062 | } 1063 | 1064 | /// Unsafely wrap a mutable slice into an index wrapper. 1065 | /// 1066 | /// # Safety 1067 | /// 1068 | /// The caller must uphold that the _exact size_ associated with the type `Tag` (see 1069 | /// [`ExactSize::new_untagged`]) is at most as large as the length of this slice. 1070 | pub unsafe fn new_unchecked_mut(slice: &mut [E], _: T) -> &'_ mut Self { 1071 | // SAFETY: by T: Tag we know that T is 1-ZST which makes Self have the same layout as [E]. 1072 | // Further, tag is evidence that T is inhabited and T: Copy implies T: !Drop. 1073 | // Finally, the tag is valid for the slice's length by assumption of relying on the caller. 1074 | unsafe { &mut *(slice as *mut [E] as *mut Self) } 1075 | } 1076 | 1077 | /// Interpret this as a slice with smaller length. 1078 | pub fn with_tag(&self, _: TagLessEq) -> &'_ Slice { 1079 | // SAFETY: by NewT: Tag we know that T NewT is 1-ZST which makes Self have the same layout 1080 | // as [E] and consequently the same layout as Self. Further, smaller.a is evidence that 1081 | // NewT is inhabited and NewT: Copy implies NewT: !Drop. Finally, the tag is valid for the 1082 | // slice's length by assumption of relying on self.element being valid based on the 1083 | // invariant of Self. 1084 | unsafe { &*(self as *const Self as *const Slice) } 1085 | } 1086 | 1087 | /// Interpret this as a slice with smaller length. 1088 | pub fn with_tag_mut(&mut self, _: TagLessEq) -> &'_ mut Slice { 1089 | // SAFETY: by NewT: Tag we know that T NewT is 1-ZST which makes Self have the same layout 1090 | // as [E] and consequently the same layout as Self. Further, smaller.a is evidence that 1091 | // NewT is inhabited and NewT: Copy implies NewT: !Drop. Finally, the tag is valid for the 1092 | // slice's length by assumption of relying on self.element being valid based on the 1093 | // invariant of Self. 1094 | unsafe { &mut *(self as *mut Self as *mut Slice) } 1095 | } 1096 | 1097 | /// Get the length as a `Capacity` of all slices with this tag. 1098 | pub fn capacity(&self) -> Capacity { 1099 | Capacity { 1100 | len: self.slice.len(), 1101 | tag: self.tag, 1102 | } 1103 | } 1104 | } 1105 | 1106 | impl Slice { 1107 | /// Index the slice unchecked but soundly. 1108 | pub fn get_safe>(&self, index: Idx) -> &I::Output { 1109 | unsafe { self.slice.get_unchecked(index.idx) } 1110 | } 1111 | 1112 | /// Mutably index the slice unchecked but soundly. 1113 | pub fn get_safe_mut>( 1114 | &mut self, 1115 | index: Idx, 1116 | ) -> &mut I::Output { 1117 | unsafe { self.slice.get_unchecked_mut(index.idx) } 1118 | } 1119 | } 1120 | 1121 | #[cfg(feature = "alloc")] 1122 | impl Boxed { 1123 | /// Try to construct an asserted box, returning it on error. 1124 | /// 1125 | /// The box slice must have _exactly_ the length indicated. 1126 | pub fn new( 1127 | inner: alloc::boxed::Box<[E]>, 1128 | len: ExactSize, 1129 | ) -> Result> { 1130 | match Slice::new(&*inner, len) { 1131 | Some(_) => Ok(Boxed { 1132 | inner, 1133 | tag: len.inner.tag, 1134 | }), 1135 | None => Err(inner), 1136 | } 1137 | } 1138 | 1139 | /// Reference the contents as an asserted `Slice`. 1140 | pub fn as_ref(&self) -> &'_ Slice { 1141 | // SAFETY: the inner invariant of `Boxed` is that the length is at least as large as the 1142 | // `ExactSize`, ensured in its only constructor `new`. 1143 | unsafe { Slice::new_unchecked(&*self.inner, self.tag) } 1144 | } 1145 | 1146 | /// Reference the contents as an asserted mutable `Slice`. 1147 | pub fn as_mut(&mut self) -> &'_ mut Slice { 1148 | // SAFETY: the inner invariant of `Boxed` is that the length is at least as large as the 1149 | // `ExactSize`, ensured in its only constructor `new`. 1150 | unsafe { Slice::new_unchecked_mut(&mut *self.inner, self.tag) } 1151 | } 1152 | 1153 | /// Get the length as a `Capacity` of all slices with this tag. 1154 | pub fn capacity(&self) -> Capacity { 1155 | Capacity { 1156 | len: self.inner.len(), 1157 | tag: self.tag, 1158 | } 1159 | } 1160 | 1161 | /// Index the boxed slice unchecked but soundly. 1162 | pub fn get_safe>(&self, index: Idx) -> &I::Output { 1163 | self.as_ref().get_safe(index) 1164 | } 1165 | 1166 | /// Mutably index the boxed slice unchecked but soundly. 1167 | pub fn get_safe_mut>( 1168 | &mut self, 1169 | index: Idx, 1170 | ) -> &mut I::Output { 1171 | self.as_mut().get_safe_mut(index) 1172 | } 1173 | 1174 | /// Unwrap the inner box, dropping all assertions of safe indexing. 1175 | pub fn into_inner(self) -> alloc::boxed::Box<[E]> { 1176 | self.inner 1177 | } 1178 | } 1179 | 1180 | impl core::ops::Deref for Slice { 1181 | type Target = [E]; 1182 | fn deref(&self) -> &[E] { 1183 | &self.slice 1184 | } 1185 | } 1186 | 1187 | impl core::ops::DerefMut for Slice { 1188 | fn deref_mut(&mut self) -> &mut [E] { 1189 | &mut self.slice 1190 | } 1191 | } 1192 | 1193 | impl core::ops::Index> for Slice 1194 | where 1195 | I: core::slice::SliceIndex<[E]>, 1196 | { 1197 | type Output = I::Output; 1198 | fn index(&self, idx: Idx) -> &Self::Output { 1199 | self.get_safe(idx) 1200 | } 1201 | } 1202 | 1203 | impl core::ops::IndexMut> for Slice 1204 | where 1205 | I: core::slice::SliceIndex<[E]>, 1206 | { 1207 | fn index_mut(&mut self, idx: Idx) -> &mut Self::Output { 1208 | self.get_safe_mut(idx) 1209 | } 1210 | } 1211 | 1212 | impl Clone for TagNamed { 1213 | fn clone(&self) -> Self { 1214 | *self 1215 | } 1216 | } 1217 | 1218 | impl Copy for TagNamed {} 1219 | 1220 | impl Clone for Constant { 1221 | fn clone(&self) -> Self { 1222 | *self 1223 | } 1224 | } 1225 | 1226 | impl Copy for Constant {} 1227 | 1228 | impl Constant { 1229 | /// A constructed instance of `ExactSize`. 1230 | /// 1231 | /// The instance can be freely copied. Making this an associated constant ensures that the 1232 | /// length associated with the type is the associated `LEN` constant while also permitting use 1233 | /// in `const` environments, despite the `ConstantSource` bound on the parameter. There are no 1234 | /// other safe constructors for this tag's `ExactSize` type. 1235 | pub const EXACT_SIZE: ExactSize = 1236 | // SAFETY: all instances have the same length, `LEN`. 1237 | unsafe { ExactSize::new_untagged(T::LEN, Constant(PhantomData)) }; 1238 | } 1239 | 1240 | impl Const { 1241 | /// A constructed instance of `ExactSize`. 1242 | /// 1243 | /// The instance can be freely copied. Making this an associated constant ensures that the 1244 | /// length associated with the type is the constant parameter `N`. There are no other safe 1245 | /// constructors for this tag's `ExactSize` type. 1246 | pub const EXACT_SIZE: ExactSize = 1247 | // SAFETY: all instances have the same length, `N`. 1248 | unsafe { ExactSize::new_untagged(N, Const) }; 1249 | 1250 | /// Create a [`Slice`] wrapping the array. 1251 | pub fn with_ref(self, arr: &[T; N]) -> &'_ Slice { 1252 | unsafe { Slice::new_unchecked(&arr[..], self) } 1253 | } 1254 | 1255 | /// Create a [`Slice`] wrapping the array mutably. 1256 | pub fn with_mut(self, arr: &mut [T; N]) -> &'_ mut Slice { 1257 | unsafe { Slice::new_unchecked_mut(&mut arr[..], self) } 1258 | } 1259 | } 1260 | 1261 | #[cfg(feature = "alloc")] 1262 | mod impl_of_boxed_idx { 1263 | use super::{ExactSize, Idx, IdxBox, Prefix, Tag}; 1264 | use core::ops::{RangeFrom, RangeTo}; 1265 | 1266 | /// Sealed trait, quite unsafe.. 1267 | pub trait HiddenMaxIndex: Sized { 1268 | fn exclusive_upper_bound(this: &[Self]) -> Option; 1269 | } 1270 | 1271 | impl HiddenMaxIndex for usize { 1272 | fn exclusive_upper_bound(this: &[Self]) -> Option { 1273 | this.iter() 1274 | .copied() 1275 | .max() 1276 | .map_or(Some(0), |idx| idx.checked_add(1)) 1277 | } 1278 | } 1279 | 1280 | impl HiddenMaxIndex for RangeFrom { 1281 | fn exclusive_upper_bound(this: &[Self]) -> Option { 1282 | this.iter().map(|range| range.start).max() 1283 | } 1284 | } 1285 | 1286 | impl HiddenMaxIndex for RangeTo { 1287 | fn exclusive_upper_bound(this: &[Self]) -> Option { 1288 | this.iter().map(|range| range.end).max() 1289 | } 1290 | } 1291 | 1292 | impl IdxBox { 1293 | /// Wrap an allocation of indices. 1294 | /// This will fail if it not possible to express the lower bound of slices for which all 1295 | /// indices are valid, as a `usize`. That is, if any of the indices references the element 1296 | /// with index `usize::MAX` itself. 1297 | pub fn new(indices: alloc::boxed::Box<[I]>) -> Result> { 1298 | match HiddenMaxIndex::exclusive_upper_bound(&indices[..]) { 1299 | Some(upper_bound) => Ok(IdxBox { 1300 | indices, 1301 | exact_size: upper_bound, 1302 | }), 1303 | None => Err(indices), 1304 | } 1305 | } 1306 | 1307 | /// Return the upper bound over all indices. 1308 | /// This is not guaranteed to be the _least_ upper bound. 1309 | pub fn bound(&self) -> usize { 1310 | self.exact_size 1311 | } 1312 | 1313 | /// Ensure that the stored `bound` is at least `min`. 1314 | pub fn ensure(&mut self, min: usize) { 1315 | self.exact_size = self.exact_size.max(min); 1316 | } 1317 | 1318 | /// Set the bound to the least upper bound of all indices. 1319 | /// 1320 | /// This always reduces the `bound` and there can not be any lower bound that is consistent 1321 | /// with all indices stored in this `IdxBox`. 1322 | pub fn truncate(&mut self) { 1323 | let least_bound = HiddenMaxIndex::exclusive_upper_bound(&self.indices) 1324 | // All mutation was performed under some concrete upper bound, and current elements 1325 | // must still be bounded by the largest such bound. 1326 | .expect("Some upper bound must still apply"); 1327 | debug_assert!( 1328 | self.exact_size >= least_bound, 1329 | "The exact size was corrupted to be below the least bound." 1330 | ); 1331 | self.exact_size = least_bound; 1332 | } 1333 | 1334 | /// Reinterpret the contents as indices of a given tag. 1335 | /// 1336 | /// The given size must not be smaller than the `bound` of this allocated. This guarantees 1337 | /// that all indices within the box are valid for the Tag. Since you can only _view_ the 1338 | /// indices, they will remain valid. 1339 | pub fn as_ref(&self, size: Prefix) -> Option<&'_ [Idx]> { 1340 | if size.get() >= self.exact_size { 1341 | Some(unsafe { 1342 | // SAFETY: `Idx` is a transparent wrapper around `I`, the type of this slice, 1343 | // and the type `T` is a ZST. The instance `size.tag` also proves that this ZST 1344 | // is inhabited and it is Copy as per requirements of `Tag`. The index is 1345 | // smaller than the ExactSize corresponding to `T` by transitivity over `size`. 1346 | let content: *const [I] = &self.indices[..]; 1347 | &*(content as *const [Idx]) 1348 | }) 1349 | } else { 1350 | None 1351 | } 1352 | } 1353 | 1354 | /// Reinterpret the contents as mutable indices of a given tag. 1355 | /// 1356 | /// The given exact size must not be exactly the same as the `bound` of this allocated 1357 | /// slice. This guarantees that all indices within the box are valid for the Tag, and that 1358 | /// all stored indices will be valid for all future tags. 1359 | pub fn as_mut(&mut self, size: ExactSize) -> Option<&'_ mut [Idx]> { 1360 | if size.get() == self.exact_size { 1361 | Some(unsafe { 1362 | // SAFETY: `Idx` is a transparent wrapper around `I`, the type of this slice, 1363 | // and the type `T` is a ZST. The instance `size.tag` also proves that this ZST 1364 | // is inhabited and it is Copy as per requirements of `Tag`. The index is 1365 | // smaller than the ExactSize corresponding to `T` by transitivity over `size`. 1366 | // Also any instance written will be smaller than `self.exact_size`, 1367 | // guaranteeing that the invariants of this type hold afterwards. 1368 | let content: *mut [I] = &mut self.indices[..]; 1369 | &mut *(content as *mut [Idx]) 1370 | }) 1371 | } else { 1372 | None 1373 | } 1374 | } 1375 | } 1376 | } 1377 | 1378 | #[cfg(test)] 1379 | mod tests { 1380 | use super::{with_ref, Constant, ConstantSource, Slice, TagEq, TagLessEq}; 1381 | 1382 | #[test] 1383 | fn basics() { 1384 | fn problematic(buf: &mut [u8], offsets: &[u8], idx: usize) { 1385 | with_ref(&offsets[..=idx], |offsets, size| { 1386 | let mut idx = size.into_len().index(idx).unwrap(); 1387 | for b in buf { 1388 | *b = idx.into_inner() as u8; 1389 | idx = idx.saturating_sub(usize::from(offsets[idx])); 1390 | } 1391 | }) 1392 | } 1393 | 1394 | let mut output = [0; 3]; 1395 | let offsets = [1, 0, 2, 2]; 1396 | problematic(&mut output, &offsets[..], 3); 1397 | assert_eq!(output, [3, 1, 1]); 1398 | } 1399 | 1400 | #[test] 1401 | fn tag_switching() { 1402 | struct ConstantLen; 1403 | impl ConstantSource for ConstantLen { 1404 | const LEN: usize = 4; 1405 | } 1406 | 1407 | let mut buffer = [0u8; 4]; 1408 | let csize = Constant::::EXACT_SIZE; 1409 | 1410 | let slice = Slice::new_mut(&mut buffer[..], csize).unwrap(); 1411 | assert_eq!(slice.len(), ConstantLen::LEN); 1412 | let all = csize.into_len().range_to_self(); 1413 | 1414 | with_ref(&buffer[..], |slice, size| { 1415 | let lesseq = TagLessEq::with_sizes(size, csize).unwrap(); 1416 | let moreeq = TagLessEq::with_sizes(csize, size).unwrap(); 1417 | // 'prove': csize = size 1418 | let eq = TagEq::new(lesseq, moreeq).transpose(); 1419 | 1420 | // Use this to transport the index. 1421 | let all = all.with_tag(eq.into_le()); 1422 | let safe = slice.get_safe(all); 1423 | assert_eq!(safe.len(), ConstantLen::LEN); 1424 | 1425 | assert_eq!(csize.with_tag(eq).get(), csize.get()); 1426 | }); 1427 | } 1428 | 1429 | #[test] 1430 | fn bad_inequalities() { 1431 | struct SmallLen; 1432 | struct LargeLen; 1433 | impl ConstantSource for SmallLen { 1434 | const LEN: usize = 1; 1435 | } 1436 | impl ConstantSource for LargeLen { 1437 | const LEN: usize = 2; 1438 | } 1439 | 1440 | let small = Constant::::EXACT_SIZE; 1441 | let large = Constant::::EXACT_SIZE; 1442 | 1443 | assert!( 1444 | TagLessEq::with_pair(small.into_capacity(), large.into_len()).is_some(), 1445 | "Small is in fact less than large" 1446 | ); 1447 | assert!( 1448 | TagLessEq::with_pair(large.into_capacity(), small.into_len()).is_none(), 1449 | "Large should not appear less than small" 1450 | ); 1451 | } 1452 | 1453 | #[test] 1454 | #[cfg(feature = "alloc")] 1455 | fn idx_boxing() { 1456 | use super::IdxBox; 1457 | use alloc::boxed::Box; 1458 | 1459 | struct ExactBound; 1460 | struct LargerBound; 1461 | 1462 | impl ConstantSource for ExactBound { 1463 | const LEN: usize = 3; 1464 | } 1465 | 1466 | impl ConstantSource for LargerBound { 1467 | const LEN: usize = 4; 1468 | } 1469 | 1470 | let indices = Box::from([0, 1, 2]); 1471 | 1472 | let mut boxed = IdxBox::new(indices).expect("Have a valid upper bound"); 1473 | assert_eq!(boxed.bound(), ::LEN); 1474 | 1475 | let exact = Constant::::EXACT_SIZE; 1476 | boxed.as_ref(exact.into_len()).expect("A valid upper bound"); 1477 | let larger = Constant::::EXACT_SIZE; 1478 | boxed 1479 | .as_ref(larger.into_len()) 1480 | .expect("A valid upper bound"); 1481 | 1482 | boxed.as_mut(exact).expect("A valid exact bound"); 1483 | assert!(boxed.as_mut(larger).is_none(), "An invalid exact bound"); 1484 | 1485 | // Now increase the bound 1486 | boxed.ensure(larger.get()); 1487 | assert_eq!(boxed.bound(), ::LEN); 1488 | assert!( 1489 | boxed.as_mut(exact).is_none(), 1490 | "No longer a valid exact bound" 1491 | ); 1492 | boxed.as_mut(larger).expect("Now a valid exact bound"); 1493 | 1494 | // But we've not _actually_ changed any index, so go back. 1495 | boxed.truncate(); 1496 | assert_eq!(boxed.bound(), ::LEN); 1497 | 1498 | boxed.as_mut(exact).expect("A valid exact bound"); 1499 | assert!(boxed.as_mut(larger).is_none(), "An invalid exact bound"); 1500 | } 1501 | } 1502 | 1503 | /// assertion macros are due to (c) theInkSquid (foobles) 1504 | /// ```compile_fail 1505 | /// use index_ext::tag; 1506 | /// macro_rules! assert_is_covariant { 1507 | /// (for[$($gen_params:tt)*] ($type_name:ty) over $lf:lifetime) => { 1508 | /// #[allow(warnings)] 1509 | /// const _: fn() = || { 1510 | /// struct Cov<$lf, $($gen_params)*>($type_name); 1511 | /// 1512 | /// fn test_cov<'__s, '__a: '__b, '__b, $($gen_params)*>( 1513 | /// subtype: &'__s Cov<'__a, $($gen_params)*>, 1514 | /// mut _supertype: &'__s Cov<'__b, $($gen_params)*>, 1515 | /// ) { 1516 | /// _supertype = subtype; 1517 | /// } 1518 | /// }; 1519 | /// }; 1520 | /// 1521 | /// (($type_name:ty) over $lf:lifetime) => { 1522 | /// assert_is_covariant!(for[] ($type_name) over $lf); 1523 | /// }; 1524 | /// } 1525 | /// 1526 | /// assert_is_covariant! { 1527 | /// (tag::Prefix<'r>) over 'r 1528 | /// } 1529 | /// ``` 1530 | /// 1531 | /// ```compile_fail 1532 | /// use index_ext::tag; 1533 | /// macro_rules! assert_is_contravariant { 1534 | /// (for[$($gen_params:tt)*] ($type_name:ty) over $lf:lifetime) => { 1535 | /// #[allow(warnings)] 1536 | /// const _: fn() = || { 1537 | /// struct Contra<$lf, $($gen_params)*>($type_name); 1538 | /// 1539 | /// fn test_contra<'__s, '__a: '__b, '__b, $($gen_params)*>( 1540 | /// mut _subtype: &'__s Contra<'__a, $($gen_params)*>, 1541 | /// supertype: &'__s Contra<'__b, $($gen_params)*>, 1542 | /// ) { 1543 | /// _subtype = supertype; 1544 | /// } 1545 | /// }; 1546 | /// }; 1547 | /// 1548 | /// (($type_name:ty) over $lf:lifetime) => { 1549 | /// assert_is_contravariant!(for[] ($type_name) over $lf); 1550 | /// }; 1551 | /// } 1552 | /// 1553 | /// assert_is_contravariant! { 1554 | /// (tag::Prefix<'r>) over 'r 1555 | /// } 1556 | /// ``` 1557 | #[cfg(doc)] 1558 | mod _doctests {} 1559 | --------------------------------------------------------------------------------