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::*;