# Hash Map в Rust: полное руководство по HashMap и ключам

> Что такое Hash Map в языке Rust? Узнайте, как эффективно работать с коллекцией HashMap, хэшированием, ключами и значениями на практических примерах.

- **URL:** https://eagle2000.space/rust/rust-hashmap/
- **Date:** 2025-03-05

- **Tags:** rust, dev

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

Hash Map — это коллекция пар "ключ-значение", которая позволяет быстро находить значения по ключу. Представьте ее как справочник, где вы моментально получаете нужную информацию.

## Создание HashMap и добавление элементов

Для создания пустого HashMap используется функция `HashMap::new()`. Чтобы наполнить коллекцию данными, её необходимо сделать изменяемой с помощью ключевого слова `mut`, после чего использовать метод `insert(key, value)`.

Давайте посмотрим на интерактивном слайде, как создаётся HashMap, вставляются новые значения и как обновить существующее значение:


### 💻 Базовые операции с HashMap: создание, вставка и обновление


#### Шаг 1

```rust
use std::collections::HashMap;

fn main() {
    // Создаем изменяемый HashMap
    let mut smart_home = HashMap::new();

    // Добавляем устройства и их состояние
    smart_home.insert("гостиная_свет".to_string(), "включен".to_string());
    smart_home.insert("спальня_термостат".to_string(), "22°C".to_string());

    println!("Устройства: {:?}", smart_home);
}
```

**Создание HashMap и добавление элементов**
- Для использования `HashMap` импортируем его из `std::collections`.
- Чтобы наполнить коллекцию данными, она должна быть объявлена изменяемой (`mut`).
- Метод `insert(key, value)` добавляет новую пару ключ-значение в мапу.


#### Шаг 2

```rust
use std::collections::HashMap;

fn main() {
    let mut smart_home = HashMap::new();
    smart_home.insert("гостиная_свет".to_string(), "включен".to_string());
    smart_home.insert("спальня_термостат".to_string(), "22°C".to_string());

    // Обновляем значение по существующему ключу
    smart_home.insert("гостиная_свет".to_string(), "выключен".to_string());

    println!("Обновленные устройства: {:?}", smart_home);
}
```

**Обновление значения по ключу**
- Если мы вызываем `insert` с ключом, который уже существует в `HashMap`, старое значение перезаписывается новым.


## Получение значений и Entry API

При поиске значений в HashMap по ключу основным инструментом является метод `.get()`. Он возвращает перечисление `Option<&V>`, что защищает программу от ошибок обращения к отсутствующим элементам. Также Rust предоставляет мощный Entry API для условных операций (добавление или изменение значений только при выполнении определенных условий).

Давайте подробно разберем, как искать элементы, обрабатывать их отсутствие и использовать Entry API на интерактивном слайде:


### 💻 Поиск значений, unwrap_or и Entry API


#### Шаг 1

```rust
use std::collections::HashMap;

fn main() {
    let mut smart_home = HashMap::new();
    smart_home.insert("гостиная_свет", "включен");

    // Метод get() возвращает Option<&V>
    let light_status = smart_home.get("гостиная_свет");
    let lock_status = smart_home.get("дверь_замок");

    println!("Свет в гостиной: {:?}", light_status);
    println!("Замок двери: {:?}", lock_status);
}
```

**Поиск значений по ключу через .get()**
- Метод `.get()` принимает ссылку на ключ и возвращает `Option<&V>`.
- Если ключ найден, возвращается `Some(&value)`. Если ключа нет, возвращается `None`.
- Это позволяет безопасно проверять наличие данных без риска паники во время работы.


#### Шаг 2

```rust
use std::collections::HashMap;

fn main() {
    let mut smart_home = HashMap::new();
    smart_home.insert("гостиная_свет", "включен");

    // Получаем значение с дефолтом, если ключ отсутствует
    let default_status = "выключен";
    let light = smart_home.get("гостиная_свет").unwrap_or(&default_status);
    let lock = smart_home.get("дверь_замок").unwrap_or(&default_status);

    println!("Свет: {}", light);
    println!("Замок: {}", lock);
}
```

**Безопасное извлечение с unwrap_or**
- Метод `.unwrap_or()` позволяет задать запасное значение, которое будет использовано, если `.get()` вернул `None`.


#### Шаг 3

```rust
use std::collections::HashMap;

fn main() {
    let mut smart_home = HashMap::new();
    smart_home.insert("гостиная_свет".to_string(), "включен".to_string());

    // or_insert добавляет значение, только если ключа нет
    smart_home.entry("кухня_чайник".to_string()).or_insert("выключен".to_string());
    smart_home.entry("гостиная_свет".to_string()).or_insert("включен".to_string());

    // and_modify изменяет значение, только если ключ есть
    smart_home.entry("гостиная_свет".to_string())
        .and_modify(|status| *status = "выключен".to_string());

    println!("Итоговое состояние: {:?}", smart_home);
}
```

**Entry API: or_insert и and_modify**
- Метод `.entry()` возвращает перечисление Entry, представляющее запись (присутствующую или отсутствующую).
- Метод `.or_insert()` вставляет значение, только если ключа нет, и возвращает мутабельную ссылку на значение.
- Метод `.and_modify()` позволяет выполнить замыкание для изменения существующего значения по ключу.


### Способы обработки результатов поиска

1. **Обработка отсутствующих ключей с помощью сопоставления с образцом (`match`)**:
   Поскольку `.get()` возвращает `Option<&V>`, вы можете использовать сопоставление шаблонов для детальной обработки обоих сценариев (`Some` и `None`):
   ```rust
   match smart_home_devices.get("гостиная_свет") {
       Some(status) => println!("Статус света: {}", status),
       None => println!("Статус света не найден"),
   }
   ```

2. **Значения по умолчанию с `unwrap_or`**:
   Если вместо детальной обработки `match` вам достаточно подставить стандартное значение в случае отсутствия ключа, используйте метод `.unwrap_or()`:
   ```rust
   let default_status = "выключен".to_string();
   let status = smart_home_devices.get("гостиная_свет").unwrap_or(&default_status);
   ```

---

## Практический пример

Давайте рассмотрим практический пример, связанный с управлением умным домом. Предположим, у нас есть несколько устройств в доме и нам нужно отслеживать их текущий статус. Например, мы хотим знать, включен ли свет в гостиной, какая температура установлена на термостате в спальне и работает ли кондиционер на кухне. Если статус какого-то устройства неизвестен, мы хотим выводить соответствующее сообщение.

- У нас есть список всех устройств, которые нас интересуют.
- Для каждого устройства нужно вывести его название и текущий статус из HashMap, подставив значение по умолчанию для неизвестных приборов.

Давайте посмотрим, как работает этот алгоритм на практике с помощью интерактивного слайда:


### 💻 Практический пример: Мониторинг умного дома


```rust
use std::collections::HashMap;

fn main() {
    // Создаем HashMap для хранения статусов устройств
    let mut home_device_status: HashMap<String, String> = HashMap::new();

    // Добавляем статусы для некоторых устройств
    home_device_status.insert("гостиная_свет".to_string(), "включен".to_string());
    home_device_status.insert("спальня_термостат".to_string(), "22°C".to_string());

    // Список интересующих нас приборов
    let devices = vec!["гостиная_свет", "спальня_термостат", "кухня_кондиционер"];
    let default_device_status = "статус неизвестен".to_string();

    // Перебираем все устройства и выводим их статус
    for device in devices {
        let status = home_device_status.get(device).unwrap_or(&default_device_status);
        println!("{}: {}", device, status);
    }
}
```

**Практический пример: Мониторинг умного дома**
- Мы обходим вектор интересующих нас приборов и ищем состояние каждого в `HashMap`.
- Для прибора `кухня_кондиционер`, которого нет в мапе, метод `.unwrap_or()` подставляет запасное значение `статус неизвестен`.


## Почему HashMap так полезен?

- 🚀 **Быстрый доступ**: поиск, добавление и удаление элементов происходят за константное время `O(1)` в среднем.
- 🔄 **Entry API**: очень лаконичный и безопасный способ условной работы с коллекцией без лишних проверок.
- 🛡️ **Безопасность типизации**: компилятор Rust гарантирует, что ключи и значения будут строго соответствовать объявленным типам.

## Заключение

HashMap предоставляет мощный способ работы с парами "ключ-значение". Они идеально подходят для задач, требующих быстрого поиска и эффективного управления данными.

{{< lead >}}
Используйте HashMap, когда:
- Вы хотите связать произвольный ключ с произвольным значением.
- Вам нужен кэш.
- Вам нужна эффективная ассоциативная коллекция без сохранения порядка вставки.
{{< /lead >}}

[Официальная документация по HashMap](https://doc.rust-lang.org/std/collections/struct.HashMap.html)

---

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

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


### 📝 HashMap в Rust


**1. Из какого модуля стандартной библиотеки Rust необходимо импортировать HashMap?**

- [ ] std::collections::HashMap
- [ ] std::hash::HashMap
- [ ] std::collections::hash_map::HashMap
- [ ] std::map::HashMap

**2. Какой тип возвращает метод `HashMap::get()` при поиске по ключу?**

- [ ] Непосредственно тип значения V, либо вызывает панику, если ключ отсутствует
- [ ] Option<&V> (ссылку на значение, обёрнутую в Option)
- [ ] Result<&V, Error>
- [ ] Option<V> (копию значения, обёрнутую в Option)

**3. Что произойдет при вызове метода `.insert()` с ключом, который уже существует в HashMap?**

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

**4. Какой метод Entry API позволяет вставить значение по умолчанию только в случае отсутствия ключа?**

- [ ] and_modify()
- [ ] or_insert()
- [ ] insert_default()
- [ ] unwrap_or()

**5. Почему порядок обхода пар в HashMap при итерации является произвольным и меняется при перезапусках?**

- [ ] Из-за оптимизации компилятора под разные архитектуры процессоров
- [ ] Rust намеренно использует случайную соль для хэширования, чтобы защитить приложение от атак типа HashDoS (переполнение коллизиями)
- [ ] Это вызвано ошибкой в стандартной библиотеке
- [ ] Для уменьшения потребления оперативной памяти




