4 releases
0.1.3 | Jul 13, 2021 |
---|---|
0.1.2 | May 13, 2020 |
0.1.1 | May 11, 2020 |
0.1.0 | May 11, 2020 |
#819 in Rust patterns
9,680 downloads per month
Used in 13 crates
(6 directly)
52KB
651 lines
Crate tuple_list
Rust crate for macro-free variadic tuple metaprogramming.
Provides a way to recursively define traits for tuples of arbitrary size.
Please see crate documentation for details and examples.
Licensed under MIT license.
lib.rs
:
Crate for macro-free 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: PlusOne
recursive trait
Let's create a trait which adds one to each element of a tuple list of arbitrary length, behaving differently depending on element type.
// `TupleList` is a helper trait implemented by all tuple lists.
// Its use is optional, but it allows to avoid accidentally
// implementing traits for something other than tuple lists.
use tuple_list::TupleList;
// 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 an 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,
// thus defining recursion and supporting tuple lists of arbitrary length.
impl<Head, Tail> PlusOne for (Head, Tail) where
Head: PlusOne,
Tail: PlusOne + TupleList,
{
fn plus_one(&mut self) {
self.0.plus_one();
self.1.plus_one();
}
}
// `tuple_list!` as a helper macro used to create
// tuple lists from a list of arguments.
use tuple_list::tuple_list;
// 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");
Example 2: CustomDisplay
recursive trait
Let's create a simple Display
-like trait implemented for all tuples
lists whose elements implement it.
// Define the 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 an empty tuple,
// thus defining initial condition.
impl CustomDisplay for () {
fn fmt(&self) -> String { String::from("<empty>") }
}
// In order to avoid having trailing spaces, we need
// custom logic for tuple lists of exactly one element.
//
// The simplest way is to use `TupleList::TUPLE_LIST_SIZE`
// associated constant, but there is also another option.
//
// Instead of defining initial condition for empty tuple list
// and recursion for non-empty ones, we can define *two*
// initial conditions: one for an empty tuple list and
// one for tuple lists of exactly one element.
// Then we can define recursion for tuple lists of two or more elements.
//
// Here we define second initial condition for tuple list
// of exactly one element.
impl<Head> CustomDisplay for (Head, ()) where
Head: CustomDisplay,
{
fn fmt(&self) -> String {
return self.0.fmt()
}
}
// Recursion step is defined for all tuple lists
// longer than one element.
impl<Head, Next, Tail> CustomDisplay for (Head, (Next, Tail)) where
Head: CustomDisplay,
(Next, Tail): CustomDisplay + TupleList,
Tail: TupleList,
{
fn fmt(&self) -> String {
return format!("{} {}", self.0.fmt(), self.1.fmt());
}
}
// 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` themselves, 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",
);
Example 3: SwapStringAndInt
recursive trait
Let's implement a trait which converts i32
to String
and vice versa.
This example is way more complex that the other because it maps one tuple list into another tuple list.
// Let's define and implement a 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 an 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,
// thus defining recursion and supporting tuple lists of arbitrary length.
impl<Head, Tail, TailOther> SwapStringAndInt for (Head, Tail) where
Head: SwapStringAndInt,
Tail: SwapStringAndInt<Other=TailOther> + TupleList,
TailOther: TupleList,
{
type Other = (Head::Other, Tail::Other);
fn swap(self) -> Self::Other {
(self.0.swap(), self.1.swap())
}
}
// Tuple lists implement `SwapStringAndInt` by calling `SwapStringAndInt::swap`
// on each member and returning tuple list of resulting values.
let original = tuple_list!(4, String::from("2"), 7, String::from("13"));
let swapped = tuple_list!(String::from("4"), 2, String::from("7"), 13);
assert_eq!(original.swap(), swapped);
Example 4: prepend and append functions
Let's implement append and prepend functions for tuple lists.
// Prepend is a trivial operation for tuple lists.
// We just create a new pair from prepended element
// and the remainder of the list.
fn prepend<T, Tail: TupleList>(value: T, tail: Tail) -> (T, Tail) {
(value, tail)
}
// Append is a bit more comples. We'll need a trait for that.
trait Append<T>: TupleList {
type AppendResult: TupleList;
fn append(self, value: T) -> Self::AppendResult;
}
// Implement append for an empty tuple list.
impl<T> Append<T> for () {
type AppendResult = (T, ());
// Append for an empty tuple list is quite trivial.
fn append(self, value: T) -> Self::AppendResult { (value, ()) }
}
// Implement append for non-empty tuple list.
impl<Head, Tail, T> Append<T> for (Head, Tail) where
Self: TupleList,
Tail: Append<T>,
(Head, Tail::AppendResult): TupleList,
{
type AppendResult = (Head, Tail::AppendResult);
// Here we deconstruct tuple list,
// recursively call append on the
// tail of it, and then reconstruct
// the list using the new tail.
fn append(self, value: T) -> Self::AppendResult {
let (head, tail) = self;
return (head, tail.append(value));
}
}
// Now we can use our append and prepend functions
// on tuple lists.
let original = tuple_list!( 1, "foo", false);
let appended = tuple_list!( 1, "foo", false, 5);
let prepended = tuple_list!(5, 1, "foo", false);
assert_eq!(original.append(5), appended);
assert_eq!(prepend(5, original), prepended);
Example 5: reverse function
We can also implement a function which reverses elements of a tuple list.
// Rewind is a helper trait which maintains two tuple lists:
// `Todo` (which is `Self` for the trait) is the remainder of a tuple list to be reversed.
// `Done` is already reversed part of it.
trait Rewind<Done: TupleList> {
// RewindResult is the type of fully reversed tuple.
type RewindResult: TupleList;
fn rewind(self, done: Done) -> Self::RewindResult;
}
// Initial condition.
impl<Done: TupleList> Rewind<Done> for () {
type RewindResult = Done;
// When nothing is left to do, just return reversed tuple list.
fn rewind(self, done: Done) -> Done { done }
}
// Recursion step.
impl<Done, Next, Tail> Rewind<Done> for (Next, Tail) where
Done: TupleList,
(Next, Done): TupleList,
Tail: Rewind<(Next, Done)> + TupleList,
{
type RewindResult = Tail::RewindResult;
// Strip head element from `Todo` and prepend it to `Done` list,
// then recurse on remaining tail of `Todo`.
fn rewind(self, done: Done) -> Self::RewindResult {
let (next, tail) = self;
return tail.rewind((next, done));
}
}
// Helper function which uses `Rewind` trait to reverse a tuple list.
fn reverse<T>(tuple: T) -> T::RewindResult where
T: Rewind<()>
{
// Initial condition, whole tuple list is `Todo`,
// empty tuple is `Done`.
tuple.rewind(())
}
// Now `reverse` is usable on tuple lists.
let original = tuple_list!(1, "foo", false);
let reversed = tuple_list!(false, "foo", 1);
assert_eq!(reverse(original), reversed);
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 them to tuples if necessary.
Alternatively it's possible to create a helper function which accepts a tuple, converts it to a tuple list, calls trait method and then returns the result.
Here's an example of such function for Append
trait from previous example:
// `Tuple` trait is needed to access conversion function.
use tuple_list::Tuple;
fn append<T, AppendedTupleList, Elem>(tuple: T, elem: Elem) -> AppendedTupleList::Tuple where
T: Tuple, // input argument tuple
T::TupleList: Append<Elem, AppendResult=AppendedTupleList>, // input argument tuple list
AppendedTupleList: TupleList, // resulting tuple list
{
// Convert tuple into tuple list, append the element
// and convert the result back into tuple.
tuple.into_tuple_list().append(elem).into_tuple()
}
// Unlike `Append` trait which is defined for tuple lists,
// `append` function works on regular tuples.
let original = (1, "foo", false);
let appended = (1, "foo", false, 5);
assert_eq!(append(original, 5), appended);
Please note that tuple/tuple list conversions are destructive and consume the original, which seemingly prevents you from, for example, modifying content of the original tuple.
In order to alleviate this problem, tuple_list
crate
introduces AsTupleOfRefs
trait, which allows one to
convert reference to tuple into tuple of references.
The idea is that if you you can convert reference to tuple into tuple of references, then convert tuple of references into tuple list and then use recursive trait as usual.
Let's modify PlusOne
trait example so it can be used
to modify regular tuples:
// 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 define a new trait
// specifically for tuple lists of references.
//
// Unlike the original, it accepts `self` by value.
trait PlusOneTupleList: TupleList {
fn plus_one(self);
}
// Now we have to implement trait for an empty tuple,
// thus defining initial condition.
impl PlusOneTupleList for () {
fn plus_one(self) {}
}
// Now we can implement trait for a non-empty tuple list,
// thus defining recursion and supporting tuple lists of arbitrary length.
//
// Note that we're implementing `PlusOneTupleList` for
// *tuple list of mutable references*, and as a result
// head of the list is a mutable reference, not a value.
impl<Head, Tail> PlusOneTupleList for (&mut Head, Tail) where
Self: TupleList,
Head: PlusOne,
Tail: PlusOneTupleList,
{
fn plus_one(self) {
self.0.plus_one();
self.1.plus_one();
}
}
// Now let's define a helper function operating on regular tuples.
fn plus_one<'a, T, RT>(tuple: &'a mut T) where
T: AsTupleOfRefs<'a, TupleOfMutRefs=RT>,
RT: Tuple + 'a,
RT::TupleList: PlusOneTupleList,
{
tuple.as_tuple_of_mut_refs().into_tuple_list().plus_one()
}
// Now we can use this helper function on regular tuples.
let mut tuple = (2, false, String::from("abc"));
plus_one(&mut tuple);
assert_eq!(tuple.0, 3);
assert_eq!(tuple.1, true);
assert_eq!(&tuple.2, "abc1");
As you can see, working with tuples requires a lot of bolierplate code. Unless you have preexisting code you need to support, it's generally better to use tuple lists directly, since they are much easier to work with.
Implementing recursive traits for regular tuples
Implementing recursive traits for regular tuples poses
certain problems. As of now it is possible within
tuple_list
crate, but quickly leads to orphan rules
violations when used outside of it.
You can see a working example of a trait implemented for
regular tuples in tuple_list::test::all_features
,
but it's overly complex and pretty much experimental.
It should be possible to define recursive traits on regular tuples once trait specialization feature is implemented in Rust.