Перейти к основному содержимому
  1. Rust/

Императивный и функциональный Rust: работа с методом collect

·980 слов·5 минут· loading · loading · · ·Rust-middle
О Rust - Эта статья часть цикла.
Статей прочитано 0/36
0%
📚 Введение и дополнительные материалы
🟢 Начальный уровень (Rust-basic)
Не прочитана
🔵 Средний уровень (Rust-middle)
21 Императивный и функциональный Rust: работа с методом collect (текущая)
Не прочитана

Разные подходы для решения одной задачи
#

Введение:
#

В программировании приняты несколько парадигм. Cегодня остановлюсь и рассмотрю императивный и функциональные стили.

Давайте напишем код который выполняет одинаковую задачу, но использует при этом различные подходы. Но до этого краткая справка по стилям.

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

Функциональное программирование плотно интегрировано в современные языки: будь то js, ruby, java, dart и др. Map, reduce, filter наверняка уже где-то встречались 😉

Функциональный подход, это когда функция возвращает другую функцию. Вот такое краткое определение 😆 которое конечно не отражает всего многообразия этого царства. Но я не хочу тратить ваше время, а поэтому поехали писать код)

Если хотите углубиться в функциональную тему, то вот замечательная книга: “Грокаем функциональное мышление” Эрика Норманда

Приложение:
#

Давайте представим что нам нужно написать приложение для умного дома которое фильтрует и обрабатывает данные от сенсоров.

Определим структуру для данных от датчиков
#

Пусть это будет температурный датчик DS18B20 с тремя ногами и работющий по протоколу 1-wire. Это не так важно для нашего примера, но зато позволяет проникнуть в мир умного дома и немного оживляет повествование 😁

#[derive(Debug)]
struct Sensor {
    id: String,
    temperature: f32,
}

Определили структуру для сенсора, переходим к основному коду.

Примечание: derive(Debug) trait помогает при отладке кода и позволяет вывести информацию об объекте передав {:?} или {:#?} при форматировании строки. Пример -> println!("New sensor found: {:?}", Sensor { id: "123".to_string(), temperature: 20.0 }); подробнее в доке

Реализация в разных стилях
#

Давайте посмотрим на оба подхода в действии. Ниже представлены интерактивные слайды, где на первом шаге показан императивный стиль, а на втором — функциональный.

Сравнение императивного и функционального стилей в Rust
Шаг 1/2
 1#[derive(Debug)]
 2struct Sensor {
 3    id: String,
 4    temperature: f32,
 5}
 6
 7fn main() {
 8    let sensors = vec![
 9        "sensor1 22.5",
10        "sensor2 35.7",
11        "sensor3 29.8",
12        "sensor4 41.3",
13        "sensor5 30.0"
14    ];
15
16    let critical_threshold = 30.0;
17    let mut critical_sensors = vec![];
18
19    // Проходимся циклом по датчикам
20    for s in sensors {
21        let mut s = s.split(' ');
22        let id = s.next();
23        let temperature = s.next();
24
25        if id.is_some() && temperature.is_some() {
26            let id = id.unwrap().to_owned();
27            let temperature = temperature.unwrap().parse::<f32>();
28
29            if temperature.is_ok() {
30                let temperature = temperature.unwrap();
31                if temperature > critical_threshold {
32                    critical_sensors.push(Sensor { id, temperature });
33                }
34            }
35        }
36    }
37
38    for s in critical_sensors {
39        println!("{:?}", s);
40    }
41}

Императивный стиль

  • Мы вручную разбираем строки, выполняем проверки и наполняем изменяемый вектор critical_sensors.
  • Код требует явного управления состоянием и частых вызовов unwrap().

Сравнение подходов:
#

  1. Императивный стиль:

    • Особенности: большое количество строчек кода, ручной перебор элементов в цикле for, ручная проверка Option и Result.
    • Минусы: код перегружен деталями управления памятью и состоянием, менее читаем, требует аллокации временных коллекций.
  2. Функциональный стиль:

    • Особенности: использование цепочки адаптеров итераторов (map, flatten, filter).
    • Плюсы: высокая читаемость, отсутствие необходимости изменять состояние вручную, лаконичность.

Iterator::collect
#

Плюс использования Iterator::collect что нам не нужно аллоцировать лишнюю коллекцию и добавлять туда элементы в цикле. Метод collect позволяет взять итератор и конвертировать его в коллекцию для удобной дальнейшей работы или вывода.

Еще один небольшой пример:

    let input = vec![
        "Hello".to_string(),
        "World!".to_string(),
        "From".to_string(),
        "E@Gle".to_string(),
        "Blog".to_string(),
    ];
    
    let output: Vec<String> = input.iter().cloned().collect();
    println!("{:?}", output);

Бонус
#

Предлагаю пройтись по методам которые могут вызвать вопросы, а ведь они часто встречаются в исходниках программ написанных на rust

is_some
#

Метод is_some вызывается для типа Option и возвращает true, если Option является Some, и false, если None.

to_owned
#

Метод to_owned берет копию объекта и передаёт владение вызывающей стороне или как написано в доке преобразует заимствованные данные в собственные. Обычно он используется для создания String из &str.

unwrap
#

Метод unwrap извлекает значение из Option, если оно является Some, иначе выбрасывает панику, если Option является None.

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

flatten
#

Метод flatten преобразует Option<Option> в Option, удаляя один уровень вложенности. Его также можно использовать с итераторами для удаления значений None.

ok
#

Метод ok преобразует Result в Option. Если результат Ok, то возвращается Some, в противном случае возвращается None.

parse::<F>()
#

Механизм parse::() в Rust используется для преобразования фрагмента строки (&str) в число. Этот метод реализован в &str и может парсить строковый слайс в различные типы, не только f32. Ниже пример показывающий как можно преобразовать &str в u32

let four: u32 = "4".parse().unwrap();

assert_eq!(4, four);

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

Надеюсь, что заметка поможет еще лучше познакомиться с языком Rust и увидеть, что это современный гибкий язык не заточенный на какой-то один стиль написания кода.

Rust собирает в себе лучшее из мира программирования.

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

Хочу напомнить: не обязательно скролить блог чтобы читать статьи можно также нажать “пробел”.
Пишите код, практикуйтесь, делитесь ссылкой на блог и удачи! 👾

История обновлений

  • Добавил подробное сравнение производительности и рекомендации по использованию метода collect.
  • Обновил примеры кода под стандарты новой редакции Rust.
Статья прочитана
Пожалуйста, оцените насколько статья была вам полезна и понятна
Цикл статей
О Rust - Эта статья часть цикла.
Статей прочитано 0/36
0%
📚 Введение и дополнительные материалы
🟢 Начальный уровень (Rust-basic)
Не прочитана
🔵 Средний уровень (Rust-middle)
21 Императивный и функциональный Rust: работа с методом collect (текущая)
Не прочитана

Связанные статьи