https://github.com/lpenz/andex
Safe, strongly typed array indexes and wrappers for rust, with zero dependencies
https://github.com/lpenz/andex
array indexing rust rust-crate rust-library
Last synced: 3 days ago
JSON representation
Safe, strongly typed array indexes and wrappers for rust, with zero dependencies
- Host: GitHub
- URL: https://github.com/lpenz/andex
- Owner: lpenz
- License: mit
- Created: 2021-08-02T15:45:19.000Z (almost 4 years ago)
- Default Branch: main
- Last Pushed: 2024-06-21T20:20:59.000Z (11 months ago)
- Last Synced: 2025-04-22T21:16:42.044Z (26 days ago)
- Topics: array, indexing, rust, rust-crate, rust-library
- Language: Rust
- Homepage:
- Size: 62.5 KB
- Stars: 4
- Watchers: 2
- Forks: 1
- Open Issues: 0
-
Metadata Files:
- Readme: README.md
- License: LICENSE
Awesome Lists containing this project
README
[](https://github.com/lpenz/andex/actions/workflows/ci.yml)
[](https://coveralls.io/github/lpenz/andex?branch=main)
[](https://crates.io/crates/andex)
[](https://docs.rs/andex)# andex
*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 types
[`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
enum MyIdxMarker {}
```
- Create a type alias for the [`Andex`] type that's parameterized
with that type:
```rust
type MyIdx = Andex;
```
- Create a type alias for the [`AndexableArray`] type that's
indexed by the [`Andex`] alias created above:
```rust
type MyU32 = AndexableArray;
// 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
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` that has to be
checked or explicitly ignored:
```rust
if let Ok(first) = MyIdx::try_from(0) {
// ...
}
```- Via `FIRST` and `LAST`:
```rust
const first : MyIdx = MyIdx::FIRST;
let last = MyIdx::LAST;
```- By iterating:
```rust
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
type MyU32 = AndexableArray;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
let myu32 = MyU32::from([8; MyIdx::SIZE]);
```
- Collecting an iterator with the proper elements and size:
```rust
let myu32 = (0..12).collect::();
```
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;// Create the array wrapper:
type MyU32 = AndexableArray;// We can create other arrays indexable by the same Andex:
type MyF64 = AndexableArray;fn main() -> Result<(), Box> {
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:
```rust
use andex::*;
enum MyIdxMarker {}
type MyIdx = Andex;
type MyU32 = AndexableArray;fn main() {
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:
```rust
use andex::*;
enum MyIdxMarker {}
type MyIdx = Andex;fn main() {
// 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:
```rust
use andex::*;enum MyIdxMarker {}
type MyIdx = Andex;
type MyU32 = AndexableArray;enum TheirIdxMarker {}
type TheirIdx = Andex;
type TheirU32 = AndexableArray;fn main() {
let myu32 = MyU32::default();
let theirIdx = TheirIdx::FIRST;// Error: can't index a MyU32 array with TheirIdx
println!("{}", myu32[theirIdx]);
}
```## Alternatives
These alternatives may fit better cases where we need unbound indexes
(maybe for vector):- [safe_index](https://crates.io/crates/safe_index)
- [typed-index-collections](https://crates.io/crates/typed-index-collections)[`Andex`]: https://docs.rs/andex/0/andex/struct.Andex.html
[`AndexableArray`]: https://docs.rs/andex/0/andex/struct.AndexableArray.html