← Back to index
PR #3806Work-in-progress preview from an open pull request.View on GitHub ↗
REVIEW
#3806

align_attr

AuthorJules-Bertholet
CreatedMay 1 2025
UpdatedJun 7 2026
Rust Issue

Add an #[align(…)] attribute to set the minimum alignment of struct and enum fields, statics, functions, and local variables.

Motivation

Bindings to C and C++

C and C++ provide an alignas modifier to set the alignment of specific struct fields. To represent such structures in Rust, bindgen is sometimes forced to add explicit padding fields:

// C code
#include <stdint.h>
struct foo {
    uint8_t x;
    _Alignas(128) uint8_t y;
    uint8_t z;
};
// Rust bindings generated by `bindgen`
#[repr(C, align(128))]
pub struct foo {
    pub x: u8,
    pub __bindgen_padding_0: [u8; 127usize],
    pub y: u8,
    pub z: u8,
}

The __bindgen_padding_0 field makes the generated bindings more confusing and less ergonomic. Also, it is unsound: the padding should be using MaybeUninit. And even then, there is no guarantee of ABI compatibility on all potential targets.

Packing a value into fewer cache lines

When working with large values (lookup tables, for example), it is often desirable to ensure they cross over as few cache lines as possible. This minimizes the amount of data that is brought into cache when the value is accessed heavily. One way of doing this is to force the alignment of the value to be at least the size of the cache line—or, for smaller values, next_power_of_two() of the value size.

The simplest way of accomplishing this in Rust today is to use a wrapper struct with a #[repr(align(…))] attribute:

type SomeLargeType = [[u8; 64]; 21];

#[repr(align(128))]
struct CacheAligned<T>(T);

static LOOKUP_TABLE: CacheAligned<SomeLargeType> = CacheAligned(todo!());

However, this approach has several downsides:

  • It requires defining a separate wrapper type.
  • It changes the type of the item, which may not be allowed if it is part of the crate's public API.
  • It may add unnecessary padding to the value, wasting memory.

In some cases, it can also improve performance to align a function's code in the same way.

Required alignment for low-level use cases

Some low-level use-cases (for example, the RISC-V mtvec register) require functions or statics to have a certain minimum alignment.

Pointer tagging

Users may want to specify a minimum alignment for various items, in order to leave the low bits of pointers to such items free to store additional data.

Interoperating with systems that have types where size is not a multiple of alignment

In Rust, a type’s size is always a multiple of its alignment. However, there are other languages that can interoperate with Rust, where this is not the case (WGSL, for example). It’s important for Rust to be able to represent such structures.

Explanation

The align attribute is a new inert, built-in attribute that can be applied to ADT fields, static items, function items, and local variable declarations. The attribute accepts a single required parameter, which must be a power-of-2 positive integer literal.

The maximum alignment is the same as #[repr(align(…))]: 229. That being said, some targets may not be able to support very high alignments in all contexts. In such cases, the compiler must impose a lower limit for those specific contexts on those specific targets. The compiler may choose to emit a lint warning for high, non-portable alignment specifiers.

Multiple instances of the align attribute may be present on the same item; the highest alignment among them will be used. The compiler may signal this case with a warn-by-default lint.

#[align(…)] on a public item is part of the item’s public API, and it is a semver-breaking change to lower it. As such, it is shown in rustdoc-generated documentation. To avoid making this commitment, one can use #[cfg_attr(not(doc), align(…))].

On ADT fields

The align attribute may be applied to any field of any non-#[repr(transparent)] struct, enum, or union.

#[repr(C)]
struct Foo {
    #[align(8)]
    a: u32,
}

enum Bar {
    Variant(#[align(16)] u128),
}

union Baz {
    #[align(16)]
    a: u32,
}

The effect of the attribute is to force the address of the field to have at least the specified alignment. If the field already has at least that alignment, due to the required alignment of its type or to a repr attribute on the containing type, the attribute has no effect.

In contrast to a repr(align(…)) wrapper struct, an align annotation does not necessarily add extra padding to force the field to have a size that is a multiple of its alignment. (The size of the containing ADT must still be a multiple of its alignment, which must in turn be no less than that of the most-aligned field. That hasn't changed.)

Instances of the align attribute for fields of a #[repr(packed(n))] ADT may not specify an alignment higher than n.

#[repr(packed(4))]
struct Sardines {
    #[align(2)] // OK
    a: u8,
    #[align(4)] // OK
    b: u16,
    #[align(8)] //~ ERROR
    c: u32,
}

Interaction with repr(C)

repr(C) currently has two contradictory meanings: “a simple, linear layout algorithm that works the same everywhere” and “an ABI matching that of the target’s standard C compiler”. This RFC does not aim to resolve that conflict; that is being discussed as part of RFC 3718. Henceforth, we will use repr(C_for_real) to denote “match the system C compiler”, and repr(linear) to denote “simple, portable layout algorithm”; but those names are not normative.

Of course, if a type declaration is using one of these reprs to make a public API commitment as to the exact layout of a type, then any change to field #[align(…)]s may be breaking.

repr(C_for_real)

The layout of a repr(C_for_real) ADT with align attributes on its fields is identical to that of the corresponding C ADT declared with alignas annotations. For example, the struct below is equivalent to the C struct foo from the motivation section:

#[repr(C_for_real)]
pub struct foo {
    pub x: u8,
    #[align(128)]
    pub y: u8,
    pub z: u8,
}

repr(linear)

In a repr(linear) ADT, a field with an align attribute has its alignment, as well as the alignment of the containing ADT, increased to at least what the attribute specifies.

For example, the following two structs have the same layout in memory (though not necessarily the same ABI):

#[repr(linear)]
pub struct foo {
    pub x: u8,
    #[align(128)]
    pub y: u8,
    pub z: u8,
}
#[repr(linear, align(128))]
pub struct foo2 {
    pub x: u8,
    pub _padding: [MaybeUninit<u8>; 127usize],
    pub y: u8,
    pub z: u8,
}

On static items

Any static item (including statics inside extern blocks) may have an align attribute applied:

#[align(32)]
static BAZ: [u32; 12] = [0xDEADBEEF; 12];

unsafe extern "C" {
    #[align(2)]
    safe static BOZZLE: u8;
}

The effect of the attribute is to force the static to be stored with at least the specified alignment. The attribute does not force padding bytes to be added after the static. For statics inside unsafe extern blocks, if the static does not meet the specified alignment, the behavior is undefined. (This UB is analogous to the UB that can result if the static item is not a valid value of its type. The question of whether the UB can occur even if the item is unused, has the same answer for both cases.)

The align attribute may also be applied to thread-local statics created with the thread_local! macro; the attribute affects the alignment of the underlying value, not that of the outer std::thread::LocalKey.

thread_local! {
    #[align(64)]
    static FOO: u8 = 42;
}

fn main() {
    FOO.with(|r| {
        let p: *const u8 = r;
        assert_eq!(p.align_offset(64), 0);
    });
}

derive macro helper ambiguity

Some existing derive macros in the ecosystem currently declare align as a helper attribute. To avoid breakage, we specify that derive helper attributes shadow built-in attributes of the same name (so the built-in attribute does not take effect). A warn-by-default lint is triggered when this occurs.

On function items

On function items, #[align(…)] sets the alignment of the function’s entry symbol. (It does not affect the alignment of its function item type, which remains a 1-ZST.) This replaces #[repr(align(…))] on function items, from #![feature(fn_align)].

On async fn, the attribute controls the alignment of the code of the function that returns the Future.

On function items in trait declarations, #[align(n)] (for some alignment n) specifies the minimum alignment that all implementations of the item must have. The generated implementation for the trait’s dyn type will also provide this alignment. impl blocks do not have to repeat the #[align(n)] attribute, it is implicit. (This enables the trait definition to raise n without breaking implementations.) That being said, impl blocks are free to specify an align(…) higher than that provided by the trait. (If they specify a lower alignment, it will simply be ignored.)

The numerical value of a function pointer to a function with an #[align(n)] attribute is not always guaranteed to be a multiple of n on all targets. For example, on 32-bit ARM, the low bit of the function pointer is set for functions using the Thumb instruction set, even though the actual code of the function is always aligned to at least 2 bytes.

#[align(…)] is compatible with #[naked].

#[align(…)] may be used on function items inside extern blocks. This imposes a requirement on the symbol being linked to. The UB that can result if this alignment is not satisfied, follows the same rules as the UB that can result from an incorrect function signature.

If a function item in an extern block does not specify #[align(…)], then a function pointer derived from that item may have any alignment. In other words, the following assertion may fail on any platform:

mod inner {
    #[unsafe(no_mangle)]
    #[align(64)]
    fn foo() {}
}

unsafe extern "Rust" {
    safe fn foo(); // no `#[align(…)]` specification here
}

fn main() {
    let foo_addr = foo as fn() as usize;
    assert!(foo_addr.is_multiple_of(64));
}

When #[align(…)] is combined with #[track_caller], the attribute applies to the shim that is generated for calls via function pointer, as well as to the primary function (though the alignment of the latter is usually not observable).

WASM targets will most likely not support function alignments above 1, at least initially.

On local variables

The align attribute may also be applied to local variable declarations inside let bindings. The attribute forces the local to have at least the alignment specified:

fn main() {
    let (a, #[align(4)] b, #[align(2)] mut c) = (4u8, 2u8, 1u8);
    c *= 2;
    dbg!(a, b, c);

    if let Some(#[align(4)] x @ 1..) = Some(42u8) {
        dbg!(x);
        let p: *const u8 = x;
        assert_eq!(p.align_offset(4), 0);
    }
}

The align attribute may not be applied to function parameters. (This prohibition is semantic, not syntactic; it is allowed under #[cfg(false)]).

fn foo(#[align(8)] _a: u32) {} //~ ERROR

The attribute may only be applied to binding patterns. It may not be applied to any other type of pattern, including wildcard patterns:

let #[align(4)] _ = true; //~ ERROR

Drawbacks

  • This feature adds additional complexity to the language.
  • The distinction between align and repr(align) may be confusing for users.

Rationale and alternatives

Compared to the wrapper type approach, the align attribute adds additional flexibility, because it does not force the insertion of padding. If we don't adopt this feature, bindgen will continue to generate suboptimal bindings, and users will continue to be forced to choose between suboptimal alignment and additional padding.

#[align(…)] vs #[repr(align(…))]

One potential alternative would be to use #[repr(align(…))] everywhere, instead of introducing a new attribute.

Benefits of this alternative:

  • No new attribute polluting the namespace.
  • Requesting a certain alignment is spelled the same everywhere.
  • #[repr(…)] on fields might accept additional options in the future, for specifying layout and padding more precisely.
  • #[repr(…)] on function items could also accept instruction_set(…) as an argument, replacing the existing attribute of that name.

Drawbacks:

  • #[repr(align(…))] is a longer and noisier syntax.
  • #[repr(…)] on non-ADTs would never accept the same set of options as on ADTs. On field definitions, it might accept additional options to precisely control layout; on function items, it might accept instruction_set(…), if we were to overturn the precedent of that being a standalone attribute. On statics and local variables, I doubt it would ever accept anything else at all.
  • #[align(…)] only aligns, while #[repr(align(…))] also pads to a multiple of the alignment. Having different syntax makes that distinction more clear.

#[align(n)] vs #[align = n]

align = n might be misinterpreted as requesting an alignment of exactly n, instead of at least n.

#[align(…)] on function parameters

We could choose to allow this. However, this RFC specifies that it should be rejected, because users might incorrectly think the attribute affects ABI when it does not. C and C++ make the same choice.

To give an example of what could go wrong, consider the following function:

fn example(#[align(1024)] very_large_value: [u64; 8192]) {
    // use `very_large_value` by reference
}

On typical platforms, calling this function will involve first passing very_large_value on the stack or by pointer, and then copying the entire array to a new place on the stack in order to align it. This implicit extra stack copy is not present for #[align(…)]ed locals. Forbidding this, and requiring users to make the move/copy explicit, avoids the performance footgun.

We could always lift this limitation in the future.

Interaction with async fn

This RFC specifies that when applied to async fn, the align attribute should affect the alignment of the function that returns the future. This breaks precedent with #[inline], which affects the future poll method.

There is good reason for this difference. In the case of inline, controlling the inlineability of the function that returns the future is almost never what you want. That function is mostly trivial, and there is little reason to deviate from the default of inlining it in most cases. Controlling the inlineability of the poll method is far more useful.

In contrast, there are several potential reasons to want to control the alignment of an async fn. For example, this could be used in concert with function pointer tagging schemes. If users apply #[align(…)] to an async fn item believing that it will affect the alignment of the function’s pointer (as it does with any other function item), but instead it affects that of the poll method, that could even result in UB. Therefore, it makes more sense to choose the simpler and more consistent rule of having the #[align(…)] attribute affect the alignment of the function that returns the future.

The current #![feature(fn_align)] works this way already.

#[align(…)] mut local via mut #[align(…)] local

This RFC proposes that the #[align(…)] attribute should come before the mut keyword when declaring an aligned local variable.

Local variables are declared with identifier patterns. The local is defined by three pieces of information:

  • Its name, which is declared explicitly
  • Its mutability, which is declared explicitly via the presence or absence of the mut keyword
  • Its type, which is derived implicitly from the structure and type of the surrounding pattern.

In Rust, attributes come before the element they modify. In this case, the mut keyword is an integral part of the local’s declaration; therefore, the attribute should precede it.

#[align(…)] on _ wildcard patterns

This makes no semantic sense. Just as binding modes can only be applied to bindings, #[align(…)] also can only be applied to bindings. _ patterns are not bindings; they are a completely separate element in the grammar.

#[align(…)] function item types being 1-ZSTs

Anything else would pessimize memory usage for no reason.

Prior art

This proposal is the Rust equivalent of C and C++ alignas.

There are a few significant semantic differences between those features and this RFC:

  • #[align] additionally allows applying the attribute to function item declarations, which alignas does not permit.
  • C++, but not C, allows applying alignas to type declarations, like Rust’s repr(align); this RFC does not permit that usage.
  • alignas(n) accepts any integer constant expression or type name for n; this RFC accepts only integer literals (for now).
  • alignas(n) allows n to be zero, in which case the specifier is ignored; this RFC does not permit that usage.

Unresolved questions

MSVC

Does MSVC do something weird with alignas? In other words, is the concern about repr(C) vs repr(linear) purely theoretical at this point, or does it matter in practice today?

Interaction with ref/ref mut

What should the syntax be for applying the align attribute to ref/ref mut bindings?

  • Option A: the attribute goes inside the ref/ref mut.
fn foo(x: &u8) {
    let ref #[align(4)] _a = *x;
}
  • Option B: the attribute goes outside the ref/ref mut.
fn foo(x: &u8) {
    let #[align(4)] ref _a = *x;
}

I believe the simplest option is to forbid this combination entirely for now, especially as there is effectively no use-case for it.

How I believe we should decide this eventually

In my view, the resolution of this question hinges on whether ref/ref mut are an integral part of the local declaration. My instinct is to say that they are not. Like the rest of the surrounding pattern, they describe how the initial value of the local should be extracted from the scrutinee. Like this surrounding pattern, they implicitly affect the local’s type, but don’t otherwise affect its properties. At the point of use, you can’t distinguish a local declared with ref/ref mut from one declared some other way.

However, one could argue that ref/ref mut are part of the same “binding mode” syntactic element as mut, and that therefore, if the align attribute precedes mut, it should precede ref/ref mut also.

I believe the the correct time to resolve this question will be when we decide on a syntax for combining ref/ref mut with mut. If we choose a syntax that make it clear that these are distinct elements:

let ref (mut x) =;
let ref mut (mut x) =;

Then, that would imply that #[align(…)] should be applied just before the mut:

let ref #[align()] x =;
let ref mut #[align()] x =;
let ref #[align()] mut x =;
let ref mut #[align()] mut x =;

But if we choose a syntax that treats them as components of a single “binding mode” element:

let mut ref x =;
let mut ref mut x =;

Then, #[align] should always precede that element:

let #[align()] ref x =;
let #[align()] ref mut x =;
let #[align()] mut ref x =;
let #[align()] mut ref mut x =;

Future possibilities

  • The align(…) and repr(align(…)) attributes currently accept only integer literals as parameters. In the future, they could support const expressions as well.
  • We could provide additional facilities for controlling the layout of ADTs; for example, a way to specify exact field offsets or arbitrary padding.
  • We could add type-safe APIs for over-aligned pointers; for example, over-aligned reference types that are subtypes of &/&mut.
    • We could also introduce a similar facility for function pointers.
  • We could also add similar APIs for over-aligned function pointers.
  • We could loosen the restriction that fields of a packed(n) struct cannot specify an alignment greater that n. (Apparently, some C compilers allow something similar.)
  • Once #![feature(stmt_expr_attributes)] is stable, we could allow applying #[align(…))] to closures and async blocks as well.
  • We could add tools to make it easier to implement function pointer tagging in a way that’s resilient to the ARM Thumb issue (and similar strangeness on hypothetical future targets).