andex/
lib.rs

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