# Низкоуровневое представление типов в памяти в Rust (Type Layout)

> Как компилятор Rust располагает типы в памяти: выравнивание, размер, паддинг, оптимизации переупорядочивания полей и атрибуты представления (repr).

- **URL:** https://eagle2000.space/rust/rust-type-layout/
- **Date:** 2026-08-01

- **Tags:** rust, dev

---## Введение

Писать безопасный и быстрый код — это здорово, но иногда полезно понимать, как именно наши данные располагаются на физическом уровне в оперативной памяти. В Rust компилятор берет на себя управление памятью, оптимизируя размеры типов и выравнивание. В этой статье мы разберем основные понятия **Type Layout** в Rust.

## Поведение компилятора по умолчанию

* **Управление памятью:** Компилятор Rust автоматически рассчитывает и задает типу такие размеры (`size`) и выравнивание (`alignment`), которые гарантированно удовлетворяют техническим требованиям всех его внутренних полей на целевой архитектуре процессора.
* **Паддинг (Padding):** Компилятор имеет право автоматически вставлять неявные байты заполнения (паддинг) в структуру для обеспечения корректного выравнивания данных в памяти.
* **Переупорядочивание полей:** По умолчанию Rust **не гарантирует** сохранение порядка полей, написанного в коде. Компилятор оставляет за собой право произвольно менять порядок полей в кортежах, структурах и перечислениях для оптимизации размера и минимизации паддинга.

Давайте изучим выравнивание, размеры типов, использование атрибутов представления `#[repr(...)]` и работу с тегами перечислений на интерактивных слайдах ниже:


### 💻 Расположение типов в памяти в Rust


#### Шаг 1

```rust
use std::mem::{size_of, align_of};

#[allow(dead_code)]
struct Foo {
    a: u8,
    b: u32,
    c: u16,
}

fn main() {
    println!("Размер Foo: {} байт", size_of::<Foo>());
    println!("Выравнивание Foo: {} байт", align_of::<Foo>());
    
    // Если бы поля лежали в памяти строго по порядку (a, b, c):
    // 1 байт (a) + 3 байта паддинга + 4 байта (b) + 2 байта (c) + 2 байта паддинга = 12 байт.
    // Однако компилятор Rust переупорядочил поля в памяти (b, c, a):
    // 4 байта (b) + 2 байта (c) + 1 байт (a) + 1 байт паддинга = 8 байт!
}
```

**Выравнивание и размер по умолчанию**
- Компилятор Rust автоматически вставляет байты заполнения (padding) для правильного выравнивания.
- По умолчанию Rust свободно меняет порядок полей в памяти для минимизации размера структуры.
- Для `Foo` размер уменьшается с 12 до 8 байт благодаря оптимизации переупорядочивания полей.


#### Шаг 2

```rust
use std::mem::size_of;

#[repr(C)]
#[allow(dead_code)]
struct StructC {
    a: u8,
    b: u32,
    c: u16,
}

#[repr(align(16))]
#[allow(dead_code)]
struct StructAlign {
    a: u32,
}

#[repr(transparent)]
#[allow(dead_code)]
struct Wrapper(u32);

fn main() {
    println!("Размер StructC (repr(C)): {} байт", size_of::<StructC>());
    println!("Размер StructAlign (repr(align(16))): {} байт", size_of::<StructAlign>());
    println!("Размер Wrapper (repr(transparent)): {} байт", size_of::<Wrapper>());
}
```

**Атрибуты представления #[repr(...)]**
- `#[repr(C)]` выстраивает поля строго по порядку объявления (как в C/C++), запрещая переупорядочивание.
- `#[repr(align(N))]` задает минимальное выравнивание структуры в памяти (в байтах).
- `#[repr(transparent)]` гарантирует, что структура-обертка имеет точно такое же представление, как и ее единственное поле.


#### Шаг 3

```rust
#[repr(u32)] // Задаем тип тега (дискриминанта) в памяти как u32
#[allow(dead_code)]
enum Status {
    Active = 42,
    Inactive = 24,
    Pending = 134,
}

fn main() {
    let val = Status::Active;
    
    // Приведение перечисления к целому числу с помощью оператора `as`
    let num = val as u32;
    
    println!("Тег Status::Active в памяти равен: {}", num);
}
```

**Дискриминанты перечислений**
- Мы можем явно задавать числовые значения (дискриминанты) вариантам enums.
- Атрибут `#[repr(u32)]` (или другие целочисленные типы) фиксирует тип хранения тега в памяти.
- Варианты можно приводить к соответствующему числу с помощью оператора `as`.


### Детали реализации и шаги:

1. **Шаг 1. Выравнивание и размер по умолчанию:**
   - Каждому типу данных требуется выравнивание в памяти: адрес начала данных должен быть кратен выравниванию типа (обычно степени двойки: 1, 2, 4, 8 байт).
   - Из-за этого между полями могут образовываться пустые байты — **паддинг**.
   - По умолчанию (представление `repr(Rust)`) компилятор переупорядочивает поля структуры `Foo` так, чтобы они занимали 8 байт вместо 12 байт, размещая сначала самые строгие по выравниванию типы.

2. **Шаг 2. Управление расположением данных с помощью атрибутов:**
   Для явного изменения логики размещения данных в памяти и обеспечения совместимости с другими языками используются специальные атрибуты представления:
   - `#[repr(C)]` — отключает оптимизацию переупорядочивания полей Rust и выстраивает их строго в том порядке, в котором они объявлены (совместимо с ABI языка C).
   - `#[repr(align(N))]` — принудительно устанавливает минимальное выравнивание типа в памяти (например, 16 байт).
   - `#[repr(transparent)]` — гарантирует, что тип имеет точно такое же представление в памяти и ABI, как и его единственное некорректное поле (используется для newtype-оберток).

3. **Шаг 3. Явное тегирование перечислений (Enum Discriminants):**
   - Для перечислений можно жестко фиксировать тип числового тега (дискриминанта) в памяти и присваивать явные числовые значения каждому варианту.
   - Это позволяет осуществлять безопасное приведение типов (кастинг) из перечисления в целочисленный тип с помощью ключевого слова `as`.

---

## Проверь свои знания!

Пройдите короткий интерактивный тест, чтобы закрепить понимание расположения типов данных в памяти.


### 📝 Type Layout в Rust


**1. Что по умолчанию делает компилятор Rust с порядком полей в структурах и кортежах?**

- [ ] Сохраняет порядок полей точно в таком виде, как они объявлены в коде
- [ ] Может произвольно менять порядок полей для минимизации размера типа и паддинга
- [ ] Сортирует поля строго по алфавиту их имен
- [ ] Размещает все поля по случайным адресам в куче

**2. Какой атрибут представления отключает оптимизацию переупорядочивания полей в Rust и выстраивает их в порядке объявления?**

- [ ] #[repr(align(8))]
- [ ] #[repr(transparent)]
- [ ] #[repr(C)]
- [ ] #[repr(packed)]

**3. Зачем используется атрибут `#[repr(transparent)]`?**

- [ ] Чтобы сделать структуру полностью невидимой для отладчика
- [ ] Чтобы гарантировать, что тип имеет точно такое же представление в памяти и ABI, как и его единственное не-нулевое поле
- [ ] Чтобы автоматически привести тип к строковому представлению
- [ ] Чтобы компилятор игнорировал правила выравнивания для этого типа

**4. Каким образом в Rust можно получить числовой тег (дискриминант) варианта перечисления, если задано #[repr(u32)]?**

- [ ] С помощью метода `.to_u32()`
- [ ] С помощью приведения типов через ключевое слово `as` (например, `val as u32`)
- [ ] Обратившись к полю `val.tag`
- [ ] Это невозможно сделать в безопасном коде

**5. Что такое паддинг (Padding) в контексте расположения данных в памяти?**

- [ ] Неявные байты заполнения, вставляемые компилятором для обеспечения корректного выравнивания полей
- [ ] Специальный тип данных для работы со строками
- [ ] Процесс копирования структуры из стека в кучу
- [ ] Очистка неиспользуемой памяти сборщиком мусора

