March 29, 2018·4 min read·engineeringrustcompilermemory-layout

Rust 1.25 updates LLVM and exposes alignment as a type property

Rust 1.25 moves to LLVM 6, stabilizes explicit struct alignment, and simplifies import grouping. The changes affect different layers of how Rust describes a program.

A systems language needs a way to describe a hardware requirement without hiding it inside manual allocation arithmetic. Rust 1.25 stabilizes explicit alignment on structs, while also updating its compiler backend from LLVM 4 to LLVM 6. The release announcement includes a smaller source-level improvement in nested imports. These changes concern different parts of the toolchain, so their effects deserve separate explanations.

The LLVM upgrade changes the backend Rust uses to generate code. The announcement identifies progress toward AVR support as one consequence. It does not announce a completed stable AVR target. Moving closer to a target and supporting it as a released platform are different claims, and only the first belongs to this release's account.

I find Rust interesting precisely where it gives low-level requirements an explicit expression. A compiler upgrade can improve the implementation, but a layout declaration also lets the source communicate a requirement to another programmer. That makes the alignment addition worth examining beyond its short release-note entry.

Alignment can belong to the type

RFC 1358 describes the motivation for custom alignment. Hardware or a foreign interface may require stronger alignment than the fields naturally imply. Manually arranging storage can meet such a requirement, but it puts responsibility on each allocation or placement. A type annotation lets the compiler carry the requirement wherever values of that type appear.

The stabilized syntax in Rust 1.25 is #[repr(align(N))]. The RFC uses an older proposed spelling in its examples, so the release announcement is the authority for the spelling a stable program should use now. Proposal documents explain design intent, but their earliest syntax is not necessarily the syntax that ships.

The release's small example shows the relationship between size and alignment.

#[repr(align(16))]
struct Aligned(i32);

fn main() {
    assert_eq!(std::mem::align_of::<Aligned>(), 16);
    assert_eq!(std::mem::size_of::<Aligned>(), 16);
}

The wrapped field does not become a larger integer. The struct has a stronger placement requirement and enough size to preserve that requirement when values occur consecutively. This declaration therefore affects storage layout without changing the field's numeric meaning.

The RFC explains why this is useful for interoperability. If a foreign type requires an alignment beyond that suggested by its member sizes, an equivalent Rust type needs to express the same requirement. Alignment alone is not a declaration of every other aspect of a foreign representation. It supplies one property needed in the larger layout contract.

There is a cost in storage where padding is necessary. A stronger alignment is not an automatic performance improvement. The value of the attribute is that a concrete requirement can be represented directly. Choosing that requirement still depends on the data structure or external interface being implemented.

A declaration also makes review more local. The person inspecting the type can see the requested alignment without finding a separate allocator wrapper. Code that creates or embeds the type then inherits a compiler-visible requirement. That reduces the number of independent places where an author must remember the same placement rule.

A backend version does not redefine the source language

The LLVM change operates below this source-level contract. Rust hands work to a backend that performs optimization and code generation. Updating that component can affect the implementation's capabilities without requiring the same kind of source change as a new syntax feature.

The announcement's AVR qualification is useful because backend support is only part of a complete target. A reader should not infer a supported deployment workflow merely from the inclusion of relevant machinery upstream. The release gives a reason to follow that work, while leaving the current support claim limited.

Likewise, no general runtime improvement percentage follows from the backend version number. The release announcement does not provide one. A compiler change can affect different programs differently, and its exact version identifies the implementation being used. That is concrete information even without a universal performance claim.

Nested imports compress a repeated prefix

Rust 1.25 also stabilizes nested import groups. A module can combine paths with a shared prefix while keeping subordinate groups. The change offers another way to write the same dependencies, rather than changing which names the imported items denote.

For example, the release demonstrates grouping File, the Read trait, and path types beneath std. The nested braces express the path hierarchy in the declaration. Separate imports remain an understandable alternative, especially where a compact group would become difficult to scan.

This is a source-maintenance choice with a compiler-version consequence. A crate adopting the new syntax asks its supported compiler to understand it. The absence of a runtime semantic change does not make the syntax available to an older compiler. Maintainers still need to distinguish source compatibility from executable behavior.

I prefer imports that make a module's dependencies easy to find. Nesting is useful when it removes a distracting repeated prefix without obscuring the actual names. The available syntax gives the author that choice without requiring every module to adopt the most compressed spelling.

Documentation completes its renderer transition

The announcement says rustdoc now uses pulldown-cmark by default and follows CommonMark. Rust 1.23 had warned about differences between the old and new renderers. The transition therefore reaches a new stage in this release, with the implementation change following a period in which authors could identify differences in their documentation.

Cargo's defaults also become more oriented toward creating a runnable program. cargo new now selects a binary unless the user requests a library explicitly. That changes a tool default, while an explicit --lib still states the library intention. For reproducible project-generation instructions, spelling out the intended kind avoids depending on which side of that default change the tool belongs to.

The release combines implementation updates with more direct ways to express intent. A struct can state its alignment requirement in the type declaration. Imports can share a path prefix without changing the imported items. Project creation can state the desired artifact explicitly. Those mechanisms affect different layers, and keeping them distinct makes the upgrade's consequences easier to understand.