Перейти к содержанию
Educora
Средний18 мин7 / 10

Коллекции, замыкания и итераторы

Храни данные в `Vec` и `HashMap`, пиши замыкания и превращай циклы в короткие понятные цепочки с методами итераторов `map`, `filter` и `collect`.

Проверь себя
В этом уроке ты узнаешь
  • Добавлять и удалять элементы Vec и обходить его тремя способами
  • Строить счётчик на HashMap и выводить результат в стабильном порядке
  • Писать замыкания, захватывающие переменные из окружения
  • Строить цепочки итераторов с map, filter, sum и collect

После каждого урока учитель добавляет в журнал новые оценки — список растёт. Потом он хочет посчитать, сколько раз встречается каждая оценка, — а это словарь «ключ → количество». Большинство реальных программ работает именно с такими коллекциями. В Rust для этого есть Vec и HashMap, а для их обработки — мощные итераторы.

Vec — растущий список

Vec<T> — список элементов одного типа переменного размера, хранящийся в куче. Его создают макросом vec![...] или через Vec::new(). push добавляет элемент в конец, pop удаляет последний и возвращает Option, а len даёт длину. v[i] при выходе за границы паникует, а v.get(i) возвращает None.

Rust
fn main() {
    let mut scores = vec![72, 95, 88];
    scores.push(64);
    println!("{:?} len={}", scores, scores.len());
    println!("first = {}", scores[0]);
    println!("get(10) = {:?}", scores.get(10));

    if let Some(last) = scores.pop() {
        println!("popped {last}");
    }
    for s in &mut scores {
        *s += 5;
    }
    println!("{:?}", scores);
}
Ожидаемый результат
[72, 95, 88, 64] len=4
first = 72
get(10) = None
popped 64
[77, 100, 93]
for s in &mut scores даёт изменяемую ссылку на каждый элемент; *s обращается к значению за ссылкой.
ЦиклЭлементv после цикла
for x in &v&T — чтениеможно использовать
for x in &mut v&mut T — изменениеможно использовать
for x in vT — владение забираетсяперемещён, использовать нельзя

HashMap — ключи и значения

HashMap<K, V> сопоставляет каждому ключу значение; его нужно подключить из std::collections. insert добавляет пару, а get возвращает Option<&V>. Самый короткий способ построить счётчик — entry API: entry(ключ).or_insert(0) создаёт ключ со значением 0, если его нет, и возвращает изменяемую ссылку на значение.

Rust
use std::collections::HashMap;

fn main() {
    let text = "the cat and the dog and the bird";
    let mut counts: HashMap<&str, u32> = HashMap::new();
    for word in text.split_whitespace() {
        *counts.entry(word).or_insert(0) += 1;
    }
    println!("the: {:?}", counts.get("the"));
    println!("fish: {:?}", counts.get("fish"));

    let mut pairs: Vec<(&str, u32)> = counts.into_iter().collect();
    pairs.sort();
    println!("{:?}", pairs);
}
Ожидаемый результат
the: Some(3)
fish: None
[("and", 2), ("bird", 1), ("cat", 1), ("dog", 1), ("the", 3)]
split_whitespace() делит текст на слова по пробелам. Чтобы показать результат в стабильном порядке, мы собрали пары в список и отсортировали его.

Замыкания

Замыкание (closure) — безымянная функция, написанная прямо на месте: |параметры| тело. Типы параметров обычно писать не нужно — компилятор выводит их из использования. Главная особенность замыкания — оно захватывает окружающие переменные: ниже add_bonus читает bonus, а click меняет clicks, поэтому само объявлено как mut.

Rust
fn main() {
    let bonus = 10;
    let add_bonus = |score: i32| score + bonus;
    let double = |x| x * 2;
    println!("{}", add_bonus(80));
    println!("{}", double(21));

    let mut clicks = 0;
    let mut click = || clicks += 1;
    click();
    click();
    println!("clicks = {clicks}");
}
Ожидаемый результат
90
42
clicks = 2

Итераторы: map, filter, collect

v.iter() создаёт итератор, который выдаёт элементы по одному. Адаптеры вроде map (преобразовать каждый элемент) и filter (оставить подходящие) ленивы: сами ничего не вычисляют, а лишь строят план. Работа начинается, когда вызывают завершающий метод — collect, sum, count или max. Для collect нужно указать тип результата, например Vec<i32>.

Rust
fn main() {
    let numbers = vec![1, 2, 3, 4, 5, 6];

    let squares: Vec<i32> = numbers.iter().map(|n| n * n).collect();
    let evens: Vec<i32> = numbers.iter().copied().filter(|n| n % 2 == 0).collect();
    let total: i32 = numbers.iter().sum();
    let max = numbers.iter().max();

    println!("{:?}", squares);
    println!("{:?}", evens);
    println!("total = {total}, max = {max:?}");
    println!("{:?}", numbers);
}
Ожидаемый результат
[1, 4, 9, 16, 25, 36]
[2, 4, 6]
total = 21, max = Some(6)
[1, 2, 3, 4, 5, 6]
iter() заимствует элементы, поэтому numbers в конце не изменился. copied() превращает ссылки &i32 в значения i32. Для пустого списка max вернул бы None, поэтому результат — Option.
Циклом
let names = vec!["aysel", "ali", "leyla", "rauf"];
let mut result = Vec::new();
for name in &names {
    if name.len() > 3 {
        result.push(name.to_uppercase());
    }
}
Цепочкой итераторов
let names = vec!["aysel", "ali", "leyla", "rauf"];
let result: Vec<String> = names
    .iter()
    .filter(|name| name.len() > 3)
    .map(|name| name.to_uppercase())
    .collect();
Оба варианта дают ["AYSEL", "LEYLA", "RAUF"]. Цепочка читается сверху вниз как описание «что мы делаем», а result создаётся без mut.
МетодЧто делает
map(f)преобразует каждый элемент через f
filter(p)оставляет элементы, для которых p истинно
enumerate()добавляет к каждому элементу индекс
zip(other)попарно объединяет два итератора
take(n), skip(n)берёт / пропускает первые n элементов
sum(), count(), max()сумма, количество, максимум
any(p), all(p), find(p)подходит ли хоть один / все ли / найти первый
collect()собирает результат в коллекцию

Главное

  • Vec<T> — растущий список: push, pop, len; get(i) за границами возвращает None.
  • Счётчик в HashMap: *map.entry(k).or_insert(0) += 1; перед выводом сортируй или используй BTreeMap.
  • Замыкание записывается как |x| выражение и может захватывать окружающие переменные.
  • map и filter ленивы; работа начинается с завершающего метода вроде collect, sum или count.
  • iter() заимствует, а into_iter() забирает владение.

Проверь себя

Вопросов: 10. Каждый правильный ответ приносит XP.

1 / 10
Что вернёт v.get(10) для Vec из трёх элементов?