Is there an easy way to cast entire tuples of scalar values at once?

I want to cast a (u16, u16) to a (f32, f32). This is what I tried:

let tuple1 = (5u16, 8u16);
let tuple2 = tuple1 as (f32, f32);

Ideally, I would like to avoid writing

let tuple2 = (tuple1.0 as f32, tuple1.1 as f32);

Solution 1:

There's no built-in way to do this, but one can do it with a macro:

macro_rules! tuple_as {
    ($t: expr, ($($ty: ident),*)) => {
        {
            let ($($ty,)*) = $t;
            ($($ty as $ty,)*) 
        }
    }
}

fn main() {
    let t: (u8, char, isize) = (97, 'a', -1);

    let other = tuple_as!(t, (char, i32, i8));

    println!("{:?}", other);
}

Prints ('a', 97, -1).

The macro only works for casting between types with names that are a single identifier (that's what the : ident refers to), since it reuses those names for binding to the elements of the source tuple to be able to cast them. All primitive types are valid single identifiers, so it works well for those.

Solution 2:

No, you cannot. This is roughly equivalent to "can I cast all the fields in a struct to different types all at once?".

You can write a generic extension trait which can do this conversion for you, the only problem is that I don't believe there's any existing generic "conversion" trait which also has a u16 -> f32 implementation defined.

If you really want a function that does this, here is an as-minimal-as-I-could-make-it skeleton you can build on:

trait TupleCast<T> {
    type Output;
    fn tuple_cast(self) -> <Self as TupleCast<T>>::Output;
}

impl<T> TupleCast<T> for () {
    type Output = ();
    fn tuple_cast(self) -> <() as TupleCast<T>>::Output {
        ()
    }
}

impl<S, T> TupleCast<T> for (S,) where S: CustomAs<T> {
    type Output = (T,);
    fn tuple_cast(self) -> <(S,) as TupleCast<T>>::Output {
        (self.0.custom_as(),)
    }
}

impl<S, T> TupleCast<T> for (S, S) where S: CustomAs<T> {
    type Output = (T, T);
    fn tuple_cast(self) -> <(S, S) as TupleCast<T>>::Output {
        (self.0.custom_as(), self.1.custom_as())
    }
}

// You would probably have more impls, up to some size limit.

// We can't use std::convert::From, because it isn't defined for the same
// basic types as the `as` operator is... which kinda sucks.  So, we have
// to implement the desired conversions ourselves.
//
// Since this would be hideously tedious, we can use a macro to speed things
// up a little.

trait CustomAs<T> {
    fn custom_as(self) -> T;
}

macro_rules! custom_as_impl {
    ($src:ty:) => {};
    ($src:ty: $dst:ty) => {
        impl CustomAs<$dst> for $src {
            fn custom_as(self) -> $dst {
                self as $dst
            }
        }
    };
    ($src:ty: $dst:ty, $($rest:ty),*) => {
        custom_as_impl! { $src: $dst }
        custom_as_impl! { $src: $($rest),* }
    };
}

// You could obviously list others, or do manual impls.
custom_as_impl! { u16: u16, u32, u64, i32, i64, f32, f64 }

fn main() {
    let x: (u16, u16) = (1, 2);
    let y: (f32, f32) = x.tuple_cast();
    println!("{:?}", y);
}