1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
// Copyright (C) 2021 Leandro Lisboa Penz <lpenz@lpenz.org>
// This file is subject to the terms and conditions defined in
// file 'LICENSE', which is part of this source code package.
//! *andex* (Array iNDEX) is a zero-dependency rust crate that helps
//! us create strongly-typed, zero-cost, numerically bound array index
//! and the corresponding array type with the provided size. The index
//! is safe in the sense that an out-of-bounds value can't be created,
//! and the array type can't be indexed by any other types.
//!
//! This is useful in scenarios where we have different arrays inside a
//! `struct` and we want reference members without holding proper
//! references that could "lock" the whole `struct`. It may also be useful
//! when programming an
//! [Entity Component System](https://en.wikipedia.org/wiki/Entity_component_system).
//!
//! And it's all done without requiring the use of any macros.
//!
//! # Usage
//!
//! ## Creating the andex type and array
//!
//! [`Andex`] is the index type and [`AndexableArray`] is the type of
//! the array wrapper.
//!
//! The recommended approach to use andex is as follows:
//! - Create a unique empty type
//! ```rust
//! # use andex::*;
//! enum MyIdxMarker {}
//! ```
//! - Create a type alias for the [`Andex`] type that's parameterized
//! with that type:
//! ```rust
//! # use andex::*;
//! # enum MyIdxMarker {}
//! type MyIdx = Andex<MyIdxMarker, 12>;
//! ```
//! - Create a type alias for the [`AndexableArray`] type that's
//! indexed by the [`Andex`] alias created above:
//! ```rust
//! # use andex::*;
//! # enum MyIdxMarker {}
//! # type MyIdx = Andex<MyIdxMarker, 12>;
//! type MyU32 = AndexableArray<MyIdx, u32, { MyIdx::SIZE }>;
//! // There is also a helper macro for this one:
//! type MyOtherU32 = andex_array!(MyIdx, u32);
//! ```
//!
//! ## Creating andex instances
//!
//! When an andex is created, it knows *at compile time* the size of the
//! array it indexes, and all instances are assumed to be within bounds.
//!
//! For this reason, it's useful to limit the way `Andex`'s are
//! created. The ways we can get an instance is:
//!
//! - Via `new`, passing the value as a generic const argument:
//! ```rust
//! # use andex::*;
//! # enum MyIdxMarker {}
//! # type MyIdx = Andex<MyIdxMarker, 12>;
//! const first : MyIdx = MyIdx::new::<0>();
//! ```
//! This checks that the value is valid at compile time, as long as you
//! use it to create `const` variables.
//!
//! - Via `try_from`, which returns `Result<Andex, Error>` that has to be
//! checked or explicitly ignored:
//! ```rust
//! # use std::convert::TryFrom;
//! # use andex::*;
//! # enum MyIdxMarker {}
//! # type MyIdx = Andex<MyIdxMarker, 12>;
//! if let Ok(first) = MyIdx::try_from(0) {
//! // ...
//! }
//! ```
//!
//! - Via `FIRST` and `LAST`:
//! ```rust
//! # use std::convert::TryFrom;
//! # use andex::*;
//! # enum MyIdxMarker {}
//! # type MyIdx = Andex<MyIdxMarker, 12>;
//! const first : MyIdx = MyIdx::FIRST;
//! let last = MyIdx::LAST;
//! ```
//!
//! - By iterating:
//! ```rust
//! # use andex::*;
//! # enum MyIdxMarker {}
//! # type MyIdx = Andex<MyIdxMarker, 12>;
//! for idx in MyIdx::iter() {
//! // ...
//! }
//! ```
//!
//! The assumption that the instances can only hold valid values allows us
//! to use `get_unsafe` and `get_unsafe_mut` in the indexer
//! implementation, which provides a bit of optimization by preventing the
//! bound check when indexing.
//!
//! ## Creating andexable arrays
//!
//! [`AndexableArray`] instances are less restrictive. They can be created
//! in several more ways:
//! - Using `Default` if the underlying type supports it:
//! ```rust
//! # use andex::*;
//! # enum MyIdxMarker {}
//! # type MyIdx = Andex<MyIdxMarker, 12>;
//! type MyU32 = AndexableArray<MyIdx, u32, { MyIdx::SIZE }>;
//!
//! let myu32 = MyU32::default();
//!
//! // We also have a helper macro that avoids repeating the size:
//! type MyOtherU32 = andex_array!(MyIdx, u32);
//! ```
//! - Using `From` with an appropriate array:
//! ```rust
//! # use andex::*;
//! # enum MyIdxMarker {}
//! # type MyIdx = Andex<MyIdxMarker, 12>;
//! # type MyU32 = AndexableArray<MyIdx, u32, { MyIdx::SIZE }>;
//! let myu32 = MyU32::from([8; MyIdx::SIZE]);
//! ```
//! - Collecting an iterator with the proper elements and size:
//! ```rust
//! # use andex::*;
//! # enum MyIdxMarker {}
//! # type MyIdx = Andex<MyIdxMarker, 12>;
//! # type MyU32 = AndexableArray<MyIdx, u32, { MyIdx::SIZE }>;
//! let myu32 = (0..12).collect::<MyU32>();
//! ```
//! Note: `collect` panics if the iterator returns a different
//! number of elements.
//!
//! ## Using andexable arrays
//!
//! Besides indexing them with a coupled `Andex` instance, we can
//! also access the inner array by using `as_ref`, iterate it in a
//! `for` loop (using one of the `IntoIterator` implementations) or
//! even get the inner array by consuming the `AndexableArray`.
//!
//! # Full example
//!
//! ```rust
//! use std::convert::TryFrom;
//! use std::error::Error;
//! use andex::*;
//!
//! // Create the andex type alias:
//! // First, we need an empty type that we use as a marker:
//! enum MyIdxMarker {}
//! // The andex type takes the marker (for uniqueness)
//! // and the size of the array as parameters:
//! type MyIdx = Andex<MyIdxMarker, 12>;
//!
//! // Create the array wrapper:
//! type MyU32 = AndexableArray<MyIdx, u32, { MyIdx::SIZE }>;
//!
//! // We can create other arrays indexable by the same Andex:
//! type MyF64 = AndexableArray<MyIdx, f64, { MyIdx::SIZE }>;
//!
//! fn main() -> Result<(), Box<dyn Error>> {
//! let myu32 = MyU32::default();
//!
//! // We can now only index MyU32 using MyIdx
//! const first : MyIdx = MyIdx::new::<0>();
//! println!("{:?}", myu32[first]);
//!
//! // Trying to create a MyIdx with an out-of-bounds value
//! // doesn't work, this won't compile:
//! // const _overflow : MyIdx = MyIdx::new::<30>();
//!
//! // Trying to index myu32 with a "naked" number
//! // doesn't work, this won't compile:
//! // println!("{}", myu32[0]);
//!
//! // We can create indexes via try_from with a valid value:
//! let second = MyIdx::try_from(2);
//! // ^ Returns a Result, which Ok(MyIdx) if the value provided is
//! // valid, or an error if it's not.
//!
//! // We can also create indexes at compile-time:
//! const third : MyIdx = MyIdx::new::<1>();
//!
//! // The index type has an `iter()` method that produces
//! // all possible values in order:
//! for i in MyIdx::iter() {
//! println!("{:?}", i);
//! }
//! Ok(())
//! }
//! ```
//!
//! # Compile-time guarantees
//!
//! This is the reason to use Andex instead of a plain array in the
//! first play, right? Below is a list of some of the compile-time
//! restrictions that we get.
//!
//! - We can't index [`AndexableArray`] with a `usize`.
//!
//! The following code doesn't compile:
//!
//! ```compile_fail
//! use andex::*;
//! enum MyIdxMarker {}
//! type MyIdx = Andex<MyIdxMarker, 12>;
//! type MyU32 = AndexableArray<MyIdx, u32, { MyIdx::SIZE }>;
//!
//! let myu32 = MyU32::default();
//!
//! // Error: can't index myu32 with a usize
//! println!("{}", myu32[0]);
//! ```
//!
//! - We can't create a const [`Andex`] with an out-of-bounds value.
//!
//! The following code doesn't compile:
//!
//! ```compile_fail
//! use andex::*;
//! enum MyIdxMarker {}
//! type MyIdx = Andex<MyIdxMarker, 12>;
//!
//! // Error: can't create out-of-bounds const:
//! const myidx : MyIdx = MyIdx::new::<13>();
//! ```
//!
//! - We can't index [`AndexableArray`] with a different Andex, even when
//! it has the same size. This is what using different markers gets
//! us.
//!
//! The following code doesn't compile:
//!
//! ```compile_fail
//! use andex::*;
//!
//! enum MyIdxMarker {}
//! type MyIdx = Andex<MyIdxMarker, 12>;
//! type MyU32 = AndexableArray<MyIdx, u32, { MyIdx::SIZE }>;
//!
//! enum TheirIdxMarker {}
//! type TheirIdx = Andex<TheirIdxMarker, 12>;
//! type TheirU32 = AndexableArray<TheirIdx, u32, { TheirIdx::SIZE }>;
//!
//! let myu32 = MyU32::default();
//! let theirIdx = TheirIdx::FIRST;
//!
//! // Error: can't index a MyU32 array with TheirIdx
//! println!("{}", myu32[theirIdx]);
//! ```
pub use *;