11 releases
0.3.4 | Mar 28, 2023 |
---|---|
0.3.3 | Dec 30, 2022 |
0.3.2 | Jun 19, 2022 |
0.2.4 | Jul 3, 2021 |
0.1.0 | Apr 13, 2021 |
#764 in Rust patterns
42 downloads per month
26KB
555 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.
Dependencies
~1.5MB
~33K SLoC