13 releases
new 0.4.1 | Nov 13, 2024 |
---|---|
0.4.0 | Jul 3, 2024 |
0.3.4 | Mar 28, 2023 |
0.3.3 | Dec 30, 2022 |
0.1.0 | Apr 13, 2021 |
#581 in Data structures
100 downloads per month
31KB
664 lines
Ordes
A crate for treating arrays and tuples a little bit more like vectors.
At the moment, this crate provides .pop()
, .rest()
, .push(val)
, and .cons(val)
methods
through the OrdesPop
, OrdesRest
, OrdesPush
, and OrdesCons
traits respectively, and
additionally provides the .split()
, .concat(arr)
, .remove()
, and .insert(val)
methods
through the OrdesSplit
, OrdesConcat
, OrdesRemove
, and OrdesInsert
traits respectively
through the const_generics
crate feature, which requires a nightly compiler and enables the
incomplete generic_const_exprs
feature. These last four traits are only available and implemented
through this feature since each individual one would require a nonlinearly-scaling number of trait
implementations, and I'm not sure anyone would like to put up with those compile times. For this
same reason they're also only implemented on arrays.
This crate was born out of my stubborn desire to use iterators wherever possible instead of for
loops, which makes packing data together a bit of a pain in the neck at times. For instance,
consider an iterator producing all lowercase four letter "words":
('a'..='z')
.flat_map(|l1| ('a'..='z').map(move |l2| (l1, l2)))
.flat_map(|(l1, l2)| ('a'..='z').map(move |l3| (l1, l2, l3)))
.flat_map(|(l1, l2, l3)| ('a'..='z').map(move |l4| (l1, l2, l3, l4)))
.for_each(|(l1, l2, l3, l4)| println!("{}{}{}{}", l1, l2, l3, l4));
As we can see, this is both:
- Not particularly pleasant to write or look at
- Not something a sane person would do
This crate offers an alternative:
use ordes::OrdesPush;
('a'..='z')
.flat_map(|l1| ('a'..='z').map(move |l2| (l1, l2)))
.flat_map(|chars| ('a'..='z').map(move |l3| chars.push(l3)))
.flat_map(|chars| ('a'..='z').map(move |l4| chars.push(l4)))
.for_each(|(l1, l2, l3, l4)| println!("{}{}{}{}", l1, l2, l3, l4));
There's not any real magic going on here. .push(val)
produces a value of a new type. In the first
example, chars.push(l3)
, the input type is (char, char)
and the output is (char, char, char)
.
Similar story for chars.push(l4)
- input of (char, char, char)
, output of
(char, char, char, char)
. A nearly identical implementation can be made with arrays as well:
use ordes::OrdesPush;
('a'..='z')
.flat_map(|l1| ('a'..='z').map(move |l2| [l1, l2]))
.flat_map(|chars| ('a'..='z').map(move |l3| chars.push(l3)))
.flat_map(|chars| ('a'..='z').map(move |l4| chars.push(l4)))
.for_each(|[l1, l2, l3, l4]| println!("{}{}{}{}", l1, l2, l3, l4));
In this case, chars.push(l3)
takes in [char; 2]
and produces [char; 3]
, and chars.push(l4)
takes in [char; 3]
and produces [char; 4]
.
A silly use case that came up recently that prompted me to add two new traits - OrdesRest
and
OrdesCons<T>
- is tagging a fixed-size array with an indicator byte before serialising that into a
bytestream:
use std::net::IpAddr;
use ordes::OrdesCons;
fn ipaddr_bytestream(addr: IpAddr) -> impl Iterator<Item = u8> {
let mut data = [0; 17];
let len = match addr {
IpAddr::V4(addr) => {
data[0..5].copy_from_slice(&addr.octets().cons(4));
5
},
IpAddr::V6(addr) => {
data[0..17].copy_from_slice(&addr.octets().cons(6));
17
}
};
data.into_iter().take(len)
}
Without .cons(_)
, it would be necessary to include another line in each branch writing 4
or 6
to data[0]
. Small improvement, but I'm petty enough to count it.
In this crate I implement OrdesPop
, OrdesRest
, OrdesPush<T>
, and OrdesCons<T>
for arrays and
tuples, but with some caveats:
- On stable, all traits are only implemented for array lengths up to 32 (by default, there's features for up to 1024).
- On both nightly and stable, all traits are only implemented for tuples up to 32 types (by default, there's features for up to 1024).
All trait methods exposed by this crate consume self
and produce a new type that's longer or
shorter (or with an extra or dropping the last type in the case of tuples). I'm well aware this is a
very limited use case, but it's useful to me, and hopefully a few other people.
Note about the const_generics
feature
In order to use this crate with the const_generics
feature, you must also put
#![feature(generic_const_exprs)]
somewhere in your crate (lib.rs
, main.rs
, etc.). Any trait
impl in this crate that relies on ConstCheck<B>
will have that bound fail if the feature is not
enabled.
Dependencies
~1.5MB
~36K SLoC