[−][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]);
Motivation
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<String>) { /* 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<String>>(v: V) { /* do stuff */ }
Then, convert the argument
fn foo<V: TupleOrVec<String>>(v: V) { let v = v.inner(); /* 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
Limitations
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.
Performance
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.
Traits
RepeatedTuple | A trait implemented on all tuples composed of a single type.1 |
TupleOrVec | A trait implemented on repeated tuples and vectors. |