[][src]Crate tuple_conv

A simple crate for providing conversions from tuples to vectors and boxed slices.

Small example

This crate is pretty simple. Here's a trivial example.

let t = (0, 1, 2);
let v = t.to_vec();
assert_eq!(v, [0, 1, 2]);


The primary motivation for using these tools is for syntactic elegance. In APIs where a small, but variable number of arguments of a single type is wanted, it's standard to use a Vec<T>. This can become cumbersome for the user, however - particularly when we have nested types. See, for example:

fn do_something_2d(a: Vec<Vec<i32>>) { /* ... */ }

do_something_2d(vec![vec![1, 2, 3],
                     vec![4, 5, 6],
                     vec![7, 8, 9]]);

Calling this function is somewhat cumbersome, and can be made cleaner with:

fn do_something_2d<T, S>(a: T) where
    T: RepeatedTuple<S>,
    S: RepeatedTuple<i32>,
{ /* ... */ }

do_something_2d(((1, 2, 3),
                 (4, 5, 6),
                 (7, 8, 9)));

Even though it starts to give us flashbacks from LISP, more of our code is made up of things we actually care about - gone with the vec macros everywhere. The primary benefit is simpler syntax.

Typical usage

Although we can use RepeatedTuple as a type restriction, this would usually be replacing a Vec, so there's a good chance we'd still like to allow it. The main usage for this crate is then with the TupleOrVec trait - it's implemented for all repeated tuples and for every Vec, which allows us to easily change the public-facing API to allow tuples without removing any functionality.

Here's how we'd go about doing that, given a function foo that takes some Vec:

fn foo(v: Vec<&str>) {
    /* do stuff */

// a typical way to call `foo`:
foo(vec!["bar", "baz"]);

The first step is to change the function signature to accept tuples:

extern crate tuple_conv;
use tuple_conv::TupleOrVec;

// ...

fn foo<V: TupleOrVec<&'static str>>(v: V) {
    /* do stuff */

Then, convert the argument

fn foo<V: TupleOrVec<&'static str>>(v: V) {
    let v = v.as_vec();
    /* do stuff */

And now we can call foo like this:

foo(("bar", "baz"));
foo(vec!["baz", "bar"]);

It is, however, worth keeping in mind the implications of large generic functions implemented for many types. This is discussed in more detail below.

Limitations and performance


Because each new tuple is a distinct type, we can only implement for finitely many tuple lengths. We've chosen to go up to tuples with 64 elements of the same type. If you find yourself needing more (although I suspect this will be unlikely), the source is relatively simple, and not too difficult to extend.

Additionally, because of the Rust's visibility rules for public traits, there isn't a good way to ensure that certain traits aren't implemented by others - like TupleOrVec for example. That being said, the trait is defined such that it shouldn't matter.


The details of the implementation are such that vectors are constructed in reverse, and Vec<_>.reverse() called, due to a limitation of Rust's macro system.

This is not very significant (only ~10% increase with tuples of length 64), but something worth considering for performance-critical code. For more detail, pull this repository on GitHub and run cargo bench.

There are two other considerations: time to compile and final binary size. These should usually be very minor - hardly noticeable. However: if you are having issues, keep this in mind: While these both may increase for functions that are being implemented on many types, it may be possible to reduce them by using public functions simply as wrappers for your internals that only take vectors.



A trait implemented on all tuples composed of a single type.1


A trait implemented on repeated tuples and vectors.