I don't know that much about alignment and how sizes are determined, so I was looking into it.
One question I have is:
Why does size_of::<(usize,i32)>() == size_of::<(usize,usize)>(), but != size_of::<([u8;8],usize)>()?
println!("{}", mem::size_of::<(&i32,i32)>()); //16
println!("{}", mem::size_of::<(usize,i32)>()); //16 //Why not 12?
println!("{}", mem::size_of::<([u8;8],i32)>()); //12 //Why not 16?
The pithy slogan version (which skips the details) is: "the size of a type must be a multiple of its alignment". This makes it easy to index properly aligned types in arrays.
Though do note that Rust, unlike C, is free to automatically reorder the fields in structures to be more performant. And of course types where size == 0 are special.
Another thing to mention is that tuples can be thought of as syntactic sugar for structs. So (usize, i32) is represented as
struct UsizeAndI32 {
_0: usize,
_1: i32,
}
And because there aren't any specifiers around layout (i.e. UsizeAndI32 is considered Repr(Rust)), the compiler is free to rearrange fields or insert padding as it sees fit. At the moment I think the only interesting layout tweaks it'll do is the null pointer optimisation (a form of "niche" optimisation), though.
If you haven't already found it, the Type Layout chapter in The Rust Reference may be useful.