[][src]Crate tuple_list

Crate for variadic tuple metaprogramming.

Rationale

As of writing this crate, rust does not support variadic generics and does not allow to reason about tuples in general.

Most importantly, rust does not allow one to generically implement a trait for all tuples whose elements implement it.

This crate attempts to fill the gap by providing a way to recursively define traits for tuples.

Tuple lists

Tuple (A, B, C, D) can be unambiguously mapped into recursive tuple (A, (B, (C, (D, ())))).

On each level it consists of a pair (Head, Tail), where Head is tuple element and Tail is a remainder of the list. For last element Tail is an empty list.

Unlike regular flat tuples, such recursive tuples can be effectively reasoned about in rust.

This crate calls such structures "tuple lists" and provides a set of traits and macros allowing one to conveniently work with them.

Example 1: CustomDisplay recursive trait

Let's create a simple Display-like trait implemented for all tuples whose elements implement it.

// Define trait and implement it for several standard types.
trait CustomDisplay {
    fn fmt(&self) -> String;
}
impl CustomDisplay for i32  { fn fmt(&self) -> String { self.to_string() } }
impl CustomDisplay for bool { fn fmt(&self) -> String { self.to_string() } }
impl CustomDisplay for &str { fn fmt(&self) -> String { self.to_string() } }
 
// Now we have to implement trait for empty tuple,
// thus defining initial condition.
impl CustomDisplay for () {
    fn fmt(&self) -> String { String::new() }
}
 
// Now we can implement trait for a non-empty tuple list, 
// this defining recursion and supporting tuple lists of arbitrary length.
impl<Head, Tail> CustomDisplay for (Head, Tail) where
    Head: CustomDisplay,
    Tail: CustomDisplay,
{
    fn fmt(&self) -> String {
        let (head, tail) = self;
        return format!("{} {}", head.fmt(), tail.fmt());
    }
}
 
// `tuple_list!` macro creates tuple lists from list of arguments.
use tuple_list::tuple_list;

// Ensure `fmt` is called for each element.
let tuple_list = tuple_list!(2, false, "abc");
assert_eq!(
    tuple_list.fmt(),
    "2 false abc ",
);
 
// Since tuple lists implement `CustomDisplay`, they can
// be elements in other tuple lists implementing `CustomDisplay`.
let nested_tuple_list = tuple_list!(2, false, "abc", tuple_list!(3, true, "def"));
assert_eq!(
    nested_tuple_list.fmt(),
    "2 false abc 3 true def  ",
);
Run

Example 2: PlusOne recursive trait

Let's create a trait which adds one to each element of a tuple, behaving differently depending on element type.

// Define trait and implement it for several primitive types.
trait PlusOne {
    fn plus_one(&mut self);
}
impl PlusOne for i32    { fn plus_one(&mut self) { *self += 1; } }
impl PlusOne for bool   { fn plus_one(&mut self) { *self = !*self; } }
impl PlusOne for String { fn plus_one(&mut self) { self.push('1'); } }
 
// Now we have to implement trait for empty tuple,
// thus defining initial condition.
impl PlusOne for () {
    fn plus_one(&mut self) {}
}
 
// Now we can implement trait for a non-empty tuple list, 
// this defining recursion and supporting tuple lists of arbitrary length.
impl<Head, Tail> PlusOne for (Head, Tail) where
    Head: PlusOne,
    Tail: PlusOne,
{
    fn plus_one(&mut self) {
        self.0.plus_one();
        self.1.plus_one();
    }
}
 
// Now we can use our trait on tuple lists.
let mut tuple_list = tuple_list!(2, false, String::from("abc"));
tuple_list.plus_one();
 
// tuple_list! macro also allows us to unpack tuple lists
let tuple_list!(a, b, c) = tuple_list;
assert_eq!(a, 3);
assert_eq!(b, true);
assert_eq!(&c, "abc1");
Run

Example 3: SwapStringAndInt recursive trait

Let's implement a trait which converts i32 to String and vice versa.

This example is way more complex because it maps tuple list into another tuple list.

// Let's define and implement trait for i32 and String
// so that it converts String to i32 and vice versa.
trait SwapStringAndInt {
    type Other;
    fn swap(self) -> Self::Other;
}
impl SwapStringAndInt for i32 {
    type Other = String;
    fn swap(self) -> String { self.to_string() }
}
impl SwapStringAndInt for String {
    type Other = i32;
    fn swap(self) -> i32 { self.parse().unwrap() }
}
 
// Now we have to implement trait for empty tuple,
// thus defining initial condition.
impl SwapStringAndInt for () {
    type Other = ();
    fn swap(self) {}
}
 
// Now we can implement trait for a non-empty tuple list, 
// this defining recursion and supporting tuple lists of arbitrary length.
impl<Head, Tail> SwapStringAndInt for (Head, Tail) where Head: SwapStringAndInt, Tail: SwapStringAndInt {
    type Other = (Head::Other, Tail::Other);
    fn swap(self) -> Self::Other {
        (self.0.swap(), self.1.swap())
    }
}
 
let original = tuple_list!(4, String::from("2"), 7, String::from("13"));
 
// Tuple lists implement `SwapStringAndInt` by calling `SwapStringAndInt::swap`
// on each member and returnign tuple list of resulting values.
let swapped = original.swap();
 
// Not that types of elements have changed.
assert_eq!(
    swapped,
    tuple_list!(String::from("4"), 2, String::from("7"), 13),
);
 
// Since tuple lists now implement SwapStringAndInt,
// they can even contain nested tuple lists:
let nested = tuple_list!(tuple_list!(1, String::from("2")), 3);
let nested_swapped = nested.swap();
assert_eq!(
    nested_swapped,
    tuple_list!(tuple_list!(String::from("1"), 2), String::from("3")),
);
 
// Now, we can't implement `SwapStringAndInt` for regular tuples
// because it would conflict with tuple list implementation,
// but we can define helper function allowing us to use `swap`
// on regular tuples seamlessly.
use tuple_list::Tuple;
use tuple_list::TupleList;
 
// Argument of this function is a regular tuple, not a tuple list.
fn swap<T, TL, OtherTL>(tuple: T) -> OtherTL::Tuple where
    T: Tuple<TupleList=TL>, // argument type
    TL: TupleList + SwapStringAndInt<Other=OtherTL>, // tuple list corresponding to argument tuple
    OtherTL: TupleList, // another tuple list, result of `SwapStringAndInt::swap` applied to original tuple list
{
    tuple.to_tuple_list().swap().to_tuple()
}
 
// Now we can indirectly use `SwapStringAndInt` with regular tuples.
let original_tuple = (4, String::from("2"), 7, String::from("13"));
let swapped_tuple = swap(original_tuple);
assert_eq!(
    swapped_tuple,
    (String::from("4"), 2, String::from("7"), 13),
);
Run

Tuple lists and tuples interoperability

This crate defines Tuple and TupleList traits, which are automatically implemented and allow you to convert tuples into tuple lists and vice versa.

Best way to handle interoperability is to store your data as tuple lists and convert to tuples if necessary.

Anoter reasonable alternative is to use helper function like the one described in SwapStringAndInt example.

Please note that tuple/tuple list conversions are destructive and consume the original, which may pose a problem in some cases.

Implementing recursive traits for regular tuples

Previous examples described how to implement recursive traits for tuple lists. If you really, really need to implement recursive traits on regular tuples, it's possible, but not recommended. Such implementations are extremely complex and have serious limitations.

There are two major problems:

  1. Trait implementation for regular tuples may conflict with implementation for tuple lists.
  2. Reference to tuple cannot be transformed into reference to tuple list.

Both problems are solveable, but working around them makes code extremely complex.

Depending on your use case, you may have to do this:

  1. In order to avoid implementations conflict, add separate trait mirroring the main one, which will be used for typed list only.
  2. Handling self and &self is very different in recursive traits for regular tuples. As a result you may have to split your trait into parts that accept self in the same way.
  3. You may have to add generic lifetime to your original trait. It's not mandatory, but without it nesting won't work properly.

All use cases can be broadly grouped into two types depending on how self is passed into function.

Implementing traits with functions which accept self by value

The major problem in this case is the fact that trait implementation for tuple list will conflict with trait implementation for regular tuple.

General solution is to create separate trait mirroring the main one, which will only used for type lists.

Naive solution will break nesting, but there is a way to solve that with pretty complex birecursion.

For details please see tuple_lists::tests::swap_string_and_int_dual_traits_recursion.

Implementing traits with functions which accept self by reference or mutable reference

General idea is to convert refernce to tuple into tuple of references, then convert tuple of references into tuple list, and then use recursive traits as usual.

The main problem is that recursive trait implementation must have generic lifetime argument in order to track lifetimes of references in tuple list.

This makes it impossible to unambiguously implement recursive trait for regular tuples, unless original trait has free generic lifetime argument.

For an example of this approach see tuple_lists::tests::plus_one_tuple_list_trait_with_lifetime.

If it is impossible or not practical to add generic lifetime argument to the original trait, then it's possible to only implement recursive trait for tuple lists, as a result breaking nesting.

For an example of this approach see tuple_lists::tests::plus_one_tuple_list_trait_without_lifetime.

Rust unstable fetaures and future

This crate works at the edge of what's possible with current type system of rust.

Following planned or unstable features will significantly affect this crate when implemented:

  1. Associated generic types will remove extra lifetime argument requirement in recursive traits implemented for regular tuples.
  2. Trait specialization will allow crate users to use TupleCons and NonEmptyTuple traits to define traits directly on regular tuples, without recursion through tuple lists.
  3. Obviously, as soon as rust implements variadic generics, this crate will become obsolete and deprecated.

Macros

tuple_list

Macro creating tuple list values from list of expressions.

tuple_list_type

Macro creating tuple list types from list of element types.

Traits

NonEmptyTuple

Trait allowing to recursively deconstruct tuples.

Tuple

Trait providing conversion from tuple into tuple list.

TupleAsRef

Trait providing conversion from references to tuples into tuples of references.

TupleCons

Trait providing tuple construction function, allows to prepend a value to a tuple.

TupleList

Trait providing conversion from tuple list into tuple.