Set alignment of a struct based on the type size

Say I have a simple pair struct

pub struct Pair<First, Second> {
}

I want to be able to align it with, fn(size_of(First), size_of(Second)), where fn is min(16, bit_ceil(size_of(First) + size_of(Second))).

But repr(align()) seems to require a literal.

For more clarity, in C++, it would look something like:

struct alignas(get_alignment<First, Second>()) pair

where get_alignment is a const function

I don't think it's possible. You can use #[repr(align(16))] to get the min(16 part of the equation, but having it be based on the size I don't think you can.

What's the purpose of setting the alignment this high? (It is possible that there is a sufficient alternative, or that this won't actually improve anything.)

What's the purpose of setting the alignment this high? (It is possible that there is a sufficient alternative, or that this won't actually improve anything.)

It's for code to run on a GPU. To not go into too much detail, would like powers of two aligment capped at 16. Would rather not always align to 16 if data types are smaller (say a pair of f32)

first, alignment of a type in rust is always power of 2.

second, "cap"-ping the alignment of a type to a value smaller than the default alignment has no effect, e.g., if a type's alignment is 8,and you add a #[repr(align(4)] attribute, the alignment will still be 8, not 4. [1].


  1. only way to "shrink" alignment from the natural alignment is to use #[repr(packed)] â†Šī¸Ž

1 Like

Defining an alignment the same as of a type can be done by including a [Type; 0] field.

To do more complicated computations, you can use a trait with an associated type.

Unfortunately it requires #![feature(generic_const_exprs)] and even typenum cannot help, due to the usage of size_of():

#![feature(generic_const_exprs)]
#![allow(incomplete_features)]

pub const fn alignment<First, Second>() -> usize {
    let x = (size_of::<First>() + size_of::<Second>()).next_power_of_two();
    if x < 16 { 16 } else { x }
}

pub struct Pair<First, Second>
where
    (): AlignedTo<{ alignment::<First, Second>() }>,
{
    _marker: Aligned<{ alignment::<First, Second>() }>,
    x: (First, Second),
}

pub type Aligned<const ALIGN: usize> = <() as AlignedTo<ALIGN>>::Aligned;

pub trait AlignedTo<const ALIGN: usize> {
    type Aligned;
}

macro_rules! aligned_to {
    ( $($align:literal),* $(,)? ) => {
        $(
            const _: () = {
                #[repr(align($align))]
                pub struct Aligned;
                
                impl AlignedTo<$align> for () {
                    type Aligned = Aligned;
                }
            };
        )*
    }
}

aligned_to!(1, 2, 4, 8, 16, 32, 64, 128); // ...
1 Like

first, alignment of a type in rust is always power of 2.

Let's take the case of a Pair<First, Second>, Is it guaranteed that the alignment is no larger that bit_ceil(size_of(First) + size_of(Second)). If not, how do I do that? Since that is what I am trying to achieve.

second, "cap "-ping the alignment of a type to a value smaller than the default alignment has no effect,

I am not trying to make the alignment smaller than default alignment. It's just that trying to force beyond 16 byte alignment via a repr(align) / alignas() has diminishing returns.

To do more complicated computations, you can use a trait with an associated type.

Aa that's a good idea. I'll try that out. Thanks

the attribute #[repr(align(X))] only guarantees the alignment is no smaller than X, there's no way to express the requirement that the alignment must be no "larger" than certain value.

the default alignment of a struct is the maximum of the alignment of all the fields. although alignment and size are related, it is NOT calculated from the size of the fields, only alignment. that's also why you can use a field of the desired (minimal) alignment but is zero-sized (typically an array of 0 element) as an alternative way to specify alignment for a struct, instead of using the #[repr(align(X))] attribute.

1 Like

There actually is: #[repr(packed(alignment))]. Although it has implications larger than just setting the alignment. And it cannot be combined with #[repr(align)].

However, if #[repr(C)] is used, then #[repr(align)] does actually guarantee it will set the alignment to the exact value, unless the alignment of a field is greater.

that's whole can of different worms, I didn't think that's what the original problem was about. but on a second thought, maybe it's indeed what OP actually asked for.

IMO, this is for sure an XY problem. OP was most likely trying to solve the wrong problem, (or a non-problem at all). I think it's not an alignment problem, but an serialization/deserialization problem. and the packed representation (combined with C representation) is indeed one way to solve similar ser/de problem.

they were probably trying to control the "stride" of data arrays that are passed to the gpu. I'm guessing so, based on the fact that they used the sum of size of the fields to calculate the desired "alignment".

although alignment and array "stride" is related, they are different concepts. alignment of rust types also change the way how rustc generate the CPU code, and packed representation may cause unexpected compile errors about unaligned field references, which could be confusing if the user don't really understand what's going on, especially when implicit reborrows occurred.

so I was hesitating to suggest the "obvious" solution:

#[repr(packed(16))]
pub struct Pair<A, B> {
    a: A,
    b: B,
}

in reality, this probably has the desired behavior described by OP, but I was under the impression they had misunderstood how alignment (and struct layout, in general) in rust works, and was trying to clarify a bit.

that's why I call it "no smaller" value, not "exact" value, of the alignment.


side note:

the accepted answer with generic_const_exprs also sets a "no smaller" value for the alignment, not "no larger".

1 Like

I fully agree, and I also believe the original question was not about that. I just responded, perhaps nitpick-ly, to the claim that "there's no way to express the requirement that the alignment must be no "larger" than certain value".

Yes, but unlike with #[repr(Rust)], you can calculate and know. In this case for example, we can know the alignment of the fields won't be greater, since the alignment is calculated to be greater than them.

1 Like