🦀 Rust 内存布局

Layout 到分配器契约的源码级拆解

基于 rust-lang/rust 源码深度解析
2026-03-12 | 技术深度解读

目录

目录

  1. 问题背景与源码入口
  2. Layout 数据模型
  3. 合法性与溢出防线
  4. 对齐、填充、组合算法
  5. 数组与重复布局
  6. 和 Allocator / GlobalAlloc 的关系
  1. repr(C) 与字段偏移
  2. DST / trait object / slice
  3. 性能注释与 const 设计
  4. 常见误区与反模式
  5. 工程实践建议
  6. 总结与延伸阅读

为什么值得研究

Layout 是 Rust 手写内存管理、容器实现、分配器 API、FFI 结构布局计算的共同底座。
  • Vec / RawVec / Box / allocators 最终都要落到 size + align 的组合
  • 源码把“内存布局”收敛成一个小而硬的数学对象
  • 它既是 安全边界,也是 性能关键路径

源码入口

主文件:library/core/src/alloc/layout.rs
  • Layout:描述一块内存的最小 size / align 需求
  • LayoutError:所有非法组合统一错误
  • mod.rs:定义 Allocator trait,消费 Layout
  • global.rs:全局分配 API 的更高层包装

性能注释很不寻常

文件开头直接提醒:“你的性能直觉没用,跑 perf。”
  • Layout 逻辑会被很多容器单态化实例反复内联
  • 看似无害的改动,会让 LLVM 反复优化重复 IR
  • 这解释了源码为何大量使用 const fn、小函数、unchecked intrinsics
// Seemingly inconsequential code changes to this file can lead to measurable
// performance impact on compilation times, due at least in part to the fact
// that the layout code gets called from many instantiations of the various
// collections, resulting in having to optimize down excess IR multiple times.
// Your performance intuition is useless. Run perf.

Layout 结构体

2 个字段
  • size: usize
  • align: Alignment
注意这里不是裸 usize 对齐值,而是更强约束的 Alignment 类型。
  • 表达能力极小,但语义非常强
  • 保证一旦构造成功,后续 API 都可假设不变量成立
/// (Note that layouts are *not* required to have non-zero size,
/// even though `GlobalAlloc` requires that all memory requests
/// be non-zero in size. A caller must either ensure that conditions
/// like this are met, use specific allocators with looser
/// requirements, or use the more lenient `Allocator` interface.)
#[stable(feature = "alloc_layout", since = "1.28.0")]
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
#[lang = "alloc_layout"]
pub struct Layout {
    // size of the requested block of memory, measured in bytes.
    size: usize,

    // alignment of the requested block of memory, measured in bytes.
    // we ensure that this is always a power-of-two, because API's
    // like `posix_memalign` require it and it is a reasonable
    // constraint to impose on Layout constructors.
    //
    // (However, we do not analogously require `align >= sizeof(void*)`,
    //  even though that is *also* a requirement of `posix_memalign`.)

核心不变量

Layout 的三条硬约束

  • 对齐值不能为 0
  • 对齐值必须是 2 的幂
  • size 向上对齐后不能超过 isize::MAX
第三条最关键:它保证“指针偏移”与“对象地址空间模型”不会踩进 UB 区。

为什么上限是 isize::MAX

  • 很多底层偏移语义最终都以 isize 表示距离
  • 若布局向上对齐后超过 isize::MAX,一些合法 API 无法安全表达
  • Rust 把这个限制前置到 Layout 构造阶段,而不是分配阶段才报错
所以 Layout 不只是“分配多大”,还是“这个块是否仍在 Rust 指针模型可表达范围内”。

from_size_align

  • 最常用构造器:先校验,再返回 Layout
  • 成功后通过 unsafe 直接构造,避免重复检查
  • 失败统一返回 LayoutError
    /// * `size`, when rounded up to the nearest multiple of `align`,
    ///   must not overflow `isize` (i.e., the rounded value must be
    ///   less than or equal to `isize::MAX`).
    #[stable(feature = "alloc_layout", since = "1.28.0")]
    #[rustc_const_stable(feature = "const_alloc_layout_size_align", since = "1.50.0")]
    #[inline]
    pub const fn from_size_align(size: usize, align: usize) -> Result<Self, LayoutError> {
        if Layout::is_size_align_valid(size, align) {
            // SAFETY: Layout::is_size_align_valid checks the preconditions for this call.
            unsafe { Ok(Layout { size, align: mem::transmute(align) }) }

合法性检查分两层

is_size_align_valid(size, align)
  • 把 usize align 包装为 Alignment::new
  • 过滤掉 0 和非 2 次幂
is_size_alignment_valid(size, alignment)
  • 只关心 size 是否超界
  • 把“对齐类型合法”与“总尺寸合法”拆开
            Err(LayoutError)
        }
    }

    #[inline]
    const fn is_size_align_valid(size: usize, align: usize) -> bool {
        let Some(alignment) = Alignment::new(align) else { return false };
        Self::is_size_alignment_valid(size, alignment)
    }

max_size_for_alignment

  • 核心公式不是直接做 round-up,而是先算允许的最大 size
  • 上界 = isize::MAX + 1 - align
  • 这样可以把“round up 后不溢出”的条件转成简单比较
    const fn is_size_alignment_valid(size: usize, alignment: Alignment) -> bool {
        size <= Self::max_size_for_alignment(alignment)
    }

    #[inline(always)]
    const fn max_size_for_alignment(alignment: Alignment) -> usize {
        // (power-of-two implies align != 0.)

        // Rounded up size is:
        //   size_rounded_up = (size + align - 1) & !(align - 1);
        //
        // We know from above that align != 0. If adding (align - 1)
        // does not overflow, then rounding up will be fine.
        //
        // Conversely, &-masking with !(align - 1) will subtract off
        // only low-order-bits. Thus if overflow occurs with the sum,
        // the &-mask cannot subtract enough to undo that overflow.
        //
        // Above implies that checking for summation overflow is both
        // necessary and sufficient.

        // SAFETY: the maximum possible alignment is `isize::MAX + 1`,

为什么这个上界推导成立

round_up(size, align) = (size + align - 1) & !(align - 1)
  • size + align - 1 本身不溢出,则掩码只会减小结果
  • 若这一步已溢出,后续按位与不可能“救回来”
  • 因此检查加法是否会越界,既必要也充分

unchecked intrinsics 的角色

  • 源码使用 unchecked_add / unchecked_sub / unchecked_mul
  • 前提是:调用点已经由数学证明或先行检查排除了溢出
  • 收益是减少冗余分支,避免热路径被反复优化
这不是“激进 unsafe”,而是把检查前移、把核心路径变成确定性算术。

from_size_align_unchecked

  • 给已经证明合法的调用者使用
  • 通过 assert_unsafe_precondition! 记录 unsafe 契约
  • release 路径尽量零成本,debug / Miri / 工具链可获得更明确诊断
    /// Creates a layout, bypassing all checks.
    ///
    /// # Safety
    ///
    /// This function is unsafe as it does not verify the preconditions from
    /// [`Layout::from_size_align`].
    #[stable(feature = "alloc_layout", since = "1.28.0")]
    #[rustc_const_stable(feature = "const_alloc_layout_unchecked", since = "1.36.0")]
    #[must_use]
    #[inline]
    #[track_caller]
    pub const unsafe fn from_size_align_unchecked(size: usize, align: usize) -> Self {
        assert_unsafe_precondition!(
            check_library_ub,
            "Layout::from_size_align_unchecked requires that align is a power of 2 \
            and the rounded-up allocation size does not exceed isize::MAX",
            (
                size: usize = size,
                align: usize = align,
            ) => Layout::is_size_align_valid(size, align)
        );
        // SAFETY: the caller is required to uphold the preconditions.

Layout::size 与 align

读取成本极低
  • size() 直接返回字节数
  • align() 返回最小对齐
对外只暴露经过验证的视图,因此使用者不必重复守卫“是否是 2 的幂”。
    }

    /// The minimum size in bytes for a memory block of this layout.
    #[stable(feature = "alloc_layout", since = "1.28.0")]
    #[rustc_const_stable(feature = "const_alloc_layout_size_align", since = "1.50.0")]
    #[must_use]
    #[inline]
    pub const fn size(&self) -> usize {
        self.size
    }

    /// The minimum byte alignment for a memory block of this layout.
    ///
    /// The returned alignment is guaranteed to be a power of two.
    #[stable(feature = "alloc_layout", since = "1.28.0")]
    #[rustc_const_stable(feature = "const_alloc_layout_size_align", since = "1.50.0")]
    #[must_use = "this returns the minimum alignment, \
                  without modifying the layout"]
    #[inline]
    pub const fn align(&self) -> usize {
        self.align.as_usize()
    }

    /// The minimum byte alignment for a memory block of this layout.
    ///
    /// The returned alignment is guaranteed to be a power of two.
    #[unstable(feature = "ptr_alignment_type", issue = "102070")]
    #[must_use = "this returns the minimum alignment, without modifying the layout"]
    #[inline]
    pub const fn alignment(&self) -> Alignment {
        self.align

Layout::new

  • 直接复用 SizedTypeProperties::LAYOUT
  • 对 Sized 类型,布局在编译期即可确定
  • 这让很多泛型代码根本不需要运行时计算
布局系统的一个重要目标:尽可能把信息前置到编译期。

    /// Constructs a `Layout` suitable for holding a value of type `T`.
    #[stable(feature = "alloc_layout", since = "1.28.0")]
    #[rustc_const_stable(feature = "alloc_layout_const_new", since = "1.42.0")]
    #[must_use]
    #[inline]
    pub const fn new<T>() -> Self {
        <T as SizedTypeProperties>::LAYOUT

for_value 与 DST

  • for_value(&T) 支持 slice / trait object 等动态大小类型
  • 它通过 size_of_valAlignment::of_val 读取胖指针元数据
  • 因此 DST 的布局可以在运行时被恢复出来

    /// Produces layout describing a record that could be used to
    /// allocate backing structure for `T` (which could be a trait
    /// or other unsized type like a slice).
    #[stable(feature = "alloc_layout", since = "1.28.0")]
    #[rustc_const_stable(feature = "const_alloc_layout", since = "1.85.0")]
    #[must_use]
    #[inline]
    pub const fn for_value<T: ?Sized>(t: &T) -> Self {
        let (size, alignment) = (size_of_val(t), Alignment::of_val(t));
        // SAFETY: see rationale in `new` for why this is using the unsafe variant
        unsafe { Layout::from_size_alignment_unchecked(size, alignment) }

for_value_raw 的安全前提

  • 裸指针版本更强大,也更危险
  • slice 尾部长度必须已初始化
  • trait object 的 vtable 必须有效
  • extern type 虽可调用,但结果可能 panic 或不正确
    ///
    /// # Safety
    ///
    /// This function is only safe to call if the following conditions hold:
    ///
    /// - If `T` is `Sized`, this function is always safe to call.
    /// - If the unsized tail of `T` is:
    ///     - a [slice], then the length of the slice tail must be an initialized
    ///       integer, and the size of the *entire value*
    ///       (dynamic tail length + statically sized prefix) must fit in `isize`.
    ///       For the special case where the dynamic tail length is 0, this function
    ///       is safe to call.
    ///     - a [trait object], then the vtable part of the pointer must point
    ///       to a valid vtable for the type `T` acquired by an unsizing coercion,
    ///       and the size of the *entire value*
    ///       (dynamic tail length + statically sized prefix) must fit in `isize`.
    ///     - an (unstable) [extern type], then this function is always safe to
    ///       call, but may panic or otherwise return the wrong value, as the
    ///       extern type's layout is not known. This is the same behavior as
    ///       [`Layout::for_value`] on a reference to an extern type tail.
    ///     - otherwise, it is conservatively not allowed to call this function.
    ///
    /// [trait object]: ../../book/ch17-02-trait-objects.html
    /// [extern type]: ../../unstable-book/language-features/extern-types.html
    #[unstable(feature = "layout_for_ptr", issue = "69835")]
    #[must_use]
    #[inline]
    pub const unsafe fn for_value_raw<T: ?Sized>(t: *const T) -> Self {
        // SAFETY: we pass along the prerequisites of these functions to the caller
        let (size, alignment) = unsafe { (mem::size_of_val_raw(t), Alignment::of_val_raw(t)) };
        // SAFETY: see rationale in `new` for why this is using the unsafe variant
        unsafe { Layout::from_size_alignment_unchecked(size, alignment) }
    }

    /// Creates a `NonNull` that is dangling, but well-aligned for this Layout.
    ///
    /// Note that the address of the returned pointer may potentially

dangling_ptr

  • 返回一个“对齐正确但不可解引用”的悬空指针
  • 适合表示尚未分配但已知布局的状态
  • 源码明确警告:不能把它当作“未初始化哨兵”的通用替代
    /// as a "not yet initialized" sentinel value.
    /// Types that lazily allocate must track initialization by some other means.
    #[stable(feature = "alloc_layout_extra", since = "1.95.0")]
    #[rustc_const_stable(feature = "alloc_layout_extra", since = "1.95.0")]
    #[must_use]
    #[inline]
    pub const fn dangling_ptr(&self) -> NonNull<u8> {
        NonNull::without_provenance(self.align.as_nonzero())
    }

    /// Creates a layout describing the record that can hold a value
    /// of the same layout as `self`, but that also is aligned to
    /// alignment `align` (measured in bytes).

align_to / adjust_alignment_to

  • 提升布局对齐要求,但不自动补尾部 padding
  • size 可能保持原值不变
  • 适合表达“放在更高对齐位置”,不是“对象本身最终大小”
    /// If `self` already meets the prescribed alignment, then returns
    /// `self`.
    ///
    /// Note that this method does not add any padding to the overall
    /// size, regardless of whether the returned layout has a different
    /// alignment. In other words, if `K` has size 16, `K.align_to(32)`
    /// will *still* have size 16.
    ///
    /// Returns an error if the combination of `self.size()` and the given
    /// `align` violates the conditions listed in [`Layout::from_size_align`].
    #[stable(feature = "alloc_layout_manipulation", since = "1.44.0")]
    #[rustc_const_stable(feature = "const_alloc_layout", since = "1.85.0")]
    #[inline]
    pub const fn align_to(&self, align: usize) -> Result<Self, LayoutError> {
        if let Some(alignment) = Alignment::new(align) {
            self.adjust_alignment_to(alignment)
        } else {
            Err(LayoutError)
        }
    }

    /// Creates a layout describing the record that can hold a value
    /// of the same layout as `self`, but that also is aligned to
    /// alignment `alignment`.
    ///
    /// If `self` already meets the prescribed alignment, then returns
    /// `self`.
    ///
    /// Note that this method does not add any padding to the overall
    /// size, regardless of whether the returned layout has a different
    /// alignment. In other words, if `K` has size 16, `K.align_to(32)`
    /// will *still* have size 16.
    ///
    /// Returns an error if the combination of `self.size()` and the given
    /// `alignment` violates the conditions listed in [`Layout::from_size_alignment`].

padding_needed_for

  • 计算在当前 size 之后,再追加多少字节,才能满足下一个对象的对齐
  • 这是组合结构体布局的基础砖块
  • 实现上复用“size 向上取整”逻辑,再减去原 size
    #[inline]
    pub const fn adjust_alignment_to(&self, alignment: Alignment) -> Result<Self, LayoutError> {
        Layout::from_size_alignment(self.size, Alignment::max(self.align, alignment))
    }

    /// Returns the amount of padding we must insert after `self`
    /// to ensure that the following address will satisfy `alignment`.
    ///
    /// e.g., if `self.size()` is 9, then `self.padding_needed_for(alignment4)`
    /// (where `alignment4.as_usize() == 4`)
    /// returns 3, because that is the minimum number of bytes of
    /// padding required to get a 4-aligned address (assuming that the
    /// corresponding memory block starts at a 4-aligned address).
    ///
    /// Note that the utility of the returned value requires `alignment`
    /// to be less than or equal to the alignment of the starting
    /// address for the whole allocated block of memory. One way to
    /// satisfy this constraint is to ensure `alignment.as_usize() <= self.align()`.
    #[unstable(feature = "ptr_alignment_type", issue = "102070")]
    #[must_use = "this returns the padding needed, without modifying the `Layout`"]
    #[inline]

round up 算法

(size + align - 1) & !(align - 1)
  • 位运算版向上取整是内存布局经典技巧
  • 只有当 align 是 2 的幂时,这个公式才成立
  • Rust 把这个前提塞进 Alignment 类型,让公式随处可用
        let len_rounded_up = self.size_rounded_up_to_custom_alignment(alignment);
        // SAFETY: Cannot overflow because the rounded-up value is never less
        unsafe { unchecked_sub(len_rounded_up, self.size) }
    }

    /// Returns the smallest multiple of `align` greater than or equal to `self.size()`.
    ///
    /// This can return at most `Alignment::MAX` (aka `isize::MAX + 1`)
    /// because the original size is at most `isize::MAX`.
    #[inline]
    const fn size_rounded_up_to_custom_alignment(&self, alignment: Alignment) -> usize {
        // SAFETY:
        // Rounded up value is:
        //   size_rounded_up = (size + align - 1) & !(align - 1);
        //
        // The arithmetic we do here can never overflow:
        //
        // 1. align is guaranteed to be > 0, so align - 1 is always
        //    valid.
        //
        // 2. size is at most `isize::MAX`, so adding `align - 1` (which is at
        //    most `isize::MAX`) can never overflow a `usize`.
        //
        // 3. masking by the alignment can remove at most `align - 1`,
        //    which is what we just added, thus the value we return is never

pad_to_align

  • 把当前对象补齐到自身对齐边界
  • 这是构造 repr(C) 最终大小时常见的“收尾动作”
  • 源码注释专门强调:布局不变量保证这一步不会溢出
        //
        // (Size 0 Align MAX is already aligned, so stays the same, but things like
        // Size 1 Align MAX or Size isize::MAX Align 2 round up to `isize::MAX + 1`.)
        unsafe {
            let align_m1 = unchecked_sub(alignment.as_usize(), 1);
            unchecked_add(self.size, align_m1) & !align_m1
        }
    }

    /// Creates a layout by rounding the size of this layout up to a multiple
    /// of the layout's alignment.
    ///
    /// This is equivalent to adding the result of `padding_needed_for`
    /// to the layout's current size.
    #[stable(feature = "alloc_layout_manipulation", since = "1.44.0")]
    #[rustc_const_stable(feature = "const_alloc_layout", since = "1.85.0")]
    #[must_use = "this returns a new `Layout`, \
                  without modifying the original"]

repeat:带 stride 的重复布局

  • repeat(n) 返回 (layout, stride)
  • stride 是每个元素起始地址的间距
  • 内部先 pad_to_align,再重复,再把最后一个元素接回去
    pub const fn pad_to_align(&self) -> Layout {
        // This cannot overflow. Quoting from the invariant of Layout:
        // > `size`, when rounded up to the nearest multiple of `align`,
        // > must not overflow isize (i.e., the rounded value must be
        // > less than or equal to `isize::MAX`)
        let new_size = self.size_rounded_up_to_custom_alignment(self.align);

        // SAFETY: padded size is guaranteed to not exceed `isize::MAX`.
        unsafe { Layout::from_size_alignment_unchecked(new_size, self.alignment()) }
    }

    /// Creates a layout describing the record for `n` instances of
    /// `self`, with a suitable amount of padding between each to
    /// ensure that each instance is given its requested size and
    /// alignment. On success, returns `(k, offs)` where `k` is the
    /// layout of the array and `offs` is the distance between the start
    /// of each element in the array.
    ///
    /// Does not include padding after the trailing element.
    ///
    /// (That distance between elements is sometimes known as "stride".)
    ///
    /// On arithmetic overflow, returns `LayoutError`.
    ///
    /// # Examples
    ///
    /// ```
    /// use std::alloc::Layout;
    ///
    /// // All rust types have a size that's a multiple of their alignment.
    /// let normal = Layout::from_size_align(12, 4).unwrap();
    /// let repeated = normal.repeat(3).unwrap();
    /// assert_eq!(repeated, (Layout::from_size_align(36, 4).unwrap(), 12));
    ///
    /// // But you can manually make layouts which don't meet that rule.
    /// let padding_needed = Layout::from_size_align(6, 4).unwrap();
    /// let repeated = padding_needed.repeat(3).unwrap();
    /// assert_eq!(repeated, (Layout::from_size_align(22, 4).unwrap(), 8));
    ///
    /// // Repeating an element zero times has zero size, but keeps the alignment (like `[T; 0]`)
    /// let repeated = normal.repeat(0).unwrap();
    /// assert_eq!(repeated, (Layout::from_size_align(0, 4).unwrap(), 12));
    /// let repeated = padding_needed.repeat(0).unwrap();
    /// assert_eq!(repeated, (Layout::from_size_align(0, 4).unwrap(), 8));
    /// ```
    #[stable(feature = "alloc_layout_extra", since = "1.95.0")]
    #[rustc_const_stable(feature = "alloc_layout_extra", since = "1.95.0")]

repeat 为什么返回 stride

数组总大小和元素步长不是一回事。
  • 若元素 size 不是自身 align 的整数倍,stride 会大于 size
  • 这对自定义 layout 或 packed 结构尤其重要
  • 返回 stride 能让上层代码精确做索引与指针运算

extend:结构体拼接的核心

  • extend(next) 计算“当前字段后接下一个字段”的新布局
  • 返回值同时给出 新布局next 的偏移
  • 这基本就是手工实现 struct layout 的核心算法
    pub const fn repeat(&self, n: usize) -> Result<(Self, usize), LayoutError> {
        // FIXME(const-hack): the following could be way shorter with `?`
        let padded = self.pad_to_align();
        let Ok(result) = (if let Some(k) = n.checked_sub(1) {
            let Ok(repeated) = padded.repeat_packed(k) else {
                return Err(LayoutError);
            };
            repeated.extend_packed(*self)
        } else {
            debug_assert!(n == 0);
            self.repeat_packed(0)
        }) else {
            return Err(LayoutError);
        };
        Ok((result, padded.size()))
    }

    /// Creates a layout describing the record for `self` followed by
    /// `next`, including any necessary padding to ensure that `next`
    /// will be properly aligned, but *no trailing padding*.
    ///
    /// In order to match C representation layout `repr(C)`, you should
    /// call `pad_to_align` after extending the layout with all fields.
    /// (There is no way to match the default Rust representation
    /// layout `repr(Rust)`, as it is unspecified.)
    ///
    /// Note that the alignment of the resulting layout will be the maximum of
    /// those of `self` and `next`, in order to ensure alignment of both parts.
    ///
    /// Returns `Ok((k, offset))`, where `k` is layout of the concatenated
    /// record and `offset` is the relative location, in bytes, of the
    /// start of the `next` embedded within the concatenated record
    /// (assuming that the record itself starts at offset 0).
    ///
    /// On arithmetic overflow, returns `LayoutError`.
    ///
    /// # Examples
    ///
    /// To calculate the layout of a `#[repr(C)]` structure and the offsets of
    /// the fields from its fields' layouts:
    ///
    /// ```rust
    /// # use std::alloc::{Layout, LayoutError};
    /// pub fn repr_c(fields: &[Layout]) -> Result<(Layout, Vec<usize>), LayoutError> {
    ///     let mut offsets = Vec::new();
    ///     let mut layout = Layout::from_size_align(0, 1)?;
    ///     for &field in fields {
    ///         let (new_layout, offset) = layout.extend(field)?;
    ///         layout = new_layout;
    ///         offsets.push(offset);
    ///     }
    ///     // Remember to finalize with `pad_to_align`!
    ///     Ok((layout.pad_to_align(), offsets))
    /// }
    /// # // test that it works
    /// # #[repr(C)] struct S { a: u64, b: u32, c: u16, d: u32 }
    /// # let s = Layout::new::<S>();
    /// # let u16 = Layout::new::<u16>();
    /// # let u32 = Layout::new::<u32>();
    /// # let u64 = Layout::new::<u64>();
    /// # assert_eq!(repr_c(&[u64, u32, u16, u32]), Ok((s, vec![0, 8, 12, 16])));

extend 的数学过程

  1. 新对齐 = max(self.align, next.align)
  2. offset = self.size 向上对齐到 next.align
  3. new_size = offset + next.size
  4. 用安全构造器再次验证整体仍合法
这正是 C ABI 字段排布最直观的实现。

extend_packed

  • 直接把 size 相加,完全不插 padding
  • 结果布局继承 self.align,不考虑 next.align
  • 只有当你明确知道“后续访问不要求自然对齐”时才适合使用
        }
    }

    /// Creates a layout describing the record for `self` followed by
    /// `next` with no additional padding between the two. Since no
    /// padding is inserted, the alignment of `next` is irrelevant,
    /// and is not incorporated *at all* into the resulting layout.
    ///
    /// On arithmetic overflow, returns `LayoutError`.
    #[stable(feature = "alloc_layout_extra", since = "1.95.0")]
    #[rustc_const_stable(feature = "alloc_layout_extra", since = "1.95.0")]
    #[inline]
    pub const fn extend_packed(&self, next: Self) -> Result<Self, LayoutError> {
        // SAFETY: each `size` is at most `isize::MAX == usize::MAX/2`, so the
        // sum is at most `usize::MAX/2*2 == usize::MAX - 1`, and cannot overflow.
        let new_size = unsafe { unchecked_add(self.size, next.size) };

repeat_packed

  • repeat 相比,它不保证每个元素都对齐
  • 适合描述字节流拼接、序列化缓冲区、紧凑元数据块
  • 不适合直接当作普通 [T; n] 的物理内存布局
    /// aligned.
    ///
    /// On arithmetic overflow, returns `LayoutError`.
    #[stable(feature = "alloc_layout_extra", since = "1.95.0")]
    #[rustc_const_stable(feature = "alloc_layout_extra", since = "1.95.0")]
    #[inline]
    pub const fn repeat_packed(&self, n: usize) -> Result<Self, LayoutError> {
        if let Some(size) = self.size.checked_mul(n) {
            // The safe constructor is called here to enforce the isize size limit.
            Layout::from_size_alignment(size, self.align)
        } else {
            Err(LayoutError)

array(n)

  • 标准数组布局 API:返回真正意义上的 [T; n] 布局
  • 它不是简单乘法,而是同时检查 usize 溢出与 isize 上限
  • 源码把逻辑提到内部 inner,减少每个 T 的单态化代码量
        Layout::from_size_alignment(new_size, self.align)
    }

    /// Creates a layout describing the record for a `[T; n]`.
    ///
    /// On arithmetic overflow or when the total size would exceed
    /// `isize::MAX`, returns `LayoutError`.
    #[stable(feature = "alloc_layout_manipulation", since = "1.44.0")]
    #[rustc_const_stable(feature = "const_alloc_layout", since = "1.85.0")]
    #[inline]
    pub const fn array<T>(n: usize) -> Result<Self, LayoutError> {
        // Reduce the amount of code we need to monomorphize per `T`.
        return inner(T::LAYOUT, n);

        #[inline]
        const fn inner(element_layout: Layout, n: usize) -> Result<Layout, LayoutError> {
            let Layout { size: element_size, align: alignment } = element_layout;

            // We need to check two things about the size:
            //  - That the total size won't overflow a `usize`, and
            //  - That the total size still fits in an `isize`.
            // By using division we can check them both with a single threshold.
            // That'd usually be a bad idea, but thankfully here the element size
            // and alignment are constants, so the compiler will fold all of it.
            if element_size != 0 && n > Layout::max_size_for_alignment(alignment) / element_size {
                return Err(LayoutError);
            }

            // SAFETY: We just checked that we won't overflow `usize` when we multiply.
            // This is a useless hint inside this function, but after inlining this helps
            // deduplicate checks for whether the overall capacity is zero (e.g., in RawVec's
            // allocation path) before/after this multiplication.
            let array_size = unsafe { unchecked_mul(element_size, n) };

            // SAFETY: We just checked above that the `array_size` will not
            // exceed `isize::MAX` even when rounded up to the alignment.
            // And `Alignment` guarantees it's a power of two.
            unsafe { Ok(Layout::from_size_alignment_unchecked(array_size, alignment)) }
        }

array 的阈值技巧

if n > max_size_for_alignment(alignment) / element_size
  • 通过一次除法同时检查乘法溢出与 isize 上限
  • 因为 element size / align 常量可折叠,编译器能把很多检查常量传播掉
  • 这是典型“源码看复杂,生成码更省”的写法

LayoutError

  • 错误类型极简:一个零字段结构体
  • 不区分“非 2 次幂”“越过 isize 上限”等细节
  • 设计偏向:错误路径简单、成功路径更重要

#[stable(feature = "alloc_layout", since = "1.28.0")]
#[deprecated(
    since = "1.52.0",
    note = "Name does not follow std convention, use LayoutError",
    suggestion = "LayoutError"
)]
pub type LayoutErr = LayoutError;

/// The `LayoutError` is returned when the parameters given
/// to `Layout::from_size_align`
/// or some other `Layout` constructor
/// do not satisfy its documented constraints.
#[stable(feature = "alloc_layout_error", since = "1.50.0")]
#[non_exhaustive]
#[derive(Clone, PartialEq, Eq, Debug)]
pub struct LayoutError;

#[stable(feature = "alloc_layout", since = "1.28.0")]
impl Error for LayoutError {}

// (we need this for downstream impl of trait Error)
#[stable(feature = "alloc_layout", since = "1.28.0")]
impl fmt::Display for LayoutError {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        f.write_str("invalid parameters to Layout::from_size_align")
    }
}

mod.rs:Allocator trait 概览

Allocator::allocate(&self, layout: Layout) 等 API 全都以 Layout 为输入。
  • 分配器不关心“这个类型是什么”
  • 它只关心 size / align 以及 grow / shrink 前后的布局
  • 说明 Layout 是分配子系统的 lingua franca
///
/// # Safety
///
/// Memory blocks that are [*currently allocated*] by an allocator,
/// must point to valid memory, and retain their validity until either:
///  - the memory block is deallocated, or
///  - the allocator is dropped.
///
/// Copying, cloning, or moving the allocator must not invalidate memory blocks returned from it.
/// A copied or cloned allocator must behave like the original allocator.
///
/// A memory block which is [*currently allocated*] may be passed to
/// any method of the allocator that accepts such an argument.
///
/// [*currently allocated*]: #currently-allocated-memory
#[unstable(feature = "allocator_api", issue = "32838")]
#[rustc_const_unstable(feature = "const_heap", issue = "79597")]
pub const unsafe trait Allocator {
    /// Attempts to allocate a block of memory.
    ///
    /// On success, returns a [`NonNull<[u8]>`][NonNull] meeting the size and alignment guarantees of `layout`.
    ///
    /// The returned block may have a larger size than specified by `layout.size()`, and may or may
    /// not have its contents initialized.
    ///
    /// The returned block of memory remains valid as long as it is [*currently allocated*] and the shorter of:
    ///   - the borrow-checker lifetime of the allocator type itself.
    ///   - as long as the allocator and all its clones have not been dropped.
    ///
    /// [*currently allocated*]: #currently-allocated-memory
    ///
    /// # Errors
    ///
    /// Returning `Err` indicates that either memory is exhausted or `layout` does not meet
    /// allocator's size or alignment constraints.
    ///
    /// Implementations are encouraged to return `Err` on memory exhaustion rather than panicking or
    /// aborting, but this is not a strict requirement. (Specifically: it is *legal* to implement
    /// this trait atop an underlying native allocation library that aborts on memory exhaustion.)
    ///
    /// Clients wishing to abort computation in response to an allocation error are encouraged to
    /// call the [`handle_alloc_error`] function, rather than directly invoking `panic!` or similar.
    ///
    /// [`handle_alloc_error`]: ../../alloc/alloc/fn.handle_alloc_error.html
    fn allocate(&self, layout: Layout) -> Result<NonNull<[u8]>, AllocError>;

    /// Behaves like `allocate`, but also ensures that the returned memory is zero-initialized.
    ///
    /// # Errors
    ///
    /// Returning `Err` indicates that either memory is exhausted or `layout` does not meet
    /// allocator's size or alignment constraints.
    ///
    /// Implementations are encouraged to return `Err` on memory exhaustion rather than panicking or
    /// aborting, but this is not a strict requirement. (Specifically: it is *legal* to implement
    /// this trait atop an underlying native allocation library that aborts on memory exhaustion.)
    ///
    /// Clients wishing to abort computation in response to an allocation error are encouraged to
    /// call the [`handle_alloc_error`] function, rather than directly invoking `panic!` or similar.
    ///
    /// [`handle_alloc_error`]: ../../alloc/alloc/fn.handle_alloc_error.html
    fn allocate_zeroed(&self, layout: Layout) -> Result<NonNull<[u8]>, AllocError> {

为什么 grow / shrink 要传 old_layout

  • 重新分配不仅要知道“新大小”,还要知道“旧块是按什么契约分配的”
  • 分配器可能依赖旧对齐、旧大小来决定是否原地扩容
  • 因此 old/new layout 都是接口的一部分

Allocator 的上层语义

  • Layout 负责静态约束
  • Allocator 负责动态资源决策
  • 两者分离,使容器、Arena、系统分配器、自定义分配器都可复用同一布局语言

global.rs:全局分配包装

  • alloc / dealloc / realloc 等接口最终仍收敛到 Layout
  • 全局 API 只是把底层分配器暴露成 process-wide 默认入口
  • 所以理解 Layout,基本也理解了 Rust 分配调用的参数模型
use crate::alloc::Layout;
use crate::{cmp, ptr};

/// A memory allocator that can be registered as the standard library’s default
/// through the `#[global_allocator]` attribute.
///
/// Some of the methods require that a memory block be *currently
/// allocated* via an allocator. This means that:
///
/// * the starting address for that memory block was previously
///   returned by a previous call to an allocation method
///   such as `alloc`, and
///
/// * the memory block has not been subsequently deallocated, where
///   blocks are deallocated either by being passed to a deallocation
///   method such as `dealloc` or by being
///   passed to a reallocation method that returns a non-null pointer.
///
///
/// # Example
///
/// ```standalone_crate
/// use std::alloc::{GlobalAlloc, Layout};
/// use std::cell::UnsafeCell;
/// use std::ptr::null_mut;
/// use std::sync::atomic::{AtomicUsize, Ordering::Relaxed};
///
/// const ARENA_SIZE: usize = 128 * 1024;
/// const MAX_SUPPORTED_ALIGN: usize = 4096;
/// #[repr(C, align(4096))] // 4096 == MAX_SUPPORTED_ALIGN
/// struct SimpleAllocator {
///     arena: UnsafeCell<[u8; ARENA_SIZE]>,
///     remaining: AtomicUsize, // we allocate from the top, counting down
/// }
///
/// #[global_allocator]
/// static ALLOCATOR: SimpleAllocator = SimpleAllocator {
///     arena: UnsafeCell::new([0x55; ARENA_SIZE]),
///     remaining: AtomicUsize::new(ARENA_SIZE),
/// };
///
/// unsafe impl Sync for SimpleAllocator {}
///
/// unsafe impl GlobalAlloc for SimpleAllocator {
///     unsafe fn alloc(&self, layout: Layout) -> *mut u8 {
///         let size = layout.size();
///         let align = layout.align();
///
///         // `Layout` contract forbids making a `Layout` with align=0, or align not power of 2.
///         // So we can safely use a mask to ensure alignment without worrying about UB.
///         let align_mask_to_round_down = !(align - 1);
///
///         if align > MAX_SUPPORTED_ALIGN {
///             return null_mut();
///         }
///
///         let mut allocated = 0;
///         if self
///             .remaining
///             .try_update(Relaxed, Relaxed, |mut remaining| {
///                 if size > remaining {
///                     return None;
///                 }
///                 remaining -= size;
///                 remaining &= align_mask_to_round_down;
///                 allocated = remaining;
///                 Some(remaining)
///             })
///             .is_err()
///         {

Layout 与 Vec / RawVec 的关系

  • 容器扩容时,真正危险的是“capacity × element_size”溢出
  • Layout::array 恰好就是处理这类问题的标准工具
  • 因此标准库把容量计算和布局校验尽量统一到 Layout API

repr(C) 结构体布局示例

源码注释给出了一个手工计算 repr(C) 字段偏移的例子。
  • Layout::from_size_align(0, 1) 起步
  • 每加入一个字段就调用 extend
  • 最后一定要调用 pad_to_align()

为什么最后要 pad_to_align

  • 字段之间的 padding 只保证“下一个字段”对齐
  • 结构体整体大小还要是其最大对齐的整数倍
  • 否则数组中的下一个 struct 起点会失去对齐
很多手写布局代码漏的就是这最后一步。

UML:Layout 相关对象

+-----------+        consumes        +-------------+
|  Layout    | --------------------> | Allocator   |
|-----------|                        |-------------|
| size       |                        | allocate    |
| align      |                        | grow/shrink |
+-----------+                        +-------------+
      |                                        ^
      | builds                                 |
      v                                        |
+---------------+      uses offsets      +-----------+
| repr(C) record | --------------------> | Raw memory |
+---------------+                       +-----------+

时序图:手工拼结构体

Caller -> Layout: from_size_align(0, 1)
loop field in fields
  Caller -> Layout: extend(field_layout)
  Layout --> Caller: (new_layout, offset)
end
Caller -> Layout: pad_to_align()
Layout --> Caller: final repr(C) layout

数据流:分配请求如何下沉

Type/Capacity
   ↓
Layout::new / Layout::array / extend
   ↓
合法性检查(size, align, isize bound)
   ↓
Allocator::allocate / grow / shrink
   ↓
Global allocator / custom allocator
   ↓
Raw pointer / NonNull<[u8]>

DST 场景 1:slice

  • 切片布局 = 头部固定前缀 + 元素大小 × 长度
  • for_value / for_value_raw 能通过胖指针长度恢复
  • 前提是长度元数据必须可信

DST 场景 2:trait object

  • trait object 的 size / align 藏在 vtable
  • 若 vtable 非法,布局计算也会变成未定义行为入口
  • 所以 raw 版本 API 明确把责任推回调用者

为何不用 repr(Rust)

  • extend 可以精确模拟 repr(C)
  • 但默认 repr(Rust) 字段顺序和布局细节未承诺稳定
  • 标准库文档明确说明:没有 API 可以“复刻 repr(Rust) 布局算法”

const fn 设计价值

  • 很多布局推导都能在 const 环境求值
  • 这使编译期数组大小、静态缓冲区、泛型常量更容易安全表达
  • 同时减少运行时分支与重复检查

为什么 Alignment 是类型而不是裸数字

  • 把“必须是 2 的幂且非零”从注释提升为类型系统约束
  • 公式调用者无需每次重新验证
  • 能显著减少 API 面的防御性代码

常见误区 1:把 align_to 当 pad_to_align

错法:以为提高 align 后 size 会自动补齐。
  • align_to(32) 可能仍保持原 size
  • 若你在构造 struct 尾部或数组布局,需要的是 pad_to_align

常见误区 2:packed layout 可直接按 T 访问

  • extend_packed / repeat_packed 省掉了 padding
  • 但也可能让后续元素或字段失去自然对齐
  • 这类内存往往只能按字节复制、序列化或用 unaligned 访问处理

常见误区 3:只检查 usize 不检查 isize

  • 64 位上 usize 能表示的范围比 Rust 指针偏移模型要求更大
  • 只看乘法是否溢出还不够
  • Layout 的价值恰恰在于把“语言级可表达范围”也纳入检查

反模式:手写 size * n

// 易错写法
let bytes = elem_size * n; // 只要一溢出就危险

// 更稳妥
let layout = Layout::array::<T>(n)?;
标准库已经帮你处理 overflow、isize 上限、对齐保持,没必要重复造轮子。

反模式:把 dangling 指针当 null 替身

  • dangling_ptr() 可能恰好落在一个“看起来有效”的地址
  • 它表达的是“对齐正确的无 provenance 指针”,不是“空值”
  • 延迟分配状态应显式建模,而不是赌某个地址不会被误用

性能视角:为什么代码长这样

  • 小函数 + const fn + 先证合法再 unchecked
  • 减少重复边界检查,降低 IR 噪音
  • 保留文档注释与 unsafe 契约,方便工具链验证
这是标准库一类很典型的风格:高层 API 安全,底层实现非常在乎 codegen 形状。

工程建议 1:优先组合 Layout API

  • 单对象:Layout::new<T>()
  • 数组:Layout::array<T>(n)
  • 多字段记录:extend + pad_to_align
  • 只有在证明成立时再使用 unchecked 版本

工程建议 2:把 offset 明确保存下来

  • extend 返回的 offset 非常关键
  • 别二次用“猜公式”重算字段位置
  • 把 offset 作为结构布局构建过程的副产物保留,最稳

工程建议 3:先定义契约再写 unsafe

  • 标准库不是先写 unsafe,再补注释
  • 而是先在 API / 注释中写出前提,再让实现变零成本
  • 这非常值得自定义 allocator / container 学习

对比:Layout 像什么

概念Rust Layout你真正得到的价值
C 的 sizeof/alignof更强,带合法性检查把错用拦在构造阶段
手写偏移计算extend / repeat API复用标准库算法
分配器参数包统一语言容器与 allocator 解耦

总结

一句话: Rust 把“内存布局”抽象成一个可组合、可验证、尽量 const 化的小对象,然后让整个分配生态围绕它转。
  • 它解决的不只是 size/align,而是语言级安全边界
  • extend / repeat / array 构成了布局组合代数
  • Allocator / GlobalAlloc 只是消费这个代数结果

延伸阅读

  • library/core/src/alloc/layout.rs
  • library/core/src/alloc/mod.rs
  • RawVec / Vec 扩容路径中的 Layout::array
  • Rustonomicon:布局、对齐、DST、FFI

访问链接:https://atcfu.com/ai-articles/rust-memory-layout/