Lang proposal: const expressions in `#[repr(align)]` and `#[repr(packed)]` and friends.
Nobody has claimed this yet.
- Dominant language
- Rust
- Stars
- 119k
- Forks
- 16.1k
- PR merge metrics
- PR metrics pending
Description
Background
Several built-in attributes accept only integer literals where the argument is semantically a compile-time constant. The most common case is #[repr(align(N))], where N must be a literal even when the appropriate value is target-dependent and already exists as a const somewherein the crate graph.
Today this forces duplication across cfg branches:
#[cfg(target_arch = "x86_64")]
#[repr(align(64))]
pub struct CachePadded<T>(T);
#[cfg(target_arch = "aarch64")]
#[repr(align(128))]
pub struct CachePadded<T>(T);
#[cfg(any(target_arch = "arm", target_arch = "riscv32"))]
#[repr(align(32))]
pub struct CachePadded<T>(T);
The same cfg ladder appears across the ecosystem with concurrency primitives (cache-line padding), crypto (side-channel hardening), embedded HALs (DMA buffer alignment), and any crate that aligns to a target-dependent value. The constant is known at compile time; the attribute just refuses to accept it in non-literal form.
C and C++ already support this via _Alignas(expr) / alignas(expr). Zig supports it via align(expr).
Proposal
Allow #[repr(align(N))] and #[repr(packed(N))] to accept paths to const items and constant expressions composed of such paths, in addition to integer literals.
pub const CACHELINE: usize = {
#[cfg(target_arch = "x86_64")] { 64 }
#[cfg(target_arch = "aarch64")] { 128 }
#[cfg(any(target_arch = "arm", target_arch =
"riscv32"))] { 32 }
};
#[repr(align(CACHELINE))]
pub struct CachePadded<T>(T);
Proposed rules
- The argument must resolve to a const of the type the attribute already requires (for align and packed, a power-of-two usize).
- Resolution happens at attribute-processing time. The path must be visible in the dependency graph already established at that point — using a path that would require pulling in a new crate is an error. (I think this one is standard, I'm still learning the limitations of cargo dependency resolution.)
- Macro calls (align(some_macro!())) are out of scope. Only paths to already-defined constants and expressions over such paths are permitted.
- Errors at the attribute site (non-power-of-two, type mismatch, unresolved path) render at the attribute, not at the constant's definition.
Whatever form the backend machinery takes, the idea is to keep the semantics open and compatible with potential future expression resolution. I think it's important to be forward looking here.
Questions
- Syntactic vehicle. Should the surface syntax remain
#[repr(align(N))](with N resolving to a constant), or should the feature land through a different syntactic form?
Options raised so far in PR #154708
- Keep
#[repr(align(N))]as-is, with N extended to accept constant paths and expressions. Argued for by me and @programmerjake on the grounds that existing attribute syntax extends naturally - Treat the affected built-in attributes as active built-in attributes hardcoded in
rustc_expand(the model used by cfg / cfg_attr). - Implement as a built-in proc macro modeled on
#[define_opaque], with the surface attribute
preserved as the macro's invocation form. Suggested by @petrochenkov - A new dedicated syntax (e.g.,
align(CACHELINE) struct Foo;) — significantly larger scope, mentioned only for completeness.
- Backend mechanism. Independent of the syntactic vehicle, how should the constant payload be represented and resolved internally?
- (A) Extend the existing attribute IR to resolve constant paths during attribute lowering
- (B) Introduce a proper AST node (e.g.,
ast::Repr) carryingast::AnonConstpayloads, lowered through the usual AST → HIR → MIR pipeline.
The choice constrains and is constrained by the syntactic vehicle, which is why both questions appear here together.
- Scope. Is
#[repr(packed(N))]in scope alongside#[repr(align(N))]for the initial implementation, or should it be deferred? - String-valued attributes.
#[link_section = "..."]and#[export_name = "..."]have analogous motivation (target-dependent values forced into cfg ladders). Should they be considered in the same proposal, a follow-up, or deliberately left out? - Process. Is this an experiment-gated feature, or does it need a full RFC before implementation? @nnethercote raised this at the start of the PR review.
Prior Art
- RFC 3659 —
repr(tag = TypeAlias), currently S-waiting-on-team. Same tension between attributes-as-tokens and attributes-carrying-real-code, broader scope #[define_opaque](compiler/rustc_builtin_macros/src/define_opaque.rs) — existing built-in attribute that produces a proper AST node through a built-in proc macro. Reference implementation
patterncfg/cfg_attr— existing precedent for active built-in attributes producing AST inrustc_expand.- Implementation discussion: PR #154708.
- Zulip Thread: #t-lang > #[repr(align(cacheline))] Support
cc @rust-lang/lang
Note: This is my first PR/Issue in rust itself, though I've used it and many other languages all my life, I'm very new to contributing to projects of this scale. I've been told a couple times that this is unreasonable as a first-time contribution, but this started as just a private "what if?" purely at parse time, and it just exploded from there after discussions were had. Regardless, I'm happy to do the leg work, and I'm learning a hell of a lot in the process, so I appreciate all the insight I've been given by everyone!
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
Start by reading compiler/rustc_builtin_macros/src/define_opaque.rs and the existing cfg/cfg_attr handling in rustc_expand, then review PR #154708 and the linked Zulip discussion. Done means the syntax, backend representation and resolution approach, initial scope, and RFC or experiment process have been decided.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- rust
- Domain
- compilers
- Issue type
- Feature
- Difficulty
- 5/5
- Estimated time
- Over a week
- Activity status
- Quiet
- Clarity
- Needs clarification
- Newbie friendliness
- 30/100