# Создание и использование структур в Rust: Полное руководство

> Практическое руководство по структурам в Rust: классические структуры, кортежные структуры, структуры-маркеры, управление видимостью полей и синтаксис инициализации.

- **URL:** https://eagle2000.space/rust/rust-struct/
- **Date:** 2025-02-04

- **Tags:** rust, dev

---## Введение. Что такое структура

Структура (или struct) в Rust — это пользовательский тип данных, который позволяет группировать связанные данные вместе. Структуры похожи на классы в других языках программирования, но в Rust (как говорит документация) они более гибкие и мощные, так как язык предоставляет различные возможности для работы с ними. Кто бы сомневался 😁 При этом видов структур существует несколько. 

В этой главе мы подробно рассмотрим как классические C-структуры, так и структуры-кортежи, юнит-структуры, а также способы работы с ними.

## Шаг 1. Проектирование структуры

Прежде чем писать код, важно спроектировать и описать, что мы хотим получить. Давайте возьмем нашу любимую тему по системам для управления умным домом. В нашей системе есть хаб (центральное устройство) и несколько сенсоров, которые подключены к этому хабу.

### Диаграмма классов

<div style="background-color:white; padding: 20px">
{{< mermaid >}}
classDiagram
    class HUB {
        +String name
        +String id
        +String location
        +Vec~Sensor~ sensors
    }

    class Sensor {
        +String name
        +String id
        +String type
        +f32 value
        +String unit
        +bool isActive
    }

    HUB "1" --> "n" Sensor : хаб хранит сенсоры
{{< /mermaid >}}
</div>

### Описание структур и полей:
* **Hub**:
    * `name: String` — имя хаба.
    * `id: String` — уникальный идентификатор хаба.
    * `location: String` — местоположение хаба.
    * `sensors: Vec<Sensor>` — список сенсоров, подключенных к хабу.
* **Sensor**:
    * `name: String` — имя сенсора.
    * `id: String` — уникальный идентификатор сенсора.
    * `sensor_type: String` — тип сенсора (например, "температура", "влажность" и т.д.).
    * `value: f32` — текущее значение, измеренное сенсором.
    * `unit: String` — единица измерения (например, "°C", "%" и т.д.).
    * `is_active: bool` — статус сенсора (активен/неактивен).

## Шаг 2. Разновидности структур и синтаксис инициализации

Rust поддерживает три основных вида структур:
1. **Структуры с именованными полями** (классические C-структуры).
2. **Структуры-кортежи** (Tuple Structs).
3. **Юнит-структуры** (Unit-like Structs).

Давайте изучим все виды структур, сокращенную инициализацию, синтаксис перехода (`..`) и правила видимости полей на интерактивных слайдах ниже:


### 💻 Структуры в Rust


#### Шаг 1

```rust
#[allow(dead_code)]
#[derive(Debug)]
struct Hub {
    name: String,
    id: String,
    location: String,
    sensors: Vec<Sensor>,
}

#[allow(dead_code)]
#[derive(Debug)]
struct Sensor {
    name: String,
    id: String,
    sensor_type: String,
    value: f32,
    unit: String,
    is_active: bool,
}

fn main() {
    let hub = Hub {
        name: "Main Hub".to_string(),
        id: String::from("HUB001"),
        location: "Room 101".to_string(),
        sensors: Vec::new(), // пустой список сенсоров
    };

    println!("{:#?}", hub);
}
```

**Объявление и базовая инициализация структур**
- Структуры определяются с помощью ключевого слова `struct`.
- Атрибут `#[derive(Debug)]` позволяет выводить структуру в отладочном формате с помощью `{:#?}`.


#### Шаг 2

```rust
#[allow(dead_code)]
#[derive(Debug)]
struct Hub {
    name: String,
    id: String,
    location: String,
    sensors: Vec<Sensor>,
}

#[allow(dead_code)]
#[derive(Debug)]
struct Sensor {
    name: String,
    id: String,
    sensor_type: String,
    value: f32,
    unit: String,
    is_active: bool,
}

fn main() {
    let sensor1 = Sensor {
        name: "Temperature Sensor".to_string(),
        id: "TEMP001".to_string(),
        sensor_type: "Temperature".to_string(),
        value: 25.3,
        unit: "°C".to_string(),
        is_active: true,
    };

    let sensor2 = Sensor {
        name: "Humidity Sensor".to_string(),
        id: "HUM001".to_string(),
        sensor_type: "Humidity".to_string(),
        value: 60.5,
        unit: "%".to_string(),
        is_active: true,
    };

    let hub = Hub {
        name: "Main Hub".to_string(),
        id: String::from("HUB001"),
        location: "Room 101".to_string(),
        sensors: vec![sensor1, sensor2],
    };

    println!("{:#?}", hub);
}
```

**Структуры со вложенными коллекциями**
- Мы создали экземпляры `Sensor` и передали их в поле `sensors` структуры `Hub`.
- Макрос `vec!` используется для удобной инициализации вектора `Vec<Sensor>`.


#### Шаг 3

```rust
#[derive(Debug, Default)]
struct Foo {
    a: u32,
    b: u8,
}

fn main() {
    let a = 42;
    let b = 13;

    // Сокращенный синтаксис инициализации (field init shorthand)
    // Имена переменных совпадают с именами полей
    let x = Foo { a, b };

    // Синтаксис перехода (struct update syntax ..)
    // Копирование (перенос) значений оставшихся полей из другой структуры
    let y = Foo { a: 50, ..x };

    // Инициализация структуры с дефолтными значениями, кроме поля a
    let z = Foo { a: 100, ..Default::default() };

    println!("x: {:?}", x);
    println!("y: {:?}", y);
    println!("z: {:?}", z);
}
```

**Сокращенный синтаксис и синтаксис перехода**
- Если имена переменных совпадают с именами полей, можно писать `a` вместо `a: a` (field init shorthand).
- Синтаксис `..` позволяет скопировать остальные поля из другого экземпляра той же структуры.
- Типаж `Default` позволяет инициализировать поля по умолчанию с помощью `..Default::default()`.


#### Шаг 4

```rust
// Объявление структуры-кортежа
#[derive(Debug)]
struct Bar(u32, bool);

fn main() {
    // Создание экземпляра
    let x = Bar(42, true);

    // Доступ к полям по порядковому индексу
    let number = x.0;
    let flag = x.1;

    println!("x: {:?}", x);
    println!("Поля: number = {}, flag = {}", number, flag);

    // Неявное представление конструктора структуры-кортежа как функции
    let constructor: fn(u32, bool) -> Bar = Bar;
    let y = constructor(100, false);
    println!("y: {:?}", y);
}
```

**Структуры-кортежи (Tuple Structs)**
- Поля в структурах-кортежах не имеют названий, а определяются только своими типами.
- Доступ к полям осуществляется по порядковому индексу через точку: `x.0`, `x.1`.
- Имя структуры-кортежа выступает в роли функции-конструктора (например, `Bar` имеет тип `fn(u32, bool) -> Bar`).


#### Шаг 5

```rust
// Объявление юнит-структуры
#[derive(Debug)]
struct Baz;

fn main() {
    // Корректное создание (без использования скобок)
    let x = Baz;

    println!("x: {:?}", x);
}
```

**Юнит-структуры (Unit-like Structs)**
- Объявляются вообще без использования скобок.
- Создаются также без использования скобок: `let x = Baz;`.
- Обычно используются как маркеры или для реализации типажей на них без хранения данных.


#### Шаг 6

```rust
// Структура с полями разного уровня видимости
#[allow(dead_code)]
pub struct VisibleStruct {
    pub a: u32,          // Публичное поле (доступно везде)
    pub(crate) b: u8,    // Доступно только внутри текущего крейта
    c: u64,              // Приватное поле (доступно только внутри текущего модуля)
}

fn main() {
    let x = VisibleStruct { a: 1, b: 2, c: 3 };
    println!("a = {}, b = {}", x.a, x.b);
    // Доступ к x.c разрешен, так как main находится в том же модуле
    println!("c = {}", x.c);
}
```

**Управление видимостью полей**
- Поля структур приватны по умолчанию.
- Модификатор `pub` делает поле видимым извне текущего модуля и крейта.
- Модификатор `pub(crate)` делает поле видимым только внутри текущего крейта.


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

1. **Шаг 1. Определение и базовая инициализация**:
   - Мы определили классические структуры с именованными полями `Hub` и `Sensor`.
   - Инициализировали экземпляр структуры `Hub` с пустым вектором `sensors` (`Vec::new()`).

2. **Шаг 2. Структуры со вложенными коллекциями**:
   - Мы создали два сенсора и проинициализировали структуру `Hub`, передав сенсоры во вложенный массив с помощью макроса `vec!`.

3. **Шаг 3. Сокращенный синтаксис и синтаксис перехода**:
   - **Field Init Shorthand:** Если имена переменных совпадают с именами полей структуры, мы можем просто указать имя переменной (например, `let x = Foo { a, b };`).
   - **Struct Update Syntax:** С помощью `..` мы можем скопировать или перенести значения оставшихся полей из другого экземпляра структуры (например, `let y = Foo { a: 50, ..x };`).

4. **Шаг 4. Структуры-кортежи (Tuple Structs)**:
   - Именованный тип, поля которого не имеют названий, а определяются только типами в круглых скобках: `struct Bar(u32, bool);`.
   - Доступ к полям осуществляется по порядковому индексу: `x.0`, `x.1`.
   - Тип структуры-кортежа является функцией-конструктором: `let constructor: fn(u32, bool) -> Bar = Bar;`.
   - Часто используется для паттерна **Newtype** (создание безопасной обертки вокруг одного типа).

5. **Шаг 5. Юнит-структуры (Unit-like Structs)**:
   - Объявляются с помощью ключевого слова `struct` вообще без использования скобок: `struct Baz;`.
   - Создаются аналогично без скобок: `let x = Baz;`.

6. **Шаг 6. Видимость и управление доступом к полям**:
   - По умолчанию все поля приватны (доступны только внутри текущего модуля).
   - Модификаторы видимости управляют доступом:
     - `pub` — поле публично везде.
     - `pub(crate)` — поле видимо только внутри текущего крейта.
     - `pub(super)` — поле видимо только родительскому модулю.

> **Примечание по видимости:** Даже если поле структуры помечено как `pub`, сама структура должна быть доступна (публична) внешнему коду, чтобы поля можно было прочитать.

---

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

Пройдите короткий интерактивный тест, чтобы закрепить понимание структур в Rust.


### 📝 Структуры в Rust


**1. Что из нижеперечисленного позволяет использовать сокращенную инициализацию полей (field init shorthand)?**

- [ ] Если все поля структуры имеют одинаковый тип
- [ ] Если имена переменных совпадают с именами полей структуры
- [ ] Если структура является юнит-структурой
- [ ] Если все поля структуры помечены как публичные (pub)

**2. Какое ключевое выражение используется для обновления полей структуры на основе другого экземпляра того же типа?**

- [ ] ..instance
- [ ] copy instance
- [ ] extends instance
- [ ] using instance

**3. Как осуществляется доступ к полям в структуре-кортеже (Tuple Struct)?**

- [ ] По именам полей, заданным при объявлении
- [ ] По порядковому индексу через точку (например, `x.0`, `x.1`)
- [ ] С помощью квадратных скобок (например, `x[0]`)
- [ ] Через вызов метода `.get(index)`

**4. Какое из утверждений верно для юнит-структур (Unit-like Structs)?**

- [ ] Они всегда занимают ровно 1 байт в памяти
- [ ] Они объявляются и инициализируются вообще без использования скобок
- [ ] Для их инициализации обязательно вызывать `Baz()` с круглыми скобками
- [ ] Они не могут реализовывать никакие типажи (traits)

**5. Что делает модификатор видимости `pub(crate)` перед полем структуры?**

- [ ] Делает поле видимым только внутри текущего файла исходного кода
- [ ] Ограничивает видимость поля пределами текущего модуля
- [ ] Делает поле видимым и доступным исключительно в пределах текущего крейта (crate)
- [ ] Разрешает изменять поле только из внешних пакетов

