6

In Rust, is there any way to use traits and impls to (recursively) flatten tuples?

If it helps, something that works with N nested pairs is a good start

trait FlattenTuple {
    fn into_flattened(self) -> /* ??? */
}

// such that
assert_eq!((1, (2, 3)).into_flattened(), (1, 2, 3))

It would be even better if it could be extended work with any kind of nested tuple such that:

assert_eq!(((1, 2), 2, (3, (4, 5))).into_flattened(), (1, 2, 2, 3, 4, 5))
Shepmaster
  • 388,571
  • 95
  • 1,107
  • 1,366
lloydmeta
  • 1,289
  • 1
  • 15
  • 25

1 Answers1

5

Maybe for certain small definitions of "flatten", but realistically not really.

Start with the most specific implementation:

trait FlattenTuple {
    fn into_flattened(self) -> (u8, u8, u8);
}

impl FlattenTuple for (u8, (u8, u8)) {
    fn into_flattened(self) -> (u8, u8, u8) {
        (self.0, (self.1).0, (self.1).1)
    }
}

Then make it a bit more generic:

trait FlattenTuple {
    type Output;
    fn into_flattened(self) -> Self::Output;
}

impl<A, B, C> FlattenTuple for (A, (B, C)) {
    type Output = (A, B, C);

    fn into_flattened(self) -> Self::Output {
        (self.0, (self.1).0, (self.1).1)
    }
}

And then repeat for every possible permutation:

impl<A, B, C, D, E, F> FlattenTuple for ((A, B), C, (D, (E, F))) {
    type Output = (A, B, C, D, E, F);

    fn into_flattened(self) -> Self::Output {
        ((self.0).0, (self.0).1, self.1, (self.2).0, ((self.2).1).0, ((self.2).1).1)
    }
}

These two implementations cover your two cases.

However, you'd then have to enumerate every input type you'd like, probably via code generation. There's no way I'm aware of to "inspect" the input type and then "splice" it into the output type.

You can even try to write something somewhat recursive:

impl<A, B, C, D, E, F> FlattenTuple for (A, B)
    where A: FlattenTuple<Output = (C, D)>,
          B: FlattenTuple<Output = (E, F)>,
{
    type Output = (C, D, E, F);

    fn into_flattened(self) -> Self::Output {
        let (a, b) = self;
        let (c, d) = a.into_flattened();
        let (e, f) = b.into_flattened();

        (c, d, e, f)
    }
}

But this will quickly run into base-case issues: the terminal 42 doesn't implement FlattenTuple, and if you try to impl<T> FlattenTuple for T you will hit conflicting trait implementations.

Shepmaster
  • 388,571
  • 95
  • 1,107
  • 1,366
  • 1
    Thanks. Before I posted my question I had tried a few different ways but kept running into the conflicting implementations problem. For example, implementing `for (A, B)` means that you can't implement for `(A, (B, C))` and `(A, (B, (C, D)))` and so forth to solve the `N` nested pairs case, and I'm not sure the situation changes with where clauses. – lloydmeta Oct 21 '16 at 15:04
  • @lloydmeta: Yeah, this conflicting implementation thing is really annoying :( – Matthieu M. Oct 21 '16 at 15:30
  • @MatthieuM. you know that it's far better for our code to not be ambiguous. Perhaps specialization will solve this issue, or maybe it wont... – Shepmaster Oct 21 '16 at 15:32
  • @Shepmaster: I agree with the absence of ambiguity, but I've used C++ so much in the past that specialization is ingrained in my brain ^^ – Matthieu M. Oct 21 '16 at 15:34