Rust's SemVer Snares: Alignment

(Part of an ongoing series!)

In Rust, changes to a type's alignment are not usually understood to be Breaking Changes™. Of course, that isn't to say you can't break safe, downstream code by changing the alignment of a type...

align_of

The mem::align_of intrinsic is a safe function that provides the minimum alignment (in bytes) of any type. As with size_of, downstream code should not rely on mem::align_of producing a SemVer stable result, but that's only a convention. Consider:

pub mod upstream {
  #[repr(C)]
  pub struct Foo {
    bar: u8,
    // uncommenting this field is a breaking change for `downstream`:
    /* baz: u16 */
  }
}

pub mod downstream {
  use super::upstream::*;
  
  const _: [(); 1] = [(); std::mem::align_of::<Foo>()];
}
error[E0308]: mismatched types
  --> src/lib.rs:12:22
   |
12 |   const _: [(); 1] = [(); std::mem::align_of::<Foo>()];
   |                      ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ expected an array with a fixed size of 1 element, found one with 2 elements

repr(transparent)

The repr(transparent) attribute provides a mechanism for observing the alignment of a type. The repr(transparent) attribute can be applied to types where:

  • at most one field has size greater-than zero, and
  • all other fields have minimum alignment equal to 1

...to specify that the annotated type's layout is identical to that of the non-zero-sized field. Applying repr(transparent) to a type with more than one field that has alignment >1 is a compile error:

#[repr(transparent)]
pub struct Foo {
    bar: u8,      // align = 1
    baz: [u16; 0] // align = 2 (⚠)
}
error[E0691]: zero-sized field in transparent struct has alignment larger than 1
 --> src/lib.rs:4:5
  |
4 |     baz: [u16; 0]
  |     ^^^^^^^^^^^^^ has alignment larger than 1

This requirement exists because even zero-sized fields affect the alignment (and thus padding) of the types they appear in.

Consequently, upstream changes that increase the alignment of ZST can break downstream code:

pub mod upstream {
  #[repr(C)]
  pub struct Foo {
    bar: (),
    // uncommenting this field is a breaking change for `downstream`:
    /* baz: [u16; 0], */
  }
}

pub mod downstream {
  use super::upstream::*;

  #[repr(transparent)]
  struct Bar(u8, Foo);
}
error[E0691]: zero-sized field in transparent struct has alignment larger than 1
  --> src/lib.rs:14:18
   |
14 |   struct Bar(u8, Foo);
   |                  ^^^ has alignment larger than 1

You should therefore avoid #[repr(transparent)] unless the ZST field types are documented to have a minimum alignment of 1.


Thanks to /u/CUViper for nudging me towards thinking about alignment-related snares!


Email comments and corrections to jack@wrenn.fyi.