How to compute the sum of size of a nested tuple

I want to compute the the sum of size of a nested tuple recursively.

In my situation, the tuple can only be the form of (X, Y) or ().

For example, the sum of size of (i32, ((String, (String, f64)))) should be:

size_of::<i32>() + size_of::<String>() + size_of::<String>() + size_of::<f64>()

How can I achieve this?

I guess I need some type-level operations...

You can use size_of on the tuple directly, but there's no way to know the tuple's size if you just have its fields. This is because Rust represents tuples using the #[repr(Rust)] representation, so there aren't any guarantees for how tuples are laid out.

But this works:

size_of::<(X, Y)>()
3 Likes

I don't want to know the size of a tuple, I want to know the sum of the size of each leaf node of a nested tuple.

And do it on a generic parameter?

What about something like this?

use std::mem::size_of;

trait LeafSize: Sized {
    fn leaf_size() -> usize;
}

impl<A, B> LeafSize for (A, B) {
    fn leaf_size() -> usize {
        size_of::<A>() + size_of::<B>()
    }
}

macro_rules! impl_leaf_size {
    ( $($ty:ty),* $(,)?) => {
        $(
            impl LeafSize for $ty {
                fn leaf_size() -> usize {
                    size_of::<Self>()
                }
            }
        )*
    }
}

impl_leaf_size!(u32,  String);

(playground)

We can then check it like this:

fn main() {
    assert_eq!(<u32>::leaf_size(), size_of::<u32>());
    assert_eq!(<String>::leaf_size(), size_of::<String>());
    assert_eq!(
        <(u32, String)>::leaf_size(),
        size_of::<u32>() + size_of::<String>()
    );
}

Note that this will give you the size of the tuple's leaf nodes on the stack, and doesn't dereference heap allocations. It could also be implemented as an associate constant instead of a method with no loss of generality.

4 Likes

We can be more general using min_specialization (though full generality requires variadics):

#![feature(min_specialization)]

use std::mem;

trait Tuple {
    fn size() -> usize;
}

impl<T> Tuple for T {
    default fn size() -> usize {
        mem::size_of::<T>()
    }
}

macro_rules! impl_tuple {
    ( $first:ident, $($rest:ident,)* ) => {
        impl<$first, $($rest,)*> Tuple for ($first, $($rest,)*) {
            fn size() -> usize {
                <$first as Tuple>::size() $(+ <$rest as Tuple>::size())*
            }
        }
    
        impl_tuple!( $($rest,)* );
    };
    () => {
        // `()`, the empty tuple, is guaranteed to be zero-sized, so it's `size_of()`
        // is always 0 - what we want for it. So need no for a manual impl.
    };
}

impl_tuple!(
    A, B, C, D, E, F, G, H, I, J, K, L, M, N, O,
);

pub fn tuple_size<T>() -> usize {
    <T as Tuple>::size()
}
1 Like