ordes 0.2.3

A crate for treating arrays and tuples a little bit more like vectors.
Documentation

Ordes

A crate for treating arrays and tuples a little bit more like vectors.

At the moment, this crate only provides .push(val) and .pop() through the OrdesInc and OrdesDec methods respectively, though I think I could potentially add .insert(val), .concatenate(array), and .remove(index) in the nightly version without too much trouble. Soon™, I guess.

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:

  1. Not particularly pleasant to write or look at
  2. 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
        }
    };
    IntoIter::new(data).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).

Note that on nightly, both traits are implemented for arrays of arbitrary lengths through the incomplete const_generics and const_evaluatable_checked features. I wouldn't recommend that you use this, as it currently causes an ICE and two errors (one, two, so please use the stable version for the time being.

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.