[{"content":"","date":"25 сентября 2025","externalUrl":null,"permalink":"/tags/android/","section":"Tags","summary":"","title":"Android","type":"tags"},{"content":" Введение # С выходом Android 15 Google изменил фундаментальный принцип управления памятью: вместо фиксированных 4 KB страниц операционная система теперь поддерживает 16 KB memory pages.\nЭтот переход улучшает производительность и экономию энергии, но в то же время требует пересборки приложений, особенно тех, которые используют нативный код (NDK, C/C++, Flutter engine или плагины).\nС 1 ноября 2025 года Google Play Console будет отклонять приложения без поддержки 16 KB страниц. Если хотите сразу перейти к описанию как обновить flutter проект - переходите к части: Flutter-часть: что нужно сделать Flutter-разработчикам\n1. Ключевые термины: MMU, Memory Page, Page Fault, Page Table, TLB # Постараюсь привести объяснение терминов которые встретятся далее в статьею Чтобы легче было запоминать - приведу аналогию с городской библиотекой. Если вам не интересно читать описание терминов - можете переходить к следующей части: Почему Google вводит 16 KB memory pages.\nMMU (Memory Management Unit) # Аппаратный блок процессора, который сопоставляет виртуальные адреса с физической памятью и управляет правами доступа.\n📖 Аналогия: библиотекарь, который по запросу читателя ищет книгу в каталоге и определяет её местонахождение на полке.\nСтраница памяти (Memory Page) # Минимальная единица управления памятью.\nДо Android 15 стандарт → 4 KB. Теперь возможны → 16 KB. 📖 Аналогия: единообразие размеров книг библиотеке - одинаковый размер для упрощённого хранения книг.\nPage Fault # Ошибка доступа к странице, когда её ещё нет в памяти. Ядро решает: подгрузить, выделить новую или запретить доступ.\n📖 Аналогия: читатель просит книгу, библиотекарь идёт в архив потому, что на полке её пока нет.\nТаблица страниц (Page Table) # Каталог соответствий виртуальных и физических страниц.\n📖 Аналогия: карточный каталог библиотеки, где указано расположение каждой книги.\nTLB (Translation Lookaside Buffer) # Быстрый кэш в процессоре для преобразований адресов.\n📖 Аналогия: закладки для популярных книг - библиотекарь сразу знает, где они, без поиска в каталоге.\n2. Почему Google вводит 16 KB memory pages # История # Исторически Android поддерживал только 4 KB страницы. Это было оптимально для устройств с небольшим объёмом RAM.\nНо современные устройства имеют десятки гигабайт памяти и многоядерные процессоры. В этих условиях миллионы маленьких страниц стали создавать избыточные накладные расходы:\nбольше записей в таблицах страниц; больше промахов в TLB; больше обходов таблиц страниц. Что изменилось в Android 15 # Android 15 впервые сделал платформу страница-независимой и добавил поддержку 16 KB страниц.\nЗачем это было нужно:\nменьше записей в таблицах страниц; меньше TLB-промахов; меньше обходов таблиц; лучше масштабируемость под устройства с большим объёмом RAM. Пример: чтобы отобразить 64 MB памяти\nпри 4 KB страницах → 16 384 записи при 16 KB страницах → всего 4 096 записей. Результаты тестов от Google # Запуск приложений быстрее на 3,16% в среднем (до 30% для отдельных).\nЭнергопотребление при запуске ниже на 4,56%.\nКамера стартует быстрее: +4,48% (hot start) и +6,60% (cold start).\nЗагрузка системы быстрее на 8% (~950 мс).\nКакие минусы # Внутренняя фрагментация: небольшой объект (например, 5 KB) занимает целую страницу 16 KB.\nНо таблицы страниц уменьшаются в 4 раза, что компенсирует перерасход памяти.\nПодробнее от Google → Google: Support 16 KB page sizes 3. Flutter-часть: что нужно сделать Flutter-разработчикам # ❓ Почему это важно для разработки под Flutter # Flutter-приложения почти всегда используют нативный код:\nсам Flutter engine написан на C/C++; плагины (ads, analytics, camera, ML и др.) содержат .so библиотеки; собственный нативный код через FFI или C/C++ тоже требует пересборки. Даже если у вас проект написан на чистом Dart, устаревший плагин может вызвать падение на устройствах с Android 15 (16 KB).\nПоэтому вероятность несовместимости у Flutter-приложений очень высокая.\n🛠 Как исправить проблему совместимости во Flutter # 1. Обновите инструменты # Flutter SDK → последняя стабильная (3.x) NDK → r28+ Gradle → 8.14.3+ AGP → 8.6+ (лучше 8.9.1) Target SDK → 36 flutter upgrade flutter pub upgrade 2. Обновите зависимости # Проверьте все плагины в pubspec.yaml. Обновите плагины с нативными .so (firebase, camera, ads, analytics, ML). 3. Пересоберите проект # Обновлённые Flutter SDK, AGP и NDK автоматически пересоберут нативные библиотеки с 16 KB alignment.\n4. Тестирование # Эмулятор Android 15 (Device Manager → Kernel page size = 16 KB).\nФизическое устройство (Pixel) → включить “Use 16 KB memory pages” в Developer Options.\nПроверка:\nadb shell getconf PAGE_SIZE 16384 → устройство работает в 16 KB режиме.\n5. Проверьте AAB перед публикацией # unzip -q build/app/outputs/bundle/release/app-release.aab -d extracted_aab find extracted_aab/base/lib -name \u0026#34;*.so\u0026#34; -exec sh -c \u0026#39; for f; do if readelf -lW \u0026#34;$f\u0026#34; | grep -q \u0026#34;0x4000\u0026#34;; then echo \u0026#34;✅ $f 16KB совместимо\u0026#34; else echo \u0026#34;❌ $f только 4KB совместимо\u0026#34; fi done \u0026#39; sh {} + ✅ Хорошо: 0x4000 (16 KB aligned) ❌ Плохо: 0x1000 (4 KB aligned) → обновите зависимость и пересоберите. Ссылка на Github Gist с настройками для поддержки проекта Android 16K # https://gist.github.com/eaglebk/8868cc96cf11e4ce98921449ead78bfd\n✅ Чек-лист для Flutter-разработчиков # Flutter обновлён (последний stable release) NDK r28+ Gradle 8.14.3+ AGP 8.6+ Target SDK 36 Зависимости обновлены Протестировано на 16 KB эмуляторе/устройстве Проверено через скрипт с readelf AAB загружен в Play Console без предупреждений 4. Итоги # Android 15 меняет стандарт страниц памяти: с 4 KB → 16 KB. Это ускоряет запуск приложений, камеры и систему, снижает энергопотребление. Минус — фрагментация, но она компенсируется меньшими таблицами страниц. Flutter-разработчики обязаны обновить SDK, NDK, AGP и все плагины. Проверка AAB и тесты на 16 KB — ключ к успешной публикации в Play Console. FAQ # Что будет, если не обновить приложение под 16 KB страницы в Android 15?\nПриложение может упасть при запуске на новых устройствах, а с 1 ноября 2025 года Google Play Console отклонит публикацию. Коснутся ли изменения приложений без нативного кода?\nЕсли в проекте только Dart/Kotlin/Java и нет .so библиотек, приложение совместимо автоматически. Как проверить совместимость Flutter-приложения?\nСоздайте эмулятор Android 15 с Kernel page size = 16 KB. Если приложение запускается, значит совместимо. Что означает предупреждение Play Console “The App isn’t 16KB compatible”?\nЭто сигнал, что внутри AAB есть 64-битные .so библиотеки, собранные под 4 KB. Нужно пересобрать их с NDK r28+ и AGP 8.6+. ","date":"25 сентября 2025","externalUrl":null,"permalink":"/about-all/android-16k/","section":"Посты обо всем","summary":"","title":"Android 15 и 16 KB страница памяти: что изменится, как подготовить Android и Flutter-приложения","type":"about-all"},{"content":"","date":"25 сентября 2025","externalUrl":null,"permalink":"/tags/dev/","section":"Tags","summary":"","title":"Dev","type":"tags"},{"content":"","date":"25 сентября 2025","externalUrl":null,"permalink":"/tags/flutter/","section":"Tags","summary":"","title":"Flutter","type":"tags"},{"content":"","date":"25 сентября 2025","externalUrl":null,"permalink":"/tags/","section":"Tags","summary":"","title":"Tags","type":"tags"},{"content":"final sloganRu = \u0026#39;Завтра будет лучше!\u0026#39;; final sloganEn = \u0026#39;Tomorrow will be better!\u0026#39;; final sloganFr = \u0026#39;Demain sera meilleur!\u0026#39;; final sloganJp = \u0026#39;明日はもっと良くなる!\u0026#39;; final sloganZn = \u0026#39;明天会更好!\u0026#39;; ","date":"25 сентября 2025","externalUrl":null,"permalink":"/","section":"Welcome to my blog! 🎉","summary":"","title":"Welcome to my blog! 🎉","type":"page"},{"content":"","date":"18 сентября 2025","externalUrl":null,"permalink":"/series/about-rust/","section":"Series","summary":"","title":"About-Rust","type":"series"},{"content":"","date":"18 сентября 2025","externalUrl":null,"permalink":"/categories/","section":"Categories","summary":"","title":"Categories","type":"categories"},{"content":"","date":"18 сентября 2025","externalUrl":null,"permalink":"/tags/rust/","section":"Tags","summary":"","title":"Rust","type":"tags"},{"content":"","date":"18 сентября 2025","externalUrl":null,"permalink":"/categories/rust/","section":"Categories","summary":"","title":"Rust","type":"categories"},{"content":" Язык rust я изучаю как хобби. Примечание: Советы и мысли о расте - это просто мои эксперименты с интересным новым языком. Надеюсь, что в процессе ни один компилятор не пострадает\n👇🏻 Для вашего удобства чтобы не приходилось искать:\nСсылки на полезные материалы для изучения\n","date":"18 сентября 2025","externalUrl":null,"permalink":"/rust/","section":"Rust","summary":"","title":"Rust","type":"rust"},{"content":" Вступление # Иногда программа \u0026ldquo;замирает\u0026rdquo; в ожидании задач. Язык Rust, чтобы справиться с ними и избежать простоя, для нас приготовил три разные типа исполнения: синхронность, асинхронность и параллельность. Чтобы разобраться с ними, мы снова отправимся на борт «Vectoria», где находчивый капитан Нова и робот RUST-Y учатся работать с Tokio - рантаймом, который умеет переключать тысячи лёгких задач без простоя.\nВ конце вас ждёт мини-квиз по Tokio и async: 18 вопросов (разбитые на группы по 8 вопросов) - от основ до практики с кодом по темам: join!, join_all, spawn, JoinSet и другим которые вы встретите в этой статье. Поэтому совет - читайте внимательно, это пригодится при прохождении квиза. Готовы? Открываем новую главу.\nПролог. Врата # Корабль \u0026ldquo;Vectoria\u0026rdquo; медленно дрейфовал у древних космических врат. Белёсые дуги уходили в пустоту, будто застывшие волны. На мостике царила напряжённая тишина.\nRUST-Y, бортовой робот, переминался на месте: его сенсоры мигали тревожно.\nНаконец он собрался с духом и заговорил, голос дрогнул:\n— \u0026ldquo;Капитан\u0026rdquo;, разрешите обратиться и задать вопрос… Я не понимаю разницы между синхронным и асинхронным кодом. И ещё - все упоминают Tokio. Что это? И чем же асинхронность отличается от параллельности?\nЭкипаж оживился. Такие вопросы всегда означали, что сейчас будет короткое введени в новую тему - и обычно за этим следовало приключение.\nКапитан Нова активировал голографический проектор. В воздухе появились три сцены:\nСинхронный вызов: команда корабля отправляет запрос на сервер и застывает в ожидании ответа. Экипаж ждёт, пока сервер думает и ответит. Асинхронный вызов: запрос ушёл — но команда продолжает жить своей жизнью: прокладывает курс, регулирует питание, сканирует окрестности. Когда сервер ответит, задача \u0026ldquo;сообщит о готовности\u0026rdquo;. Параллельность: несколько членов экипажа работают одновременно - Зори у навигации, Хекс у связи, Арчи в лазарете. Это потоки ОС: тяжёлые, у каждого свой стек и ресурсы. Tokio — это диспетчер. Он управляет тысячами лёгких задач (Future) поверх ограниченного числа потоков ОС. Каждая задача знает, когда уступить выполнение (.await). Именно так программа остаётся всегда живой и готовой к выполнению работы. Часть 1. Когда корабль уснул # Вдруг неожиданно раздался синал тревоги и лампа вспыхнула на мостике: красный свет, гул сирен. Сенсоры дальнего сканирования перестали отвечать. Зори в отчаянии ударил ладонью по панели:\n— \u0026ldquo;Капитан! Навигация замерла. Даже связь упала!\u0026rdquo;\nRUST-Y в панике вывел код на экран:\nОшибка: thread::sleep Запуск ▶ 1// ?hidden:start 2fn query_sensor() -\u0026gt; u32 { 42 } 3fn process(d: u32) -\u0026gt; u32 { d } 4 5use std::{thread, time::Duration}; 6// ?hidden:end 7fn calibrate() -\u0026gt; u32 { 8 let d = query_sensor(); 9 thread::sleep(Duration::from_secs(5)); 10 process(d) 11} 12 13fn main() { 14 println!(\u0026#34;[МОСТИК] Запуск калибровки...\u0026#34;); 15 let data = calibrate(); 16 println!(\u0026#34;[МОСТИК] Калибровка завершена: {data}\u0026#34;); 17} 18 Проблема:\nthread::sleep блокирует ОС-поток целиком. Если так \u0026ldquo;усыпить\u0026rdquo; воркер рантайма, другие async-задачи не выполнятся. Результат выполнения: \u0026times; Нова нахмурился:\n— \u0026ldquo;Ты заставил весь корабль спать вместе с датчиком. thread::sleep блокирует поток. Это словно дежурный задремал на вахте. Пока он спит — системы корабля парализованы\u0026rdquo;.\nДоктор Арчи добавил:\n— \u0026ldquo;Это худшая сторона синхронного кода. В боевых условиях такие паузы смертельно опасны\u0026rdquo;.\nЧасть 2. Потоки и их цена # Инженер Спаркс пододвинулся к консоли:\n— \u0026ldquo;Предлагаю вынести тяжёлую работу в отдельный поток. Пусть остальное живёт!\u0026rdquo;\nНа голограмме вспыхнул новый фрагмент:\nРешение: spawn_blocking Запуск ▶ 1// ?hidden:start 2use std::time::Duration; 3use std::thread; 4use tokio::task; 5 6fn query_sensor() -\u0026gt; u32 { 42 } 7fn process(d: u32) -\u0026gt; u32 { d } 8 9fn calibrate_blocking() -\u0026gt; u32 { 10 let d = query_sensor(); 11 thread::sleep(Duration::from_secs(5)); // блокируем, но в отдельном потоке 12 process(d) 13} 14 15// ?hidden:end 16 17#[tokio::main] 18async fn main() { 19 println!(\u0026#34;[МОСТИК] Запуск калибровки (spawn_blocking)...\u0026#34;); 20 let data = task::spawn_blocking(|| calibrate_blocking()) 21 .await 22 .expect(\u0026#34;join error\u0026#34;); 23 println!(\u0026#34;[МОСТИК] Калибровка завершена: {data}\u0026#34;); 24} 25 Комментарий\nspawn_blocking уводит блокирующую работу в пул блокирующих потоков. Это спасает рантайм, но каждый поток — требует дополнительную память; используйте осторожно. Результат выполнения: \u0026times; Системы ожили, связь вернулась. Экипаж облегчённо выдохнул.\nНо капитан Нова покачал головой:\n— \u0026ldquo;Каждый поток операционной системы забирает мегабайты памяти. Это временное средство. Мы не можем плодить их тысячами\u0026rdquo;.\nЧасть 3. Искусство уступать # Через час Vectoria готовилась к гиперпрыжку. Сенсоры снова были перегружены.\nЗори докладывал:\n— \u0026ldquo;Если будем ждать по пять секунд синхронно, нас размажет о врата!\u0026rdquo;\nКапитан спокойно включил новую голограмму:\nАсинхронная калибровка Запуск ▶ 1use tokio::time::{sleep, Duration}; 2 3async fn query_sensor_async() -\u0026gt; u32 { 4 sleep(Duration::from_millis(50)).await; 5 return 42 6} 7 8async fn process_async(d: u32) -\u0026gt; u32 { d } 9 10async fn calibrate_async() -\u0026gt; u32 { 11 let d = query_sensor_async().await; 12 sleep(Duration::from_secs(1)).await; // уступаем планировщику 13 process_async(d).await 14} 15 16#[tokio::main] 17async fn main() { 18 println!(\u0026#34;[МОСТИК] Запуск калибровки (async)...\u0026#34;); 19 let data = calibrate_async().await; 20 println!(\u0026#34;[МОСТИК] Калибровка завершена: {data}\u0026#34;); 21} 22 Комментарий: Асинхронные точки ожидания .await — место где задача уступает поток выполнения рантайму (cooperative). Результат выполнения: \u0026times; — \u0026ldquo;Вот это правильный путь. Мы ждём, но не блокируем. Сенсор думает, а корабль продолжает подготовку. Это и есть сила асинхронности\u0026rdquo;.\nЧасть 4. Задачи и обещания # RUST-Y вновь задал вопрос:\n— \u0026ldquo;А если функция не async? Как вернуть результат?\u0026rdquo;\nДоктор Арчи поправил очки:\n— \u0026ldquo;Тогда мы возвращаем JoinHandle. Это обещание, что задача выполнится\u0026rdquo;.\nJoinHandle Запуск ▶ 1use tokio::task::{self, JoinHandle}; 2 3fn not_an_async_function() -\u0026gt; JoinHandle\u0026lt;()\u0026gt; { 4 task::spawn(async { 5 println!(\u0026#34;[ЭКИПАЖ] Второе сообщение из async-задачи\u0026#34;); 6 }) 7} 8 9#[tokio::main] 10async fn main() { 11 println!(\u0026#34;[МОСТИК] Первое сообщение\u0026#34;); 12 let _ = not_an_async_function().await; 13} 14 Комментарий: Возвращаем JoinHandle из sync-функции и ждём её выше (await). Результат выполнения: \u0026times; Нова добавил:\n— \u0026ldquo;Но помни: если не дождаться .await, результат уйдёт в пустоту\u0026rdquo;.\nЭкипаж понял: JoinHandle - это не просто объект, а инструмент управления задачей.\nЧасть 5. Мост между мирами # Корабль принял сигнал бедствия. Нужно было пропустить сигнал через модуль диагностики прямо из потока операционной системы.\nСпаркс нахмурился:\n— \u0026ldquo;Наш код асинхронный (async). Как его связать с обычным потоком?\u0026rdquo;\nКапитан показал:\nHandle — мост между потоками Запуск ▶ 1use tokio::runtime::Handle; 2 3fn not_an_async_function(handle: Handle) { 4 handle.block_on(async { 5 println!(\u0026#34;[ЭКИПАЖ] Второе сообщение через block_on\u0026#34;); 6 }) 7} 8 9#[tokio::main] 10async fn main() { 11 println!(\u0026#34;[МОСТИК] Первое сообщение\u0026#34;); 12 let handle = Handle::current(); 13 std::thread::spawn(move || { 14 not_an_async_function(handle); 15 }) 16 .join() 17 .unwrap(); 18} 19 Мост между потоком операционной системы и Tokio — Handle::block_on. Результат выполнения: \u0026times; — \u0026ldquo;Handle позволяет ОС-потоку вызвать асинхонную-задачу. Но помните: поток тяжёлый, используем только в исключительных случаях\u0026rdquo;.\nЧасть 6. Блокировка в сердце шторма # Начинался космический шторм, медлить больше было нельзя.\nВо время шторма у врат RUST-Y снова вставил thread::sleep в async-код.\nНавигация застыла, индикаторы мигнули красным.\n— \u0026ldquo;Опять корабль уснул!\u0026rdquo; — закричал Зори.\nНова строго посмотрел на код:\nОшибка: sleep внутри async ← Шаг 1/2 → Запуск ▶ 1use std::{thread, time::Duration}; 2 3async fn bad_sleep() { 4 // Плохо: блокирует воркер рантайма 5 thread::sleep(Duration::from_millis(200)); 6 println!(\u0026#34;Проснулся (но всё это время рантайм простаивал)\u0026#34;); 7} 8 9#[tokio::main] 10async fn main() { 11 let t1 = bad_sleep(); 12 let t2 = async { 13 for i in 0..5 { 14 println!(\u0026#34;Параллельная задача {i}\u0026#34;); 15 tokio::task::yield_now().await; 16 } 17 }; 18 tokio::join!(t1, t2); 19} 20 Итог: мало шансов увидеть чередование выполнения — поток занят thread::sleep. Результат выполнения: \u0026times; 1use tokio::time::{sleep, Duration}; 2 3async fn good_sleep() { 4 // Хорошо: уступаем планировщику 5 sleep(Duration::from_millis(200)).await; 6 println!(\u0026#34;Проснулся (планировщик выполнял другие задачи)\u0026#34;); 7} 8 9#[tokio::main] 10async fn main() { 11 let t1 = good_sleep(); 12 let t2 = async { 13 for i in 0..5 { 14 println!(\u0026#34;Параллельная задача {i}\u0026#34;); 15 tokio::task::yield_now().await; 16 } 17 }; 18 tokio::join!(t1, t2); 19} 20 В async-контексте только tokio::sleep. Всё остальное — блокировка для сотен задач. Результат выполнения: \u0026times; Он тут же внес исправление (шаг 2). 👆🏻\nСвет тревоги погас, корабль вернулся в строй.\nЧасть 7. Научиться делиться временем выполнения # Команда выполнила тренировочный пробный манёвр и навигация сожрала все ресурсы процессора. Связь и сенсоры замерли.\nНова объяснил:\nyield_now — уступаем место Запуск ▶ 1use tokio::task; 2 3async fn looper(name: \u0026amp;str) { 4 for i in 0..3 { 5 println!(\u0026#34;{name}-{i}\u0026#34;); 6 task::yield_now().await; 7 } 8} 9 10#[tokio::main] 11async fn main() { 12 tokio::join!(looper(\u0026#34;A\u0026#34;), looper(\u0026#34;B\u0026#34;)); 13} 14 Кооперативность: yield_now явно отдаёт квант выполнения другим задачам. Результат выполнения: \u0026times; — \u0026ldquo;В async Rust Tokio многозадачность кооперативная. Задачи должны уступать сами.\nБез .await или yield_now().await — планировщик бессилен\u0026rdquo;.\nЧасть 8. Экипаж действует как единое целое # Перед прыжком нужно было:\nпроверить реактор, перезапустить связь, откалибровать навигацию. Капитан показал решение:\nНесколько задач: join! Запуск ▶ 1use tokio::time::{sleep, Duration}; 2 3async fn job(id: u32) -\u0026gt; u32 { 4 sleep(Duration::from_millis(60 * id as u64)).await; 5 println!(\u0026#34;Задача {id} завершена\u0026#34;); 6 id * 10 7} 8 9#[tokio::main] 10async fn main() { 11 let (a, b) = tokio::join!(job(1), job(2)); 12 println!(\u0026#34;Результаты: {a}, {b}\u0026#34;); 13} 14 Для фиксированного набора задач используйте tokio::join! Результат выполнения: \u0026times; — \u0026ldquo;А если задач десятки, сотни?\u0026rdquo; — спросил Расти.\nКоллекция задач: join_all Запуск ▶ 1// ?hidden:start 2use tokio::time::{sleep, Duration}; 3use futures::future::join_all; 4// ?hidden:end 5async fn work(i: u32) -\u0026gt; u32 { 6 sleep(Duration::from_millis(50 * i as u64)).await; 7 i * 2 8} 9 10#[tokio::main] 11async fn main() { 12 let futs = vec![work(1), work(2), work(3)]; 13 let results = join_all(futs).await; 14 println!(\u0026#34;Результаты: {:?}\u0026#34;, results); // [2,4,6] 15} Используем join_all из пакета futures\nОн ждёт коллекцию Future и возвращает Vec результатов. Результат выполнения: \u0026times; tokio::join! — фиксированный набор задач → кортеж. futures::future::join_all — коллекция задач → Vec результатов. Часть 9. Ремонтные дроны и фоновая работа # — \u0026ldquo;Запустить ремонтных дронов!\u0026rdquo; — приказал капитан.\nRUST-Y замялся:\n— \u0026ldquo;Как запустить задачу в фоне?\u0026rdquo;\nНова вывел на экран код:\nХекс уточнил:\n— \u0026ldquo;А если дронов много?\u0026rdquo;\nНова вывел другой код на на экран:\nJoinSet: задачи по мере завершения Запуск ▶ 1use tokio::task::JoinSet; 2use tokio::time::{sleep, Duration}; 3 4async fn unit(i: u32) -\u0026gt; u32 { 5 sleep(Duration::from_millis(30 * i as u64)).await; 6 i * 100 7} 8 9#[tokio::main] 10async fn main() { 11 let mut set = JoinSet::new(); 12 for i in 1..=5 { 13 set.spawn(unit(i)); 14 } 15 16 while let Some(res) = set.join_next().await { 17 match res { 18 Ok(v) =\u0026gt; println!(\u0026#34;Готов блок: {v}\u0026#34;), 19 Err(e) =\u0026gt; eprintln!(\u0026#34;Ошибка задачи: {e}\u0026#34;), 20 } 21 } 22 println!(\u0026#34;JoinSet завершил все задачи\u0026#34;); 23} 24 JoinSet позволяет получать результаты по мере готовности (в любом порядке). Результат выполнения: \u0026times; Эпилог. Ожидающее будущее # Шторм утих. Сенсоры работали, все подсистемы были в порядке, дроны вернулись.\nRUST-Y стоял на мостике:\n— \u0026ldquo;Спасибо, капитан. Теперь я понял: ошибки — это ступени развития. Async и Future — это жизнь без блокировок\u0026rdquo;.\nНова улыбнулся:\n— \u0026ldquo;Ничего, мы понемногу учимся жить в ритме вселенной. А теперь готовы к новым испытаниям\u0026rdquo;.\nВрата активировались и Vectoria умчалась в даль скозь сияние портала.\nПриложение: шпаргалка капитана по ключевым приёмам # Не блокируйте: thread::sleep → заменяйте на tokio::time::sleep. Тяжёлое CPU/блокирующее I/O: оборачивайте в tokio::task::spawn_blocking. Старт из sync-кода: используйте tokio::runtime::Handle::block_on. Параллельное ожидание: tokio::join! для фиксированных задач; futures::join_all для коллекций. Фоновая работа: tokio::spawn (требует 'static или move). Пачки задач: tokio::task::JoinSet + join_next().await. Кооперативность: .await; при долгих циклах — yield_now().await. Финальный квиз 🚀 # 🛰️\n","date":"18 сентября 2025","externalUrl":null,"permalink":"/rust/future-async/","section":"Rust","summary":"","title":"Rust async и Tokio: В ожидании будущего","type":"rust"},{"content":"","date":"18 сентября 2025","externalUrl":null,"permalink":"/series/","section":"Series","summary":"","title":"Series","type":"series"},{"content":" Вступление # Иногда нам нужен список который не зажат в рамки фиксированного размера. В Rust для этого имеется Vec\u0026lt;T\u0026gt; динамический контейнер, который растёт и сжимается по мере необходимости.\nЧтобы сделать знакомство с ним более запоминающимся, мы отправимся в путешествие с увлекательным сюжетом.\nГлавное действующее лицо - капитан космического корабля «Vectoria». Его задача - подготовить корабль к миссии: собрать экипаж, загрузить оборудование, очистить и обслужить системы и наконец отправиться в полёт.\nВместе с капитаном будет робот-помощник RUST-Y или просто Расти. Он умеет писать программы на Rust, но часто совершает типичные ошибки новичка. Капитану придется направлять его и показывать правильные решения.\nА ещё в конце статьи вас ждёт испытание: мини-квиз, который покажет насколько хорошо вы справились с миссией и разобрались с Vec. Готовы? Тогда начнём с самого простого шага: списка экипажа.\nЧасть 1. Знакомство с Vec — первые шаги # Вас вызывают в штаб. Перед стартом новой космической миссии вам поручают задание - предоставить список экипажа. Пока известно только четыре члена команды, ещё двое прибудут позже.\nВы даете задание Расти — составь список экипажа. Ваша задача - научить его вести списки правильно, ведь от этого зависит порядок на борту и успех всей миссии.\nПопытка RUST-Y — ошибка новичка ← Шаг 1/2 → Запуск ▶ 1fn main() { 2 // Робот записывает имена по отдельности 3 let crew1 = \u0026#34;Капитан Нова\u0026#34;; 4 let crew2 = \u0026#34;Инженер Спаркс\u0026#34;; 5 let crew3 = \u0026#34;Доктор Люмен\u0026#34;; 6 let crew4 = \u0026#34;Биолог Рост\u0026#34;; 7 8 // Он решает: \u0026#34;создам массив\u0026#34; 9 let crew = [crew1, crew2, crew3, crew4]; 10 11 // Но сразу лезет к пятому элементу... 12 let crew5 = crew[4]; 13 14 println!(\u0026#34;Пятый член экипажа: {crew5}\u0026#34;); 15} Результат: программа упадёт с паникой.\nПочему? Потому что массив в Rust имеет фиксированный размер — здесь ровно 4 элемента, а доступ к индексу [4] недопустим. 🔹 Паника — это механизм аварийного завершения, когда нарушено условие безопасности (например, выход за границы памяти). Результат выполнения: \u0026times; 1fn main() { 2 // Список экипажа в гибкой коллекции 3 let crew = vec![\u0026#34;Капитан Нова\u0026#34;, \u0026#34;Инженер Спаркс\u0026#34;, \u0026#34;Доктор Люмен\u0026#34;, \u0026#34;Биолог Рост\u0026#34;]; 4 5 println!(\u0026#34;Экипаж: {:?}\u0026#34;, crew); 6 println!(\u0026#34;Длина: {}\u0026#34;, crew.len()); 7 println!(\u0026#34;Вместимость: {}\u0026#34;, crew.capacity()); 8} Исправление капитана — Vec\nКапитан объясняет: Расти, массив — это как тетрадь на 4 страницы. Если в экипаже будет больше людей, страниц уже не хватит. Нам нужен гибкий список, который умеет расти. В Rust для этого есть Vec. Под капотом он хранит три числа: указатель (ptr) на блок памяти, длину (len) — сколько элементов реально в списке, вместимость (capacity) — сколько ещё можно добавить без расширения памяти. Результат выполнения: \u0026times; Запустите код в шаге 2 и вы увидите примерно такой вывод:\nЭкипаж: [\u0026#34;Капитан Нова\u0026#34;, \u0026#34;Инженер Спаркс\u0026#34;, \u0026#34;Доктор Люмен\u0026#34;, \u0026#34;Биолог Рост\u0026#34;] Длина: 4 Вместимость: 4 Представьте, что у вас есть грузовой отсек корабля.\nВ нём 4 контейнера — это len. Отсек рассчитан максимум на 4 контейнера — это capacity. Если вы добавите ещё один контейнер, корабль \u0026ldquo;расширит отсек\u0026rdquo; (реаллокация): выделит новый, более просторный отсек, перенесёт туда все контейнеры и продолжит работу. Попробую показать это в коде: Запуск ▶ 1fn main() { 2 let mut crew = Vec::new(); 3 4 for i in 1..=5 { 5 crew.push(format!(\u0026#34;Член экипажа #{i}\u0026#34;)); 6 println!( 7 \u0026#34;Добавлен {i}-й: len={}, capacity={}\u0026#34;, 8 crew.len(), 9 crew.capacity() 10 ); 11 } 12} Запустите код и вы увидите, что capacity растёт скачками (примерно в 2 раза).\nЭто нужно, чтобы push в среднем был очень быстрым (O(1)). Результат выполнения: \u0026times; Описание методов и конструкций # push Добавляет элемент в конец вектора. Если вектора не хватает (len == capacity), он автоматически расширяет память.\nlen Возвращает текущее количество элементов в Vec.\ncapacity Показывает, сколько элементов можно ещё добавить без перераспределения памяти.\n1..=5 Это диапазон (range). В Rust ..= означает \u0026ldquo;включительно\u0026rdquo;, то есть от 1 до 5 включительно. Если бы мы написали 1..5, то получили бы числа 1, 2, 3, 4.\nНесколько способов создать Vec # В Rust есть несколько способов создать динамический вектор.\n1. Пустой вектор # Когда мы пока не знаем, какие элементы будут:\nlet mut crew: Vec\u0026lt;String\u0026gt; = Vec::new(); Здесь мы явно указали тип (Vec\u0026lt;String\u0026gt;). Без него компилятор не поймёт, что именно мы хотим хранить.\n2. Макрос vec![] # Самый удобный способ, если сразу известны элементы:\nlet crew = vec![\u0026#34;Алиса\u0026#34;, \u0026#34;Борис\u0026#34;, \u0026#34;Саяна\u0026#34;, \u0026#34;Дмитрий\u0026#34;]; 3. С фиксированным числом одинаковых значений # Макрос vec! можно использовать и так:\nlet crew = vec![\u0026#34;Неизвестный\u0026#34;; 3]; Получим вектор [\u0026ldquo;Неизвестный\u0026rdquo;, \u0026ldquo;Неизвестный\u0026rdquo;, \u0026ldquo;Неизвестный\u0026rdquo;].\n4. Через итераторы и коллекции # Например, можно создать последовательность чисел и собрать её в вектор:\nlet numbers: Vec\u0026lt;i32\u0026gt; = (1..=5).collect(); println!(\u0026#34;{:?}\u0026#34;, numbers); // [1, 2, 3, 4, 5] 5. С заранее известной ёмкостью # Если мы знаем, что скоро придёт много элементов, выгодно сразу зарезервировать память:\nlet mut crew: Vec\u0026lt;String\u0026gt; = Vec::with_capacity(10); Это создаст пустой вектор, у которого len = 0, но capacity = 10.\nНа практике чаще всего используют vec![], а остальные способы нужны для оптимизаций или специфических случаев. Часть 2. Проверка экипажа перед тренировкой (паника и безопасный доступ) # Перед первой тренировкой капитан просит RUST-Y вызвать члена экипажа по индексу из списка. Но если робот ошибётся с номером, программа завершится аварийно. Это важный момент: такие сбои недопустимы.\nПопытка RUST-Y - ошибка новичка ← Шаг 1/2 → Запуск ▶ 1fn main() { 2 let crew = vec![ 3 \u0026#34;Капитан Нова\u0026#34;, 4 \u0026#34;Инженер Спаркс\u0026#34;, 5 \u0026#34;Доктор Люмен\u0026#34;, 6 \u0026#34;Биолог Рост\u0026#34;, 7 \u0026#34;Пилот Вега\u0026#34;, 8 \u0026#34;Связист Хекс\u0026#34;, 9 ]; 10 11 // Попытка вызвать участника с индексом 99 12 let who = crew[99]; // ❌ паника 13 println!(\u0026#34;На тренировку выходит: {}\u0026#34;, who); 14} Результат: программа упадёт с паникой.\nЧто произошло? В Rust доступ по индексу (crew[99]) проверяется на корректность. Если индекс вне диапазона, программа завершается паникой.. В отличие от C/C++, где можно случайно прочитать \u0026ldquo;левые байты\u0026rdquo; или сломать память Rust останавливает программу и не даёт ей работать некорректно. Результат выполнения: \u0026times; 1fn main() { 2 let crew = vec![ 3 \u0026#34;Капитан Нова\u0026#34;, 4 \u0026#34;Инженер Спаркс\u0026#34;, 5 \u0026#34;Доктор Люмен\u0026#34;, 6 \u0026#34;Биолог Рост\u0026#34;, 7 \u0026#34;Пилот Вега\u0026#34;, 8 \u0026#34;Связист Хекс\u0026#34;, 9 ]; 10 11 match crew.get(99) { 12 Some(name) =\u0026gt; println!(\u0026#34;На тренировку выходит: {}\u0026#34;, name), 13 None =\u0026gt; println!(\u0026#34;Ошибка: участника с таким номером не существует.\u0026#34;), 14 } 15} Исправление капитана — безопасный доступ\nРазбор: [] возвращает ссылку \u0026amp;T, но вызывает панику, если индекс неверный. .get(index) возвращает Option\u0026lt;\u0026amp;T\u0026gt;: Some(\u0026amp;T) — элемент найден, None — индекс за пределами. Результат выполнения: \u0026times; Паника в Rust — это защита и повод проверить ваш код.\nЕсли индекс может быть неверным, используйте .get().\nНемного про Option # В статье про функциональный Rust мы уже встречались с Option как с типом, который позволяет явно сообщить: \u0026ldquo;значение может быть, а может не быть\u0026rdquo; такой аналог null-safety из других языков.\nВ примере выше Option используется так:\nSome(value) — элемент в векторе есть. None — элемента нет (например, индекс за пределами). Это хорошее решение языка: вместо \u0026ldquo;магических значений\u0026rdquo; или \u0026ldquo;падений программы\u0026rdquo; мы получаем явную модель отсутствия данных.\nЧасть 3. Личные коды экипажа (итерация по вектору и изменение элементов) # Перед полетом необходимо пройти инструктаж и капитан требует, чтобы каждый член экипажа получил личный код доступа в формате: R-XXX Имя\nНапример:\nR-001 Капитан Нова R-002 Инженер Спаркс Робот RUST-Y пробует сделать это с помощью цикла и сразу натыкается на ошибку компилятора.\nПопытка RUST-Y ← Шаг 1/2 → Запуск ▶ 1fn main() { 2 let mut names = vec![ 3 String::from(\u0026#34;Капитан Нова\u0026#34;), 4 String::from(\u0026#34;Инженер Спаркс\u0026#34;), 5 String::from(\u0026#34;Доктор Люмен\u0026#34;), 6 String::from(\u0026#34;Биолог Рост\u0026#34;), 7 String::from(\u0026#34;Пилот Вега\u0026#34;), 8 String::from(\u0026#34;Связист Хекс\u0026#34;), 9 ]; 10 11 // ❌ Ошибочный подход: iter() даёт неизменяемые ссылки (\u0026amp;String) 12 for (i, name) in names.iter().enumerate() { 13 *name = format!(\u0026#34;R-{:03} {}\u0026#34;, i + 1, name); 14 println!(\u0026#34;Подготовка кода для: {name} (#{})\u0026#34;, i + 1); 15 } 16} 17 Почему не компилируется\nnames.iter() возвращает неизменяемые ссылки \u0026amp;String. Изменять значения по таким ссылкам запрещено. Чтобы изменять элементы вектора, нужны изменяемые ссылки \u0026amp;mut String. Результат выполнения: \u0026times; 1// ?hidden:start 2fn main() { 3 4 let mut names = vec![ 5 String::from(\u0026#34;Капитан Нова\u0026#34;), 6 String::from(\u0026#34;Инженер Спаркс\u0026#34;), 7 String::from(\u0026#34;Доктор Люмен\u0026#34;), 8 String::from(\u0026#34;Биолог Рост\u0026#34;), 9 String::from(\u0026#34;Пилот Вега\u0026#34;), 10 String::from(\u0026#34;Связист Хекс\u0026#34;), 11 ]; 12// ?hidden:end 13 14 for (i, name) in names.iter_mut().enumerate() { 15 *name = format!(\u0026#34;R-{:03} {}\u0026#34;, i + 1, *name); 16 } 17 18 println!(\u0026#34;Коды присвоены:\\n{:#?}\u0026#34;, names); 19} Исправление капитана — iter_mut()\nКапитан объясняет: Теперь тип name стал \u0026amp;mut String и значение по ссылке можно изменять Результат выполнения: \u0026times; Разбор новых понятий # iter() vs iter_mut() # iter(\u0026amp;self) -\u0026gt; Iter\u0026lt;'_, T\u0026gt; — даёт \u0026amp;T (чтение). iter_mut(\u0026amp;mut self) -\u0026gt; IterMut\u0026lt;'_, T\u0026gt; — даёт \u0026amp;mut T (изменение). ⚠️ Пока выполняется iter_mut(), нельзя иметь другие заимствования — это правило единственной изменяемой ссылки в Rust.\nenumerate() # Оборачивает итератор и добавляет индекс. Индексы начинаются с 0, поэтому для \u0026ldquo;человекопонятной нумерации\u0026rdquo; используем i + 1. Изменение элемента через *name # В цикле name имеет тип \u0026amp;mut String. Чтобы присвоить новое значение, используем разыменование *name = format!(\u0026#34;R-{:03} {}\u0026#34;, i + 1, *name); format! # Макрос для сборки строк по шаблону. Возвращает новый String. Экономия на ручных конкатенациях (суммирования строк). Часть 4. Массовая загрузка корабля и проверка приборов # Подготовка к миссии выходит на новый уровень.\nКорабль Vectoria должен принять на борт сотни контейнеров и провести проверку бортовых приборов.\nРобот Расти пытается записать данные как есть, но работа получается \u0026ldquo;рваной\u0026rdquo; — список растёт с перебоями и задержками.\nКапитан объясняет, как избежать лишних перераспределений памяти при массовой загрузке.\nПопытка RUST-Y — загрузка ← Шаг 1/2 → Запуск ▶ 1use std::time::Instant; 2 3fn main() { 4 // Симулируем поток данных от датчиков 5 let incoming: Vec\u0026lt;String\u0026gt; = (1..=20_000) 6 .map(|i| format!(\u0026#34;Прибор #{i:05}\u0026#34;)) 7 .collect(); 8 9 let start = Instant::now(); 10 let mut manifest: Vec\u0026lt;String\u0026gt; = Vec::new(); 11 12 let mut last_cap = manifest.capacity(); 13 let mut reallocs = 0; 14 15 for item in incoming { 16 manifest.push(item); // добавляем по одному 17 if manifest.capacity() != last_cap { 18 reallocs += 1; 19 last_cap = manifest.capacity(); 20 } 21 } 22 23 println!( 24 \u0026#34;Загрузка завершена: len={}, capacity={}, reallocs={}, elapsed={:?}\u0026#34;, 25 manifest.len(), 26 manifest.capacity(), 27 reallocs, 28 start.elapsed() 29 ); 30} Что происходит:\nVec начинает с минимальной ёмкости. При каждом переполнении выделяется новый блок памяти → копирование старых данных. На больших объёмах видно множество реаллокаций. Результат выполнения: \u0026times; 1// ?hidden:start 2use std::time::Instant; 3 4fn main() { 5 let incoming: Vec\u0026lt;String\u0026gt; = (1..=20_000) 6 .map(|i| format!(\u0026#34;Прибор #{i:05}\u0026#34;)) 7 .collect(); 8 let mut reallocs = 0; 9 let start = Instant::now(); 10 11// ?hidden:end 12 13 // Предвыделяем память под все элементы заранее 14 let mut manifest: Vec\u0026lt;String\u0026gt; = Vec::with_capacity(incoming.len()); 15 16 // Массовая вставка 17 manifest.extend(incoming); 18 reallocs += 1; 19 20 println!( 21 \u0026#34;Загрузка завершена: len={}, capacity={}, reallocs={}, elapsed={:?}\u0026#34;, 22 manifest.len(), 23 manifest.capacity(), 24 reallocs, 25 start.elapsed() 26 ); 27} Исправление капитана — предвыделение памяти)\nКапитан объясняет, что теперь: нет скачков ёмкости, данные загружаются быстрее, память выделяется ровно один раз. Результат выполнения: \u0026times; Разбор кода для 2 шага # push добавляет по одному элементу. extend добавляет коллекцию или итератор целиком. append(\u0026amp;mut other) переносит данные из другого вектора без копирования (после этого other опустошается). with_capacity(n) сразу резервирует место под n элементов. reserve(n) / reserve_exact(n) — динамическое выделение при работе порциями. Аналогия с роботом и складом\nПредставьте складской отсек:\nRUST-Y тащит ящики один за одним, каждый раз переставляя стены отсека когда не хватает объема помещения (реаллокации). Капитан заранее строит отсек нужного размера — и загрузка идёт без перебоев. Часть 5. Буфер телеметрии — только последние данные # Корабль Vectoria готовится к полёту. Приборы начинают выдавать поток телеметрии: десятки и сотни значений в секунду.\nХранить всё подряд — невозможно: память ограничена.\nЗадача: держать в буфере только последние N записей, а старые сбрасывать.\nРасти пишет свой вариант и, как обычно, попадает в ловушку новичка.\nПопытка RUST-Y — неограниченный рост ← Шаг 1/4 → Запуск ▶ 1fn main() { 2 let mut telemetry: Vec\u0026lt;i32\u0026gt; = Vec::new(); 3 4 // Симулируем поток данных 5 for i in 0..200 { 6 telemetry.push(i); 7 } 8 9 println!(\u0026#34;Собрано {} значений\u0026#34;, telemetry.len()); 10} 11 Проблема:\nбуфер растёт бесконечно, через некоторое время память будет исчерпана. Результат выполнения: \u0026times; 1fn main() { 2 let mut telemetry: Vec\u0026lt;i32\u0026gt; = Vec::new(); 3 let max_len = 100; 4 5 for i in 0..200 { 6 telemetry.push(i); 7 8 // если размер превысил лимит, убираем старые записи 9 if telemetry.len() \u0026gt; max_len { 10 telemetry.remove(0); // ❌ неэффективно, сдвигает элементы 11 } 12 } 13 14 println!(\u0026#34;Буфер телеметрии: {:?}\u0026#34;, telemetry); 15} Исправление капитана — ограниченный буфер\nРаботает, но remove(0) сдвигает все элементы - на больших объёмах это дорого. Результат выполнения: \u0026times; 1fn main() { 2 let mut telemetry: Vec\u0026lt;i32\u0026gt; = Vec::new(); 3 let max_len = 100; 4 5 for i in 0..200 { 6 telemetry.push(i); 7 8 if telemetry.len() \u0026gt; max_len { 9 let overflow = telemetry.len() - max_len; 10 telemetry.drain(0..overflow); 11 } 12 } 13 14 println!(\u0026#34;Буфер телеметрии: {:?}\u0026#34;, telemetry); 15} 16 Ещё лучше — drain\ndrain(0..overflow) удаляет сразу несколько элементов с начала. В буфере всегда остаются последние max_len записей. Результат выполнения: \u0026times; 1// ?hidden:start 2fn main() { 3 let mut telemetry: Vec\u0026lt;i32\u0026gt; = Vec::new(); 4 let max_len = 100; 5 6 for i in 0..200 { 7 telemetry.push(i); 8 } 9// ?hidden:end 10 11 let last100: Vec\u0026lt;i32\u0026gt; = telemetry.iter().rev().take(100).cloned().collect(); 12 13 14 println!(\u0026#34;Буфер телеметрии: {:?}\u0026#34;, last100); 15} Альтернативное решение — срез последних элементов\nИногда проще просто брать «хвост» вектора: Результат выполнения: \u0026times; Часть 6. Итоги и старт миссии 🚀 # Итак все системы корабля Vectoria проверены.\nЭкипаж собран и получил личные коды. Приборы загружены и протестированы. Буфер телеметрии настроен чтобы хранить только актуальные данные. Робот RUST-Y научился многому: он уже не делает типичных ошибок новичка и готов сопровождать миссию в полёте.\nКапитан даёт сигнал: \u0026ldquo;К старту готовы!\u0026rdquo;\nПроверьте себя: пройдите финальный квиз ниже.\nВремя прохождения ограничено, но дано с запасом. Вопросы каждый раз перемешиваются и вы можете проходить квиз несколько раз.\nСпасибо, что прочитали статью про векторы в Rust до конца, надеюсь что смог объяснить как их правильно готовить! :D\n🛰️\n","date":"1 сентября 2025","externalUrl":null,"permalink":"/rust/rust-vec/","section":"Rust","summary":"","title":"Как работает Vec в Rust: устройство, методы, оптимизация","type":"rust"},{"content":"В этой статье разберём зачем нужен BufReader, как он работает и какие есть способы читать данные из файлов. Мы сравним разные подходы и посмотрим сколько они занимают времени.\n🎙️ К статье прилагается подкаст-версия - лёгкий и образный рассказ с аналогиями. Включайте, если хотите услышать объяснение на слух. Подкаст Rust в деталях. Выпуск 1 Трейты и чтение данных # В Rust трейты описывают общее поведение. Если тип реализует трейт - он \u0026ldquo;умеет\u0026rdquo; делать определённые действия.\nНапример, трейт Read говорит: \u0026ldquo;этот объект можно читать\u0026rdquo;. Под него попадают файлы (File), сетевые соединения (TcpStream), стандартный ввод (stdin).\nГлавный метод:\nfn read(\u0026amp;mut self, buf: \u0026amp;mut [u8]) -\u0026gt; Result\u0026lt;usize\u0026gt;; Он записывает доступные байты в буфер и возвращает число реально прочитанных. Чтобы загрузить все данные - .read() вызывают многократно.\nСистемные вызовы и их цена # Когда мы читаем файл или сокет через Read, каждый вызов .read() почти всегда приводит к системному вызову read(2) (на Linux/Unix) или его аналогу в других ОС.\n⚙️ Разберём по шагам что же происходит когда в коде вызвается этот метод:\nПрограмма вызывает метод .read(). Процессор переключается из пользовательского режима (user mode) в режим ядра (kernel mode). ОС выполняет реальную операцию чтения: обращается к диску, сети или памяти. Управление возвращается в пользовательское приложение. Каждый такой переход - это сотни инструкций процессора. Поэтому чтение по 1 байту с диска будет катастрофически медленным.\nBufReader # Чтобы уменьшить число системных вызовов существует BufReader.\nОн один раз считывает крупный блок (по умолчанию 8 КБ) во внутренний буфер, а дальше выдаёт данные уже из памяти. Так вместо тысячи вызовов вы получаете десятки.\nBufReader: чтение всего файла ← Шаг 1/2 → Запуск ▶ 1// ?hidden:start 2use std::fs::{File, OpenOptions}; 3use std::io::{self, BufReader, Read, Write}; 4use std::path::Path; 5 6fn ensure_file() -\u0026gt; io::Result\u0026lt;()\u0026gt; { 7 if !Path::new(\u0026#34;hello.txt\u0026#34;).exists() { 8 let mut f = OpenOptions::new().create(true).write(true).open(\u0026#34;hello.txt\u0026#34;)?; 9 writeln!(f, \u0026#34;Знакомство с BufReader!\u0026#34;)?; 10 } 11 Ok(()) 12} 13// ?hidden:end 14fn main() -\u0026gt; io::Result\u0026lt;()\u0026gt; { 15 // Проверяем, что файл существует (создаём при необходимости) 16 ensure_file()?; 17 18 // Открываем файл 19 let file = File::open(\u0026#34;hello.txt\u0026#34;)?; 20 21 // Оборачиваем его в BufReader 22 let mut reader = BufReader::new(file); 23 24 // Читаем содержимое в строку 25 let mut contents = String::new(); 26 reader.read_to_string(\u0026amp;mut contents)?; 27 28 println!(\u0026#34;Содержимое файла: {}\u0026#34;, contents); 29 30 Ok(()) 31} 32 Разберем подробнее:\nФункция ensure_file проверяет наличие файла hello.txt. Если его нет — создаём и записываем туда строку \u0026ldquo;Знакомство с BufReader!\u0026rdquo;. В main открываем файл и оборачиваем его в BufReader. Буферизованное чтение делает работу эффективной. С помощью read_to_string считываем всё содержимое в строку. Результат печатаем в консоль. Это самый простой пример: мы просто читаем файл целиком. Но ключевая идея уже видна - BufReader используется как оболочка вокруг файла, которая делает операции ввода-вывода более эффективными.\nРезультат выполнения: \u0026times; 1use std::fs::{File, OpenOptions}; 2use std::io::{self, BufReader, BufRead, Write}; 3use std::path::Path; 4 5 6fn ensure_file() -\u0026gt; io::Result\u0026lt;()\u0026gt; { 7 if !Path::new(\u0026#34;app.log\u0026#34;).exists() { 8 let mut f = OpenOptions::new().create(true).write(true).open(\u0026#34;app.log\u0026#34;)?; 9 writeln!(f, \u0026#34;[2025-08-27 10:00:01] INFO — Приложение запущено\u0026#34;)?; 10 writeln!(f, \u0026#34;[2025-08-27 10:00:03] WARN — Соединение нестабильно\u0026#34;)?; 11 writeln!(f, \u0026#34;[2025-08-27 10:00:05] ERROR — Ошибка чтения конфигурации\u0026#34;)?; 12 writeln!(f, \u0026#34;[2025-08-27 10:00:07] INFO — Перезапуск сервиса\u0026#34;)?; 13 } 14 Ok(()) 15} 16 17 18fn main() -\u0026gt; io::Result\u0026lt;()\u0026gt; { 19 ensure_file()?; 20 21 let file = File::open(\u0026#34;app.log\u0026#34;)?; 22 let reader = BufReader::new(file); 23 24 let mut line_count = 0; 25 let mut error_count = 0; 26 27 for line in reader.lines() { 28 let line = line?; 29 line_count += 1; 30 if line.contains(\u0026#34;ERROR\u0026#34;) { 31 error_count += 1; 32 } 33 println!(\u0026#34;\u0026gt;\u0026gt; {}\u0026#34;, line); 34 } 35 36 println!(\u0026#34;\\nВ логе {} строки. Из них {} с ошибками.\u0026#34;, line_count, error_count); 37 38 Ok(()) 39} 40 Разберем подробнее что же тут происходит. Мы читаем лог-файл с помощью BufReader:\nФункция ensure_file имитирует создание файла с логом приложения. Если файла app.log нет — создаём его и записываем несколько строк с метками времени, уровнями логов (INFO, WARN, ERROR) и сообщениями. В main открываем файл и оборачиваем его в BufReader чтобы читать эффективно. С помощью reader.lines() проходим построчно в цикле: считаем общее количество строк ищем вхождения слова \u0026quot;ERROR\u0026quot; и увеличиваем отдельный счётчик ошибок печатаем строку с префиксом \u0026gt;\u0026gt; После цикла выводим статистику: сколько строк всего и сколько среди них сообщений об ошибках. Такой приём реально полезен: BufReader позволяет работать даже с огромными логами построчно, без необходимости загружать их целиком в память.\nРезультат выполнения: \u0026times; BufRead против Read # BufReader реализует трейт BufRead, который расширяет возможности Read.\nЗачем нужен отдельный трейт? Потому что некоторые операции становятся эффективными только при наличии внутреннего буфера.\nНапример, метод .lines() возвращает итератор по строкам:\nBufReader: построчное чтение Запуск ▶ 1use std::fs::{File, OpenOptions}; 2use std::io::{self, BufReader, BufRead, Write}; 3 4// Создадим файл 5fn ensure_file() -\u0026gt; io::Result\u0026lt;()\u0026gt; { 6 if !std::path::Path::new(\u0026#34;data.txt\u0026#34;).exists() { 7 let mut f = OpenOptions::new().create(true).write(true).open(\u0026#34;data.txt\u0026#34;)?; 8 writeln!(f, \u0026#34;Первая строка\u0026#34;)?; 9 writeln!(f, \u0026#34;Вторая строка\u0026#34;)?; 10 writeln!(f, \u0026#34;Третья строка\u0026#34;)?; 11 } 12 Ok(()) 13} 14 15fn main() -\u0026gt; io::Result\u0026lt;()\u0026gt; { 16 ensure_file()?; 17 18 let file = File::open(\u0026#34;data.txt\u0026#34;)?; 19 let reader = BufReader::new(file); 20 21 for line in reader.lines() { 22 println!(\u0026#34;{}\u0026#34;, line?); 23 } 24 25 Ok(()) 26} 27 Разберем подробнее:\nBufReader читает файл большими блоками и хранит их в своём буфере. Трейт BufRead добавляет методы, которые работают не с файловым дескриптором напрямую, а с этим буфером в памяти. Метод .lines() ищет символ перевода строки \\n в буфере и возвращает готовые строки. Если бы у нас был только Read, то для поиска конца строки пришлось бы каждый раз вызывать .read() → это означало бы больше системных вызовов и ненужных копирований.\nРезультат выполнения: \u0026times; Другие полезные методы BufRead: # .read_line(\u0026amp;mut String) — читает одну строку целиком в переданную строку. .split(u8) — возвращает итератор, разделяющий поток по заданному байту. .fill_buf() — даёт прямой доступ к текущему содержимому буфера (без копирования). .consume(n) — указывает сколько байт \u0026ldquo;съесть\u0026rdquo; из буфера после обработки. Четыре способа читать файл построчно # Чтобы увидеть разницу, сравним четыре подхода.\nПримеры для 4 подходов ← Шаг 1/4 → Запуск ▶ 1// ?hidden:start 2use std::fs::{self, OpenOptions}; 3use std::io::{self, Write, Read}; 4use std::path::Path; 5use std::fs::File; 6 7 8/// Создаём файл big.txt, если его ещё нет. 9/// 100_0 строк с текстом и номерами. 10fn ensure_file() -\u0026gt; io::Result\u0026lt;()\u0026gt; { 11 if Path::new(\u0026#34;big.txt\u0026#34;).exists() { 12 return Ok(()); 13 } 14 println!(\u0026#34;Создается файл big.txt...\u0026#34;); 15 let mut f = OpenOptions::new().create(true).write(true).open(\u0026#34;big.txt\u0026#34;)?; 16 for i in 0..100_0 { 17 writeln!(f, \u0026#34;Строка номер {i:06} — пример содержимого файла для BufReader\u0026#34;)?; 18 } 19 println!(\u0026#34;Файл создан!\u0026#34;); 20 Ok(()) 21} 22// ?hidden:end 23fn main() -\u0026gt; io::Result\u0026lt;()\u0026gt; { 24 ensure_file()?; // создаём файл 25 26 let start = std::time::Instant::now(); 27 let mut file = File::open(\u0026#34;big.txt\u0026#34;)?; 28 let mut buffer = [0; 1]; 29 let mut line = String::new(); 30 31 while file.read(\u0026amp;mut buffer)? \u0026gt; 0 { 32 let ch = buffer[0] as char; 33 if ch == \u0026#39;\\n\u0026#39; { 34 line.clear(); 35 } else { 36 line.push(ch); 37 } 38 } 39 let end = std::time::Instant::now(); 40 println!(\u0026#34;Время выполнения: {:?}\u0026#34;, end - start); 41 42 Ok(()) 43} 44 Небуферизованное чтение по одному символу ❌ Очень медленно ~123 миллисекунды Здесь каждая операция read идёт напрямую к ОС. Удобно для понимания, но не для реальной работы.\nРезультат выполнения: \u0026times; 1// ?hidden:start 2use std::fs::{self, OpenOptions}; 3use std::io::{self, Write, BufRead, BufReader}; 4use std::path::Path; 5use std::fs::File; 6 7/// Создаём файл big.txt, если его ещё нет. 8/// 100_0 строк с текстом и номерами. 9fn ensure_file() -\u0026gt; io::Result\u0026lt;()\u0026gt; { 10 if Path::new(\u0026#34;big.txt\u0026#34;).exists() { 11 return Ok(()); 12 } 13 println!(\u0026#34;Создается файл big.txt...\u0026#34;); 14 let mut f = OpenOptions::new().create(true).write(true).open(\u0026#34;big.txt\u0026#34;)?; 15 for i in 0..100_0 { 16 writeln!(f, \u0026#34;Строка номер {i:06}\u0026#34;)?; 17 } 18 println!(\u0026#34;Файл создан!\u0026#34;); 19 Ok(()) 20} 21// ?hidden:end 22fn main() -\u0026gt; io::Result\u0026lt;()\u0026gt; { 23 ensure_file()?; // создаём файл 24 let start = std::time::Instant::now(); 25 let mut lines_count = 0; 26 27 let file = File::open(\u0026#34;big.txt\u0026#34;)?; 28 let reader = BufReader::new(file); 29 30 for line in reader.lines() { 31 lines_count += 1; 32 } 33 34 let end = std::time::Instant::now(); 35 println!(\u0026#34;Время выполнения: {:?}\u0026#34;, end - start); 36 37 Ok(()) 38} 39 BufReader + .lines() ⚡ Гораздо быстрее ~500µs Это стандартный способ, удобный и быстрый.\nРезультат выполнения: \u0026times; 1// ?hidden:start 2use std::fs::{self, OpenOptions}; 3use std::io::{self, Write, BufRead, BufReader}; 4use std::path::Path; 5use std::fs::File; 6 7 8 9/// Создаём файл big.txt, если его ещё нет. 10/// 100_0 строк с текстом и номерами. 11fn ensure_file() -\u0026gt; io::Result\u0026lt;()\u0026gt; { 12 if Path::new(\u0026#34;big.txt\u0026#34;).exists() { 13 return Ok(()); 14 } 15 println!(\u0026#34;Создается файл big.txt...\u0026#34;); 16 let mut f = OpenOptions::new().create(true).write(true).open(\u0026#34;big.txt\u0026#34;)?; 17 for i in 0..100_0 { 18 writeln!(f, \u0026#34;Строка номер {i:06}\u0026#34;)?; 19 } 20 println!(\u0026#34;Файл создан!\u0026#34;); 21 Ok(()) 22} 23// ?hidden:end 24fn main() -\u0026gt; io::Result\u0026lt;()\u0026gt; { 25 ensure_file()?; // создаём файл 26 27 let start = std::time::Instant::now(); 28 let mut lines_count = 0; 29 let file = File::open(\u0026#34;big.txt\u0026#34;)?; 30 let mut reader = BufReader::new(file); 31 let mut line = String::new(); 32 33 while reader.read_line(\u0026amp;mut line)? \u0026gt; 0 { 34 lines_count += 1; 35 line.clear(); // строка очищается, но не выделяется заново 36 } 37 38 let end = std::time::Instant::now(); 39 println!(\u0026#34;Время выполнения: {:?}\u0026#34;, end - start); 40 41 Ok(()) 42} 43 BufReader + переиспользование строки ⚡ Ещё быстрее ~360µs read_line записывает прямо в существующую строку. Нет лишних аллокаций памяти, работает эффективнее.\nРезультат выполнения: \u0026times; 1// ?hidden:start 2use std::fs::{self, OpenOptions}; 3use std::io::{self, Write, Read}; 4use std::path::Path; 5use std::fs::File; 6 7/// Создаём файл big.txt, если его ещё нет. 8/// 100_0 строк с текстом и номерами. 9fn ensure_file() -\u0026gt; io::Result\u0026lt;()\u0026gt; { 10 if Path::new(\u0026#34;big.txt\u0026#34;).exists() { 11 return Ok(()); 12 } 13 println!(\u0026#34;Создается файл big.txt...\u0026#34;); 14 let mut f = OpenOptions::new().create(true).write(true).open(\u0026#34;big.txt\u0026#34;)?; 15 for i in 0..100_0 { 16 writeln!(f, \u0026#34;Строка номер {i:06}\u0026#34;)?; 17 } 18 println!(\u0026#34;Файл создан!\u0026#34;); 19 Ok(()) 20} 21// ?hidden:end 22fn main() -\u0026gt; io::Result\u0026lt;()\u0026gt; { 23 24 ensure_file()?; // создаём файл 25 let start = std::time::Instant::now(); 26 27 let mut lines_count = 0; 28 let mut file = File::open(\u0026#34;big.txt\u0026#34;)?; 29 let mut contents = String::new(); 30 file.read_to_string(\u0026amp;mut contents)?; 31 32 for line in contents.lines() { 33 lines_count += 1; 34 } 35 36 let end = std::time::Instant::now(); 37 println!(\u0026#34;Время выполнения: {:?}\u0026#34;, end - start); 38 39 Ok(()) 40} 41 4. Еще один вариант\n⚡ ~270µs Всё содержимое читается за раз, а потом разбивается на строки.\nВремя выполнения у вас может отличаться от представленного здесь.\nМинус: выделяется объём памяти равный размеру файла.\nРезультат выполнения: \u0026times; Когда BufReader не нужен # Хотя BufReader полезен в большинстве сценариев, есть ситуации, когда он не только не ускорит программу, но и будет лишним.\n1. Данные уже в памяти # Если у вас есть Vec\u0026lt;u8\u0026gt;, String или срез \u0026amp;[u8], никакого дополнительного системного вызова не происходит. Данные уже находятся в оперативной памяти, а значит никакой выгоды от дополнительного буфера не будет.\n2. Чтение крупными блоками # Если вы сразу читаете данные большими кусками (десятки или сотни килобайт), то накладные расходы на системные вызовы становятся несущественными.\n3. Ограниченная память # Если у вас тысячи одновременных соединений (например, работаете с сервером), каждый BufReader создаёт внутренний буфер (по умолчанию 8 КБ). При тысячах соединений это может означать десятки мегабайт памяти, которые не всегда оправданы.\nВ асинхронном коде часто используют другие подходы: читают ровно столько - сколько пришло в сокете и сами управляют буферами чтобы экономить память.\nВывод # Read — низкоуровневый контракт для чтения потоков байтов. Каждый вызов .read() - это системный вызов и он дорогой. BufReader сокращает число системных вызовов и делает чтение в разы быстрее. На практике BufReader с повторным использованием строки даёт лучший баланс между скоростью и памятью.\n","date":"26 августа 2025","externalUrl":null,"permalink":"/rust/bufreader/","section":"Rust","summary":"","title":"BufReader в Rust: ускоряем ввод-вывод","type":"rust"},{"content":" Введение # Сегодня мы создадим консольную утилиту на Rust. По задумке она должна помочь мастеру находить устройства в конфигурации умного дома по короткому запросу.\nНапример можно ввести: \u0026ldquo;лампочка\u0026rdquo; или \u0026ldquo;розетка\u0026rdquo;.\nЭто практический пример для отладки и настройки систем умного дома.\nПараллельно с этим мы разберём основы: работу с аргументами, чтение файла, парсинг, фильтрацию и безопасную обработку данных.\nШаг 0: Создание проекта # Перед началом работы нам нужно создать структуру проекта. Rust использует инструмент Cargo (о нем мы уже говорили в начальной статье, но можно и напомнить) - это менеджер пакетов, сборщик и система управления зависимостями. Практически все проекты на Rust начинаются с cargo new.\nОткроем терминал и выполним команды:\ncargo new smart_helper cd smart_helper Команда создаёт директорию smart_helper с шаблоном проекта: src/main.rs, Cargo.toml, и .gitignore. Что внутри # После команды cargo new структура проекта будет следующей:\nsmart_helper/ ├── Cargo.toml └── src └── main.rs Cargo.toml # Файл конфигурации, где указываются:\nимя и версия пакета зависимости (dependencies) тип проекта (бинарный или библиотека в нашем случае бинарный) На данный момент содержимое будет минимальным:\n[package] name = \u0026#34;smart_helper\u0026#34; version = \u0026#34;0.1.0\u0026#34; edition = \u0026#34;2024\u0026#34; [dependencies] main.rs # Файл src/main.rs — точка входа:\nfn main() { println!(\u0026#34;Hello, world!\u0026#34;); } Мы его позже заменим на логику CLI-приложения.\nЗапуск проекта # Проверим, что проект компилируется:\ncargo run Вывод должен быть:\nHello, world!\nСовет: Никогда не начинай Rust-проект без cargo new. Это избавит от множества ручных ошибок и создаст правильную структуру сразу.\nГотово! У нас есть чистая заготовка для приложения. Теперь можно переходить к следующему шагу — работе с аргументами командной строки.\nШаг 1: Получение аргументов командной строки # Язык Rust предоставляет модуль std::env. Он позволяет взаимодействовать с окружением операционной системы. Кроме того он позволяет получить доступ к аргументам командной строки - именно этой возможностью мы и воспользуемся.\nНачнём с кода: # Что потребуется подключить? # Нам понадобится стандартная библиотека Rust. А именно подключенные модули:\nuse std::{ env, error::Error, fs::File, io::{BufRead, BufReader}, }; Собираем аргументы # fn main() -\u0026gt; Result\u0026lt;(), Box\u0026lt;dyn Error\u0026gt;\u0026gt; { let args: Vec\u0026lt;String\u0026gt; = env::args().collect(); if args.len() != 3 { println!(\u0026#34;Использование: smart_helper \u0026lt;config_file\u0026gt; \u0026lt;поисковая_фраза\u0026gt;\u0026#34;); return Ok(()); } Ok(()) } env::args() 1 - функция из модуля std::env. Она возвращает итератор по аргументам, переданным в командной строке. Аргументы возвращаются как std::env::Args - это \u0026ldquo;ленивый итератор\u0026rdquo; который мы собираем в вектор с помощью .collect() (про итераторы мы поговорим в другой раз, сейчас надо представлять их как функцию которая возвращает следующий доступный элемент при вызове, а ленивый потому что функция не вызвается сразу, а только когда после неё будет вызван специальный метод, как в нашем случае collect) .collect() 2 преобразует итератор в коллекцию Vec\u0026lt;String\u0026gt; - удобный для нас тип. Он позволяет обращаться к аргументам по индексу. Первый элемент args[0] - это путь к исполняемому файлу. Поэтому пользовательские значения начинаются с args[1]. Почему Vec\u0026lt;String\u0026gt;? # env::args() возвращает элементы типа String, а не \u0026amp;str, потому что данные копируются из системного ввода в память программы. Это значит: нельзя использовать \u0026amp;str так как у нас нет ссылки на оригинальные аргументы; String это безопасный способ владения строками в Rust; Vec\u0026lt;String\u0026gt; даёт произвольный доступ, например args[1], args[2], что невозможно с чистым итератором. Зачем мы проверяем длину через len? # if args.len() != 3\nПотому что мы ожидаем:\nargs[0] - путь к самому бинарнику; args[1] — путь к конфигурационному файлу; args[2] — строка поиска. Если аргументов меньше или больше - приложение выведет подсказку и завершится корректно. Если не проверять args.len() программа может аварийно завершиться при попытке обратиться к несуществующему элементу в Vec. Альтернативный вариант: итератор без collect # Можно было бы сразу использовать let mut args = env::args(); но это неудобно:\nlet config_path = args.nth(1).unwrap(); let query = args.next().unwrap(); Этот стиль менее удобен для новичков и не позволяет легко вывести ошибку, если аргументов недостаточно. Поэтому в этом проекте мы используем явный Vec\u0026lt;String\u0026gt;.\nШаг 2: Открытие и буферизация файла # let file = File::open(\u0026amp;args[1])?; let reader = BufReader::new(file); BufReader даёт построчный доступ без загрузки всего файла в память. (Про BufReader мы также еще поговорим отдельно) Шаг 3: Разбор одного блока конфигурации # Мы подошли к важной части: нужно распарсить (разобрать на составляющие) один блок из текстового файла, который содержит описание устройства. Конфигурация у нас будет выглядеть вот так:\ndevice_id: light_hue_rgb_03 name: Philips Hue RGB лампочка location: Гостиная (люстра) serial: PH-HUE-03 features: [диммируемая, цветная] Блоки разделяются через строку \u0026quot;---\u0026quot;\nНаша задача - превратить этот блок в структуру Device с которой уже можно работать как с Rust-объектом.\nСтруктура устройства # Сначала определим структуру, в которую будем собирать данные:\n#[derive(Default)] 3 struct Device { device_id: String, name: String, location: String, serial: String, features: Vec\u0026lt;String\u0026gt;, } #[derive(Default)] 3 позволяет создать пустой Device по умолчанию вызовом Device::default() это удобно для постепенного заполнения. Парсинг блока # fn parse_device(block: \u0026amp;str) -\u0026gt; Option\u0026lt;Device\u0026gt; { let mut device = Device::default(); for line in block.lines() { 4 let parts: Vec\u0026lt;\u0026amp;str\u0026gt; = line.splitn(2, \u0026#39;:\u0026#39;).collect(); 5 if parts.len() != 2 { continue; } let key = parts[0].trim(); 6 let value = parts[1].trim(); match key { 7 \u0026#34;device_id\u0026#34; =\u0026gt; device.device_id = value.to_string(), \u0026#34;name\u0026#34; =\u0026gt; device.name = value.to_string(), \u0026#34;location\u0026#34; =\u0026gt; device.location = value.to_string(), \u0026#34;serial\u0026#34; =\u0026gt; device.serial = value.to_string(), \u0026#34;features\u0026#34; =\u0026gt; { let cleaned = value.trim_matches(\u0026amp;[\u0026#39;[\u0026#39;, \u0026#39;]\u0026#39;][..]); device.features = cleaned .split(\u0026#39;,\u0026#39;) .map(|s| s.trim().to_string()) .collect(); } _ =\u0026gt; {} } } Some(device) 8 } Много кода получилось, давайте немного остановимся и разберём строки подробнее:\nblock.lines() # 4 Функция lines() создаёт итератор по строкам.\nНапример из этого блока конфига:\ndevice_id: light01 name: Умная лампочка получим:\n[\u0026quot;device_id: light01\u0026quot;, \u0026quot;name: Умная лампочка\u0026quot;]\nsplitn(2, ':') # 5 Используем splitn(2, \u0026lsquo;:\u0026rsquo;) — это важно потому что эта функция разбивает строку только один раз даже если значение содержит :\nПример:\nlocation: Гостиная: зона 2 Результат: [\u0026quot;location\u0026quot;, \u0026quot;Гостиная: зона 2\u0026quot;]\nЕсли бы мы использовали split(\u0026rsquo;:\u0026rsquo;), результат был бы некорректен — \u0026lsquo;Гостиная\u0026rsquo; и \u0026rsquo; зона 2\u0026rsquo; попали бы в разные части. trim() # 6 trim() убирает пробелы и символы перевода строки с обеих сторон:\nlet key = parts[0].trim(); let value = parts[1].trim(); Это избавляет от проблем при работе с файлами, особенно если строки написаны вручную.\nmatch key # 7 Мы проходим по всем строкам и в зависимости от поля присваиваем значение в device.\nРазберём что происходит:\n\u0026#34;device_id\u0026#34; =\u0026gt; device.device_id = value.to_string() .to_string() копирует \u0026amp;str в String — потому что структура содержит String а не ссылку. Разбор features # Поле features хранится в виде строки:\n[диммируемая, цветная] Чтобы превратить её в Vec\u0026lt;String\u0026gt;:\nlet cleaned = value.trim_matches(\u0026amp;[\u0026#39;[\u0026#39;, \u0026#39;]\u0026#39;][..]); device.features = cleaned .split(\u0026#39;,\u0026#39;) .map(|s| s.trim().to_string()) .collect(); Пояснение # trim_matches(\u0026amp;['[', ']']) убирает квадратные скобки. split(',') разбивает строку по запятой. map(|s| s.trim().to_string()) — убирает пробелы, превращает в String. collect() собирает вектор. Возврат результата # В конце 8 Some(device)\nМы не обрабатываем ошибки парсинга явно — просто пропускаем некорректные строки.\nМожете заметить что мы вручную разбираем структуру без сторонних библиотек. Это полезно для понимания базовой обработки строк и соответствия ключам. Шаг 4: Основной цикл чтения и фильтрации # На данный момент мы подготовили структуру Device, научились её заполнять на основе одного текстового блока. Теперь надо прочитать весь файл, разбить его на блоки и выбрать те, что соответствуют поисковой строке.\nВот фрагмент кода, реализующий эту логику:\nlet mut buffer = String::new(); let mut matches = Vec::new(); let query = args[2].to_lowercase(); for line in reader.lines() { let line = line?; if line.trim() == \u0026#34;---\u0026#34; { if let Some(device) = parse_device(\u0026amp;buffer) { if device.name.to_lowercase().contains(\u0026amp;query) { matches.push(device); } } buffer.clear(); } else { buffer.push_str(\u0026amp;line); buffer.push(\u0026#39;\\n\u0026#39;); } } Что же здесь происходит? # let mut buffer = String::new() Создаём временную строку, в которую будем записывать строки блока конфигурации до тех пор пока не встретим строку-разделитель ---. Каждый такой буфер содержит один логический блок (устройство) в текстовом представлении.\nlet mut matches = Vec::new()\nВектор, куда мы будем складывать устройства, прошедшие фильтр. Это наш итоговый результат. let query = args[2].to_lowercase()\nПереводим строку поиска в нижний регистр — это позволяет искать независимо от регистра: lamp == Лампочка == ЛАМПОЧКА Проход по файлу # for line in reader.lines() { let line = line?; Мы читаем файл построчно — BufReader здесь работает максимально эффективно. Каждая строка —\u0026gt; Result\u0026lt;String\u0026gt;, поэтому мы используем ? для автоматического выхода в случае ошибки.\nГраница блока:\nif line.trim() == \u0026#34;---\u0026#34; Когда встречаем строку ---, это значит, что закончился блок. Всё, что накоплено в buffer —\u0026gt; это одно устройство.\nПарсинг + фильтрация # if let Some(device) = parse_device(\u0026amp;buffer) { if device.name.to_lowercase().contains(\u0026amp;query) { matches.push(device); } } Разбор # parse_device() пытается разобрать buffer в структуру Device if let Some(...) — обрабатывает случай если результат удачный (Some) device.name.to_lowercase().contains(\u0026amp;query) — ищет подстроку без учёта регистра matches.push(device) — сохраняем устройство, которое подошло под критерии Почему buffer.clear()? # После обработки одного блока мы обнуляем буфер чтобы начать записывать следующий.\nЧто же делать с последним блоком? # Если в конце файла нет завершающей строки ---, последний блок так и останется в buffer. Поэтому отдельно обрабатываем его после цикла:\nif !buffer.trim().is_empty() { if let Some(device) = parse_device(\u0026amp;buffer) { if device.name.to_lowercase().contains(\u0026amp;query) { matches.push(device); } } } Это защита от потери последнего элемента - частая ошибка при обработке по разделителям.\nШаг 5: Вывод результата # Наконец мы собрали все подходящие устройства в Vec\u0026lt;Device\u0026gt; под названием matches. Теперь нужно вывести их в консоль.\nRust не имеет встроенного форматирования таблиц или цветного вывода — всё делается вручную через println!.\nВот полный блок кода:\nif matches.is_empty() { println!(\u0026#34;🚫 Ничего не найдено по запросу \u0026#39;{}\u0026#39;\u0026#34;, query); } else { println!(\u0026#34;🔍 Найдено {} совпадений:\u0026#34;, matches.len()); for device in matches { println!(\u0026#34;Название: {}\u0026#34;, device.name); println!(\u0026#34;ID: {}\u0026#34;, device.device_id); println!(\u0026#34;Местоположение: {}\u0026#34;, device.location); println!(\u0026#34;Серийный номер: {}\u0026#34;, device.serial); println!(\u0026#34;Возможности: {}\u0026#34;, device.features.join(\u0026#34;, \u0026#34;)); } } Проверка на пустой результат # if matches.is_empty() Мы не хотим запускать цикл, если ничего не найдено. Метод .is_empty() проверяет, что длина вектора равна нулю.\nЭто читаемый и эффективный способ, эквивалент matches.len() == 0, но он более привычный и используемый. Вывод количества совпадений # println!(\u0026#34;🔍 Найдено {} совпадений:\u0026#34;, matches.len()); Форматирование через макрос println! с подстановкой переменной. Здесьmatches.len() — возвращает usize, который можно спокойно вставить в форматируемую строку.\nПример конфигурационного файла # device_id: light_hue_rgb_01 name: Умная лампочка Philips Hue RGB location: Гостиная serial: PH-HUE-01 features: [диммируемая, цветная] --- device_id: plug_lenovo_16a name: Розетка умная Lenovo location: Кухня serial: LN-PLUG-44891 features: [мониторинг_питания] Пример работы # Если в файле конфига есть подходящие устройства и мы запустили:\ncargo run devices.txt ламп\nВывод может быть таким:\n🔍 Найдено 1 совпадений: Название: Умная лампочка Philips Hue RGB ID: light_hue_rgb_01 Местоположение: Гостиная (люстра) Серийный номер: PH-HUE-01 Возможности: диммируемая, цветная Готово! 🚀 Мы не просто написали утилиту, а разобрали все ключевые части Rust-приложения: от создания проекта и ввода данных до построчного чтения, обработки и красивого вывода.\nЗаключение # Мы создали полноценную CLI-утилиту на Rust для поиска устройств по описанию из конфигурационного файла. Приложение построено на стандартной библиотеке, без сторонних зависимостей. При этом оно уже решает реальную задачу.\nЧто мы изучили в процессе написания этого проекта:\nРабота с аргументами командной строки Построчное чтение файла Поиск по подстроке Парсинг структур вручную Мы написали простой, но качественный инструмент. А ещё — разобрали важные аспекты Rust, от итераторов до ошибок, от строк до структур. Это именно тот путь, по которому изучается язык. 👇🏻 Полный код проекта вы можете найти в репозитории\neaglebk/smart_helper CLI на Rust для поиска устройств в текстовых конфигурациях. Учебный проект Rust 0 0 ","date":"17 июня 2025","externalUrl":null,"permalink":"/rust/rust-cli-sh/","section":"Rust","summary":"","title":"Поиск данных в файле - Cli хелпер для мастера с помощью Rust","type":"rust"},{"content":" Введение # В этой статье разберём как получать ввод от пользователя в терминальном приложении на Rust с использованием стандартной библиотеки.\nКазалось бы, простая задача - спросить имя и поприветствовать пользователя. Но даже в этом примере кроются важные особенности языка: работа со стандартным вводом, ссылки, обработка ошибок и типы.\nЧто потребуется? # Нам понадобится модуль std::io который входит в стандартную библиотеку Rust. Он предоставляет доступ к stdin() — стандартному вводу. Чтобы использовать его, нужно подключить:\nuse std::io; Пример: Приветствие пользователя # Начнём с минимального приложения:\nuse std::io; fn main() { let mut input 1 = String::new(); 2 println!(\u0026#34;Enter your name:\u0026#34;); io::stdin().read_line(\u0026amp;mut input).unwrap(); 3 println!(\u0026#34;Hello {input}\u0026#34;); } Что здесь важно: # \u0026amp;mut input 1 - передаём изменяемую ссылку, потому что read_line требует возможность изменить содержимое. String::new() 2 - создаёт пустую строку для хранения ввода. .unwrap() - в данном случае упрощённая обработка Result. Позже заменим это на более надёжный способ. 3 Коротко о типах # Функция read_line возвращает Result\u0026lt;usize\u0026gt; где usize — количество байт прочитанных из ввода.\nТип usize — это целое без знака, размер которого зависит от архитектуры машины (например, 64-битный на x86_64). Добавим отладочную информацию: # use std::io; fn main() { let mut input = String::new(); println!(\u0026#34;Enter your name:\u0026#34;); match io::stdin().read_line(\u0026amp;mut input) { Ok(n) =\u0026gt; { println!(\u0026#34;{n} bytes read\u0026#34;); println!(\u0026#34;Hello {input}\u0026#34;); } Err(error) =\u0026gt; println!(\u0026#34;error: {error}\u0026#34;), } } Пример вывода: # Enter your name: developer 10 bytes read Hello developer Почему 10 байт? Строка \u0026quot;developer\u0026quot; — это 9 символов + 1 символ новой строки \\n автоматически добавляемый после нажатия Enter. Практическое применение # Такой ввод часто используется в CLI-интерфейсах: для подтверждения действия, выбора режима работы, простых форм.\nОсобенности ввода в CLI:\nread_line блокирует выполнение, пока пользователь не введёт строку. Строка сохраняет символ новой строки \\n, что иногда требуется удалять с помощью .trim(). Совет: чтобы избавиться от \\n, используйте .trim()\nlet name = input.trim(); println!(\u0026#34;Hello {name}\u0026#34;); Заключение # Мы познакомились с базовым способом взаимодействия с пользователем в консольном приложении на Rust. Хотя код получился коротким. Под капотом - работа с изменяемыми ссылками, возвратом Result, безопасной обработкой ввода и особенностями строк.\nНа этом всё. Пробуйте, меняйте, экспериментируйте.\n","date":"8 июня 2025","externalUrl":null,"permalink":"/rust/rust-stdio/","section":"Rust","summary":"","title":"Ввод данных от пользователя в Rust: как использовать stdin","type":"rust"},{"content":" Введение # Если вы уже знакомы с Rc\u0026lt;T\u0026gt; (если не знакомы, то можете прочитать прошлую статью), то, возможно задавались вопросом - а можно ли безопасно делить данные между потоками? Ответ — можно, но для этого нужен другой тип: Arc\u0026lt;T\u0026gt;.\nЧто такое Arc? # Arc расшифровывается как Atomically Reference Counted (атомарный счетчик ссылок). Он работает как Rc, но с одной важной особенностью: он потокобезопасен. То есть, в отличие от Rc, его можно использовать в многопоточном коде.\nСинтаксис базового использования: # use std::sync::Arc; let data = Arc::new(vec![1, 2, 3]); let data_cloned = Arc::clone(\u0026amp;data); Ключевые особенности Arc: # Подсчёт ссылок происходит атомарно (через атомарные инструкции процессора). Может использоваться в нескольких потоках. Не даёт изменять данные напрямую — для этого используется Mutex, RwLock или другие инструменты о которых постараюсь рассказать в следующих статьях. Сравнение с Rc: # Тип Поддерживает многопоточность Атомарный подсчет Rc ❌ Нет ❌ Нет Arc ✅ Да ✅ Да Пример # Пример проблемы с Rc в многопоточном коде ← Шаг 1/2 → Запуск ▶ 1use std::rc::Rc; 2use std::thread; 3 4fn main() { 5 let data = Rc::new(vec![1, 2, 3]); 6 7 let handle = thread::spawn(move || { 8 println!(\u0026#34;{:?}\u0026#34;, data); 9 }); 10 11 handle.join().unwrap(); 12} 1. Этот код не скомпилируется.\nОшибка: Rc\u0026lt;Vec\u0026lt;i32\u0026gt;\u0026gt; cannot be sent between threads safely Результат выполнения: \u0026times; 1use std::sync::Arc; 2use std::thread; 3 4fn main() { 5 let data = Arc::new(vec![1, 2, 3]); 6 let data_cloned = Arc::clone(\u0026amp;data); 7 8 let handle = thread::spawn(move || { 9 println!(\u0026#34;{:?}\u0026#34;, data_cloned); 10 }); 11 12 handle.join().unwrap(); 13} 2. Теперь всё работает корректно. Результат выполнения: \u0026times; Пример для случая когда нужно изменить данные # Arc сам по себе не даёт изменять содержимое.\nЕсли нужно менять данные из разных потоков, используйте Mutex (мютекс это как замок, который позволяет работать с данными только одному потоку за раз):\nПример с изменением данных Запуск ▶ 1use std::sync::{Arc, Mutex}; 2use std::thread; 3 4fn main() { 5 let counter = Arc::new(Mutex::new(0)); 6 let mut handles = vec![]; 7 8 for _ in 0..10 { 9 let counter_cloned = Arc::clone(\u0026amp;counter); 10 let handle = thread::spawn(move || { 11 let mut num = counter_cloned.lock().unwrap(); 12 *num += 1; 13 }); 14 handles.push(handle); 15 } 16 17 for handle in handles { 18 handle.join().unwrap(); 19 } 20 21 println!(\u0026#34;Result: {}\u0026#34;, *counter.lock().unwrap()); 22} Разберем подробнее что же тут происходит. Это пример демонстрации безопасного изменения общего значения из нескольких потоков:\ncounter — это общая переменная, защищённая Mutex и обёрнутая в Arc (как мы уже знаем он позволяет безопасно делиться данными между потоками). В цикле создаётся 10 потоков с thread::spawn каждый из которых: получает свою копию Arc для counter блокирует Mutex для получения доступа к данным увеличивает значение на 1 handle.join() ждёт завершения всех потоков. Результат выполнения: \u0026times; Мы изучим Mutex и thread::spawn более глубоко в следующих статьях - это важные концепции языка Rust для работы с потоками.\nПрактические советы по Arc # Используйте Arc если данные нужны в нескольких потоках. Для изменяемых данных — комбинируйте с Mutex, RwLock и т.д. Не злоупотребляйте Arc: каждый клон — это атомарная операция, она дороже, чем обычный счётчик Rc. 🦀 hard skill не для начинающих! По умолчанию Arc\u0026lt;T\u0026gt; — это Send + Sync для случая когда T: Send + Sync. Заключение # Arc полезен когда вы работаете с многопоточностью. Он гарантирует безопасность при доступе к общим данным. Но требует осторожности в плане производительности. Если вам не нужен доступ из разных потоков - используйте Rc. Но когда дело доходит до многопоточного доступа — Arc становится незаменимым.\nНа сегодня это все!\n","date":"5 июня 2025","externalUrl":null,"permalink":"/rust/rust-arc/","section":"Rust","summary":"","title":"Arc в Rust: потокобезопасное разделённое владение","type":"rust"},{"content":" Введение # Представьте ситуацию: у вас есть данные в куче (heap) и вы хотите, чтобы несколько объектов могли их использовать. Но Rust не любит ситуации когда неясно кто владеет объектом. Именно тогда на сцену выходит Rc\u0026lt;T\u0026gt;.\nЧто такое Rc # Rc\u0026lt;T\u0026gt; — это умный указатель, реализующий счётчик ссылок (shared ownership). При клонировании Rc\u0026lt;T\u0026gt; вы не копируете данные, а просто увеличиваете счётчик владельцев. Это эффективно, но только для однопоточного кода.\nПочему бы не использовать \u0026amp;T ?\n\u0026amp;T — это заимствование, но оно не даёт владения. Ссылка жёстко ограничена временем жизни (lifetimes) об этом еще поговорим, её нельзя сохранять в структурах без явной аннотации \u0026lsquo;a. Rc\u0026lt;T\u0026gt; решает эту проблему и даёт вам контроль и гибкость.\nПример разделения данных между структурами # В проекте у нас может быть структура City, в которой хранятся данные о населении и общее описание города. Это описание может использоваться также в других местах: например, в туристическом справочнике или для отчёта. Вместо копирования строки мы хотим её разделить.\nПример c городами и историей ← Шаг 1/2 → Запуск ▶ 1#[derive(Debug)] 2struct City { 3 name: String, 4 history: String, 5} 6 7#[derive(Debug)] 8struct CitySummary { 9 headline: String, 10 description: String, 11} 12 13fn main() { 14 let history = String::from(\u0026#34;Город основан в 1574 году\u0026#34;); 15 16 let city = City { 17 name: String::from(\u0026#34;Уфа\u0026#34;), 18 history: history, 19 }; 20 21 let summary = CitySummary { 22 headline: String::from(\u0026#34;Уфа\u0026#34;), 23 description: history, // ❌ перемещено из-под city 24 }; 25 26 println!(\u0026#34;{:?}, {:?}\u0026#34;, city, summary); 27} 1. Ошибка: попытка поделиться строкой напрямую между структурами\nМы либо копируем строку, либо нарушаем правила владения, переместив значение. ⚠️ Данные не могут быть одновременно в двух структурах. Результат выполнения: \u0026times; 1use std::rc::Rc; 2 3#[derive(Debug)] 4struct City { 5 name: Rc\u0026lt;String\u0026gt;, 6 history: Rc\u0026lt;String\u0026gt;, 7} 8 9#[derive(Debug)] 10struct CitySummary { 11 headline: Rc\u0026lt;String\u0026gt;, 12 description: Rc\u0026lt;String\u0026gt;, 13} 14 15fn main() { 16 let name = Rc::new(String::from(\u0026#34;Уфа\u0026#34;)); 17 let history = Rc::new(String::from(\u0026#34;Основан в 1574 году\u0026#34;)); 18 19 let city = City { 20 name: Rc::clone(\u0026amp;name), 21 history: Rc::clone(\u0026amp;history), 22 }; 23 24 let summary = CitySummary { 25 headline: Rc::clone(\u0026amp;name), 26 description: Rc::clone(\u0026amp;history), 27 }; 28 29 println!(\u0026#34;Город: {:?} Сводка: {:?}\u0026#34;, city, summary); 30 println!(\u0026#34;Ссылок на описание: {}\u0026#34;, Rc::strong_count(\u0026amp;history)); 31} 32 2. Решение: использование Rc для разделения строк\nС Rc можно безопасно использовать одни и те же данные в нескольких структурах. Владение разделено, а данные освобождаются когда последний владелец исчезает. Результат выполнения: \u0026times; Связка Rc + RefCell # Что делать, если нужно изменять данные? Встроить RefCell\u0026lt;T\u0026gt; в Rc. Тогда получим внутреннюю мутабельность с проверкой во время выполнения. Это даёт гибкость, но требует внимательности.\nОшибки и паника # RefCell вызывает панику при попытке взять два borrow_mut() сразу. Ошибка не в синтаксисе, а в логике. Это помогает обеспечить безопасность в рантайме, когда нельзя использовать обычные ссылки.\nПример общего изменяемого состояния через Rc + RefCell # Примеры работы с RefCell ← Шаг 1/3 → Запуск ▶ 1use std::rc::Rc; 2use std::cell::RefCell; 3 4fn main() { 5 let users = Rc::new(RefCell::new(vec![\u0026#34;Алиса\u0026#34;.to_string()])); 6 7 let session1 = Rc::clone(\u0026amp;users); 8 let session2 = Rc::clone(\u0026amp;users); 9 10 session1.borrow_mut().push(\u0026#34;Макс\u0026#34;.to_string()); 11 session2.borrow_mut().push(\u0026#34;Петр\u0026#34;.to_string()); 12 13 println!(\u0026#34;Онлайн: {:?}\u0026#34;, users.borrow()); 14} 1. Общий список пользователей в чате\nМы моделируем общее состояние users, доступное из разных частей приложения. RefCell позволяет менять данные при множественных владельцах. Результат выполнения: \u0026times; 1#[allow(unused_variables)] 2use std::rc::Rc; 3use std::cell::RefCell; 4 5fn main() { 6 let shared = Rc::new(RefCell::new(vec![1, 2, 3])); 7 8 let _first = shared.borrow_mut(); 9 let _second = shared.borrow_mut(); // ❌ паника: уже есть активный borrow_mut 10} 11 2. Ошибка: двойной borrow_mut при вложенных вызовах\nRefCell проверяет заимствование во время выполнения. Нельзя взять borrow_mut дважды одновременно. Результат выполнения: \u0026times; 1#[allow(unused_variables)] 2use std::rc::Rc; 3use std::cell::RefCell; 4fn main() { 5 let shared = Rc::new(RefCell::new(vec![1, 2, 3])); 6 7 { 8 let mut data = shared.borrow_mut(); 9 data.push(4); 10 } 11 12 let again = shared.borrow_mut(); 13 println!(\u0026#34;{:?}\u0026#34;, again); 14} 3. Исправление: ограничиваем область действия borrow\nВложите borrow_mut в отдельный блок — это освободит заимствование до следующего вызова. ✅ Результат выполнения: \u0026times; Где можно использовать Rc # AST (Abstract Syntax Tree) сам не пробовал, но логика и официальная документация говорит, что должно работать Структуры с разделёнными дочерними узлами Кэширование Общие данные для GUI, событий, конфигурации Заключение # Rc это ещё один из важных механизмов языка Rust. Он не позволяет расширяет стандартную модель владения, а также предоставляет дополнительный инструмент для безопасной и выразительной работы с памятью. Используйте Rc там, где необходимо разделить владение между несколькими объектами которым требуется доступ к данным.\nНе забывайте, что Rc используется для подсчета ссылок в однопоточном коде. ❗️ Если вам нужно работать в нескольких потоках - для этого используется Arc, но об этом вы узнаете в следующих статьях. На сегодня это все!\n","date":"3 июня 2025","externalUrl":null,"permalink":"/rust/rust-rc/","section":"Rust","summary":"","title":"Rc в Rust: Руководство по разделённому владению и RefCell","type":"rust"},{"content":" Введение # Сегодня нам предстоит разобраться с темой заимствования. Как безопасно работать с данными без передачи владения.\nНо прежде чем погружаться в эту тему разберём, что такое ссылка и её базовый синтаксис.\nЧто такое ссылки в Rust? # В Rust ссылка \u0026amp; (reference) — это указатель на значение, который не владеет данными. Рассмотрим базовый пример:\nlet x = 42; // Создаём значение let x_ref = \u0026amp;x; // Создаём ссылку (заимствование) println!(\u0026#34;{}\u0026#34;, *x_ref); // Доступ к значению через * Ключевые особенности ссылок # Нулевая стоимость — ссылки не копируют данные, работая аналогично указателям (но с проверками безопасности) Существует два типа ссылок: \u0026amp;T — неизменяемая ссылка (read-only) только чтение \u0026amp;mut T — изменяемая ссылка (read-write) чтение и запись Явное разыменование: *x_ref — получает значение (аналогично C++, но с проверками) Важно! В большинстве случаев Rust сам разыменовывает ссылки автоматически. Например, в println!(\u0026quot;{}\u0026quot;, x_ref) символ * не требуется.\nСовет: Хотите глубже понять основы? Прочитайте предыдущую статью перед продолжением. Зачем нужно заимствование? # Заимствование в языке Rust (borrowing) — это механизм, позволяющий временно использовать данные, не становясь их владельцем.\nСистема заимствования решает три ключевые задачи:\nИсключает гонки данных (data races) в многопоточном коде. Гарантирует безопасность памяти без сборщика мусора Избегает лишнего копирования больших структур данных В отличие от владения (ownership), заимствование не передаёт права на использование и освобожение памяти — оно лишь даёт доступ к данным на время.\nНеизменяемые ссылки (\u0026amp;T) в Rust # Аналогия из жизни: чтение книги в библиотеке. Вы можете только прочитать её, но не изменять.\nПрактический пример # fn calculate_length(s: \u0026amp;String) -\u0026gt; usize { s.len() // Чтение без изменения } Еще один пример # Пример с автомобилем Запуск ▶ 1fn borrow_car(borrower: \u0026amp;String) { 2 println!(\u0026#34;{} from borrow_car\u0026#34;, borrower); 3} 4 5fn main() { 6 let owner = String::from(\u0026#34;automobile\u0026#34;); 7 borrow_car(\u0026amp;owner); 8 println!(\u0026#34;{} from main\u0026#34;, owner); // всё ещё доступен 9} 10 Вместо того чтобы передавать владение переменной (move), можно передать ссылку на неё: \u0026amp; или \u0026amp;mut.\nborrow_car использует ссылку \u0026amp;String В main переменная owner всё ещё жива — потому что её никто не «забрал» Результат выполнения: \u0026times; Правила работы с \u0026amp;T:\nМожно создавать сколько угодно неизменяемых ссылок одновременно. При существовании \u0026amp;T изменяемые ссылки (\u0026amp;mut T) запрещены Нельзя изменять данные через \u0026amp;T Изменяемые ссылки (\u0026amp;mut T) в Rust # Аналогия из жизни: Эксклюзивный доступ к документу для редактирования - только один автор может изменять данные.\nПример использования # fn add_suffix(s: \u0026amp;mut String) { s.push_str(\u0026#34;_suffix\u0026#34;); } let mut data = String::from(\u0026#34;text\u0026#34;); add_suffix(\u0026amp;mut data); Жёсткие ограничения:\nТолько одна изменяемая ссылка в одной области видимости. Нельзя одновременно иметь \u0026amp;mut T и \u0026amp;T. Гарантирует отсутствие гонок данных. Ошибки заимствования: как их избежать # Компилятор Rust предотвращает распространённые ошибки:\nlet mut vec = vec![1, 2, 3]; let first = \u0026amp;vec[0]; // Неизменяемая ссылка vec.push(4); // Ошибка! Попытка изменения Решение: ограничить область видимости ссылки или переписать код, чтобы не нужно было изменять исходную коллекцию.\nВремя жизни (Lifetimes) и заимствование # Время жизни ('a) определяет, как долго ссылка остаётся действительной:\nfn longest\u0026lt;\u0026#39;a\u0026gt;(x: \u0026amp;\u0026#39;a str, y: \u0026amp;\u0026#39;a str) -\u0026gt; \u0026amp;\u0026#39;a str { if x.len() \u0026gt; y.len() { x } else { y } } Разберем чуть подробнее:\n'a — время жизни, общее для x, y и возвращаемого значения. Компилятор гарантирует, что возвращаемая ссылка будет валидной. В следующих статьях я еще вернусь к теме времени жизни потому что часто программисты сталкиваются с ошибками в этой части.\nПримеры взятые из практики # Команда работает над презентацией Только один человек может редактировать презентацию в Google Docs → \u0026amp;mut Остальные — могут только просматривать → \u0026amp; Какую в этом случае проблему решают ссылки: если один редактирует, а другой одновременно читает - возникает риск рассинхронизации (Rust не даст это сделать) Web-сервер Eсть глобальная конфигурация Config Каждый запрос читает \u0026amp;Config Изменить можно только через \u0026amp;mut Config, например, при hot-reload Практические советы по заимствованию # Всегда начинайте с \u0026amp;T по умолчанию — если не нужно изменять данные Избегайте \u0026amp;mut T в больших областях видимости — это ограничивает гибкость Доверяйте компилятору - если код компилируется, заимствование безопасно Он не даст вам запустить программу где произойдет ошибка при заимствовании Учитесь читать вывод компилятора об ошибках - он точно указывазывает на проблемы в коде Заключение # Заимствование в Rust — это то без чего этот язык сложно представить. Как и с владением вам предстоит постоянно с ним сталкиваться и работать. Эти концепции заложены в язык и благодаря им невозможно будет освободить память дважды, не будет висячих указателей, сборка мусора не требуется, а также у программы будет предсказуемое поведение в рантайме.\nПоверьте это большие проблемы в приложениях написанных на других языках. Множество уязвимостей связанно как с переполнением буфера, так и в отказе от обслуживания из-за висячих указателей. (В будущем я еще напишу об этих темах отдельно)\nЯ советую вам придумать для себя аналогии которые близки вашему представлению и образу жизни и тогда восприниматься этот механизм работы с памятью в языке Rust будет проще.\nНа сегодня это все!\n","date":"2 июня 2025","externalUrl":null,"permalink":"/rust/rust-borrowing/","section":"Rust","summary":"","title":"Заимствование (Borrowing) в Rust","type":"rust"},{"content":" Введение # В Rust реализована концепция владения (ownership) определяющая правила работы с данными. Эти правила помогают избежать ошибок при управлении памятью.\nВ языке Rust каждое значение имеет единственного владельца. Владелец управляет временем жизни данных: когда он выходит из области видимости, память автоматически освобождается. Передача данных между переменными может происходить через копирование copy (для простых типов) или перемещение move (для сложных).\nАналогия владения из обычной жизни # Представьте, что у каждой коробки с данными есть один владелец. Хозяйн коробки может владеть содержимым пока он находится в определенном помещении. Когда владелец покидает помещение и не возвращается за ней (выходит за фигурные скобки), коробка выбрасывается арендодателем (строгим borrow checker-ом). Если вы хотите передать коробку другому, старый владелец теряет к ней доступ (происходит перемещение move). Чтобы у другого человека появилась копия этой коробки - её нужно обязательно склонировать или создать дубликат.\nПример # let s1 = String::from(\u0026#34;hello\u0026#34;); 1 // s1 — владелец строки let s2 = s1; 2 // данные перемещаются в s2, s1 больше не валидна // println!(\u0026#34;{}\u0026#34;, s1); 3 // Ошибка: попытка использовать перемещённое значение В данном примере у строки hello появился владелец - переменная s1 1 Затем владение передается переменной s2 2 Все! Больше переменная s1 ничем не владеет и получить доступ к строке hello не получится. Если попробовать вывести 3 значение переменной s1 то мы получим ошибку. Вспомним аналогию с коробками:\nВладелец с именем s1 👱‍♂️ коробки со значением строки hello 🔶 решил продать эту коробку. Он разместил объявление на онлайн площадках. Через некоторое время к нему подъехал покупатель с именем s2 👩 и приобрел коробку а также значение строки 🔶 в ней.\n👱‍♂️🔶 \u0026ndash;\u0026gt; ➡️ 👩 \u0026mdash;-\u0026gt; 👩🔶\nБольше коробка никак не связана со старым владельцем 👱‍♂️ 🚫 🔶. Он только может при желании выкупить ее у нового хозяина коробки s2 👩🔶.\ns2 👩🔶 волен распоряжаться ей как угодно: передать во временное хранение, продать, напечатать книгу с содержимым этой коробки.\nПримерно так и рассуждает компилятор языка rust когда обрабатывает исходный код приложения.\nОсновные понятия # Теперь закрепим основные понятия и определения:\nВладелец (Owner) — это переменная, которая управляет данными (например: let x = 5;). Заимствование (Borrowing) — временное использование данных без передачи владения (например: \u0026amp;x). Правила владения: У данных может быть только один владелец в каждый момент времени. Можно либо изменять данные, либо читать их, но не одновременно. Данные автоматически освобождаются, когда их владелец выходит из области видимости. Borrow checker (механизим проверяющий заимствования) Это \u0026ldquo;строгий охранник/арендодатель\u0026rdquo; в компиляторе языка Rust. Он следит за соблюдением правил владения. В его задачи входит:\nПредотвращение ошибок Использование данных после их освобождения - проблема висячих указателей (dangling pointers). Одновременное чтение и изменение данных - гонка данных (data races). Утечки памяти. Он работает на этапе компиляции, поэтому ошибки обнаруживаются до запуска программы.\nСтек (Stack) и куча (Heap) # Для разработки на rust важно понимать устройство работы с памятью.\nВыделяется 2 типа памяти: cтек и куча:\nО стеке (Stack) # Как работает:\nЭто быстрая и упорядоченная память. В стеке хранятся локальные переменные. Работает по принципу \u0026ldquo;последний вошёл — первый вышел\u0026rdquo; (LIFO). Представьте стопку тарелок: кладёте по одной наверх и когда понадобилась -\u0026gt; достаете её сверху. Размер данных на стеке известен на этапе компиляции (в стеке хранятся простые типы: числа, булевы значения\u0026hellip;). Пример:\nfn main() { let x = 42; // `x` хранится на стеке let y = 10; // `y` тоже на стеке } 4 после выхода из блока 4 x и y автоматически удаляются\nКогда функция завершается, её стековые данные мгновенно очищаются без дополнительных усилий.\nО куче (Heap) # Как работает:\nЭто динамическая память, где хранятся данные, размер которых неизвестен заранее (например: строки, векторы, структуры\u0026hellip;). Выделение и освобождение памяти требует работы (аллокация и деаллокация). В Rust для работы с кучей используются Box, Vec, String и другие умные указатели. Указатель на данные лежит в стеке, а сами данные — в куче. Пример:\nfn main() { let s = String::from(\u0026#34;Hello\u0026#34;); // данные строки – в куче, указатель – в стеке } 5 s выходит из области видимости 5 память автоматически освобождается\nRust сам вызывает деструктор для освобождения памяти.\nРазличия между стеком и кучей # Стек Куча Скорость Быстро (аллокация и доступ) Медленнее (аллокация дороже и сложнее) Размер Фиксированный размер данных Динамический размер Управление Автоматическое управление Нужны указатели (Rust сам следит ними) Доступ Локальность (L1/L2 кеш CPU) Доступ из любой части программы Зачем такая сложность? # Rust строго контролирует доступ к куче через правила владения и заимствования.\nБлагодаря этому нет утечек памяти: когда что-то покидает стек - куча автоматически очищается.\nВ rust не нужно вручную вызывать free/delete (как в C++) или ждать сборщика мусора (как в Golang, Java).\nНа сегодня это все!\nЧтобы не перегружать сразу большим количеством информации о новой концепции, я решил разбить тему владения на несколько статей.\n","date":"31 мая 2025","externalUrl":null,"permalink":"/rust/rust-ownership/","section":"Rust","summary":"","title":"Владение (Ownership) в Rust","type":"rust"},{"content":" Введение # Сегодня мы поговорим о директивах компилятора, которые в языке Rust называются атрибутами (attributes). Это специальные аннотации в коде, которые указывают компилятору, как следует обрабатывать определённые участки исходного кода.\nГоворя простыми словами: директива — это инструкция компилятору: \u0026ldquo;обрати внимание, здесь есть особое правило\u0026rdquo;.\nСинтаксис директив # Атрибуты оформляются следующим образом:\n#[attribute] #[attribute(value)] #[attribute(key = \u0026#34;value\u0026#34;)] #[attribute(value1, value2)] Также существуют глобальные атрибуты, которые применяются ко всему файлу:\n#![attribute] Пример: игнорирование предупреждений об неиспользуемых переменных # Компилятор Rust по умолчанию \u0026ldquo;ругается\u0026rdquo;, если вы создаёте переменную, но не используете её:\nlet miles: i32 = 1266; // warning: unused variable Чтобы проигнорировать (подавить) это предупреждение, можно использовать директиву #[allow(unused_variables)]\nПрименение к одной переменной # #[allow(unused_variables)] let miles: i32 = 1266; Применение к нескольким переменным # #[allow(unused_variables)] let miles: i32 = 1266; #[allow(unused_variables)] let kilometers: i32 = 1300; Применение ко всей функции # #[allow(unused_variables)] fn main() { let miles: i32 = 1266; let kilometers: i32 = 1300; } Применение ко всему файлу # #![allow(unused_variables)] fn main() { let miles: i32 = 1266; let kilometers: i32 = 1300; } Где еще применяются директивы? # Rust предоставляет множество директив, каждая из которых решает свою задачу. Вот примеры полезных атрибутов:\n#[allow(dead_code)] Отключает предупреждение о неиспользуемом коде (например, функциях или структурах): #[allow(dead_code)] fn unused_function() { println!(\u0026#34;Эта функция не используется, но компилятор не ругается.\u0026#34;); } #[cfg(target_os = \u0026quot;linux\u0026quot;)] Код будет скомпилирован только для Linux: #[cfg(target_os = \u0026#34;linux\u0026#34;)] fn platform_specific() { println!(\u0026#34;Этот код скомпилируется только на Linux\u0026#34;); } #[inline(always)] Просит компилятор всегда инлайнить функцию: #[inline(always)] fn fast_function(x: i32) -\u0026gt; i32 { x * 2 } Варианты применения директив # Директивы в Rust могут быть применены к:\nпеременным функциям структурам модулям impl блокам трейтам константам ко всему файлу (через #![...]) Вывод # Директивы компилятора или атрибуты - это удобная фитча языка Rust которая часто используется в проектах и надо уметь их использовать. Со временем вы встретите их большое множество, но будете уже вооружены.\n","date":"26 мая 2025","externalUrl":null,"permalink":"/rust/rust-directives/","section":"Rust","summary":"","title":"Директивы компилятора в Rust","type":"rust"},{"content":" Введение # Псевдонимы типов type aliases — это способ дать существующему типу альтернативное, более понятное имя (аналог никнейм).\nВместо того чтобы читать \u0026ldquo;i32\u0026rdquo;, вы сразу видите: это метры, это айдишник юзера или это цельсии. Такой подход делает код яснее, безопаснее и проще в сопровождении.\nПример # Представим, что вы пишете программу для спортивных соревнований. Вам нужно оперировать длиной трассы.\nfn main() { let race_distance: i32 = 1600; } Rust по умолчанию определит тип i32 для переменной. Но 1600 чего? Метров, милей, cантиметров? Совсем неоднозначно.\nТакое значение без контекста легко может привести к ошибке.\nИспользуем псевдоним для типа i32.\ntype Meters = i32; fn main() { let race_distance: Meters = 1600; } Теперь стало проще и из кода понятно: это метры.\nКак объявить псевдоним для типа? # Синтаксис простой:\ntype NameAlias = Существующий_тип; Старайтесь придерживаться именования CamelСase -\u0026gt; слова пишутся слитно без пробелов, при этом каждое слово внутри фразы пишется с прописной буквы Повторное использование # Псевдонимы можно легко переиспользовать:\ntype Meters = i32; fn main() { let race_one_distance: Meters = 1600; let race_two_distance: Meters = 3000; } Такой подход особенно полезен при работе с единицами измерения, координатами, идентификаторами и другими значениями, которые иначе были бы просто i32, u64, String и так далее.\nПсевдонимы для сложных типов # Псевдонимы особенно удобны, когда у вас есть длинные или вложенные типы. Например, колбэки (функции обратного вызова в аргументах):\nБез псевдонима # fn run_callback(cb: fn(i32) -\u0026gt; bool) { if cb(10) { println!(\u0026#34;Callback returned true\u0026#34;); } } С псевдонимом # type IntPredicate = fn(i32) -\u0026gt; bool; fn run_callback(cb: IntPredicate) { if cb(10) { println!(\u0026#34;Callback returned true\u0026#34;); } } Теперь это читается гораздо проще.\nГде еще удобно использовать псевдонимы? # Типы ошибок # type Result\u0026lt;T\u0026gt; = std::result::Result\u0026lt;T, MyError\u0026gt;; Функции-обработчики обратного вызова (колбэки/callbacks) # type EventHandler = fn(Event) -\u0026gt; (); При работе с деньгами # type Rubles = f64; fn calculate_total(price: Rubles, discount: Rubles) -\u0026gt; Rubles { price - discount } Идентификаторы в веб-разработке # type UserId = String; fn get_user(id: UserId) { ... } Разработка игр # type HealthPoints = u32; let player_health: HealthPoints = 100; Важные моменты:\nНе злоупотребляйте ими - если тип и так понятен (например, count: i32), псевдоним не нужен Используйте там, где важно подчеркнуть смысл данных Помните, что это просто переименование - компилятор всё равно видит исходный тип Итог # Используйте алиасы типов везде, где базовый тип не раскрывает достаточного смысла или где повторяется сложная сигнатура.\nВ своей практике я почти в каждом языке встречал алиасы для типов и использовал их в работе, это полезная возможность.\n","date":"25 мая 2025","externalUrl":null,"permalink":"/rust/rust-type-alias/","section":"Rust","summary":"","title":"Псевдонимы типов в Rust или как сделать код понятнее","type":"rust"},{"content":" Введение # Константы const — один из базовых инструментов в Rust который как и переменные позволяет сохранять какие-либо данные в памяти. На первый взгляд они могут показаться похожими на переменные. Но есть несколько ключевых отличий, которые делают их полезными в определённых ситуациях. Давайте разберёмся, как их использовать, зачем они нужны и какие подводные камни могут нам встретиться.\nОтличие констант от переменных # Начнём с главного: константы неизменяемы. В отличие от let, которое хоть и может создавать неизменяемые переменные let x = 5 но их всегда можно перезаписать через затенение let x = 6\nС константами такой трюк не пройдёт: значение констант фиксировано в момент компиляции и не может быть изменено.\nДва важных момента:\nОбласть видимости Обычные переменные живут только внутри своего блока {} скоупа Константы можно объявлять на уровне файла. Они будут доступны на протяжении всего кода в файле Когда вычисляется значение Переменные могут \u0026ldquo;принимать\u0026rdquo; значения во время выполнения let user_input = read_line() Константы должны быть известны при компиляции - нельзя присвоить им результат вычислений в рантайме Как объявить константу? # Синтаксис прост:\nconst ИМЯ_КОНСТАНТЫ: Тип = значение; Пример:\nconst MAX_CONNECTIONS: u32 = 100; 🔥 Важные правила:\nСтарайтесь придерживаться именования SCREAMING_SNAKE_CASE -\u0026gt; заглавные буквы и подчёркивание: Так принято в Rust и во многих других языках Это помогает сразу понять, что перед вами константа Нельзя изменять значение константы: MAX_CONNECTIONS = 200 -\u0026gt; получим ошибку компиляции! Нельзя затенять: const FAST_LANG: \u0026amp;str = \u0026#34;Rust\u0026#34;; fn main(){ println(\u0026#34;The {FAST_LANG} language\u0026#34;); let FAST_LANG = \u0026#34;Dart\u0026#34;; // Ошибка! Константы нельзя переопределять } Нужно обязательно указать тип после двоеточия Зачем же так строго? Компилятор Rust следует принципу \u0026ldquo;явное лучше неявного\u0026rdquo;. Если бы константы можно было затенять - код становился бы менее предсказуемым и проще было бы допустить ошибку.\nГде используют константы? # Знаменитые \u0026ldquo;магические числа\u0026rdquo; # Вместо:\nif users.len() \u0026gt; 100 { ... } Лучше написать:\nconst MAX_USERS: usize = 100; if users.len() \u0026gt; MAX_USERS { ... } Глобальные настройки # Например, лимиты или таймауты, коды ошибок:\nconst REQUEST_TIMEOUT: u64 = 5000; // 5 секунд Константные вычисления # Rust позволяет делать простые вычисления в const:\nconst SECONDS_IN_HOUR: u64 = 60 * 60; // Компилятор вычислит это заранее ⚠️ Что нельзя делать с константами # Нельзя брать значение из рантайма например из пользовательского ввода let user_input = get_user_input(); const INPUT: \u0026amp;str = user_input; // Ошибка! Для таких случаев лучше использовать обычные переменные.\nНельзя изменять элементы внутри массива/коллекции Если массив или коллекция объявлены как const их содержимое неизменяемо:\nconst NUMBERS: [i32; 3] = [1, 2, 3]; NUMBERS[0] = 5; // Ошибка! Итоги # Константы в Rust отлично подходят:\nДля неизменяемых значений известных заранее Для замены \u0026ldquo;магических чисел\u0026rdquo; в коде Для глобальных параметров которые не меняются Не забывайте что:\nНельзя изменять или затенять константы Значение должно вычисляться на этапе компиляции SCREAMING_SNAKE_CASE- правило наименования для констант На последок совет: если не можете придумать название константы, подумайте к какой предметной области относится ее значение, а также читайте чужой код. Лучше если это будут исходники больших документированных проектов. Так вы получите насмотренность и зафиксируете для себя правильные наименования.\n","date":"24 мая 2025","externalUrl":null,"permalink":"/rust/rust-const/","section":"Rust","summary":"","title":"Создание и работа с константами в Rust","type":"rust"},{"content":" Блоки и скоупы # Подключим воображение чтобы лучше понять эту тему. Итак, существует переменная со своей силой. Теперь представьте себе область видимости как некую территорию на которой эта переменная имеет силу. В Rust такая территория называется скоупом (Scope). В этой статье мы поговорим о том, что такое скоуп/блоки и как это всё работает.\nБлок (Block) - это область между открытыми и закрытыми фигурными скобками. Каждый раз, когда вы пишете фигурные скобки, например после названия функции:\nfn hello() { 1 2 } вы создаете новый блок 1 . Все, что находится внутри этого блока 2 считается его областью видимости.\nПример:\nСоздадим переменную для хранения значения срабатывания датчика движения от 0 до 100%\nfn main(){ 3 let move_sensor = 10; } Теперь зададим себе вопрос: в какой области эта переменная доступна? Ответом будет: область видимости переменной move_sensor - главная функция 3 (main).\nСтало чуть понятнее? Хорошо тогда движемся дальше.\nВложенные скоупы # Что если мы хотим создать новый скоуп внутри функции main? Мы можем сделать это используя фигурные скобки:\nfn main(){ let move_sensor = 10; { 4 } } Мы создали новый блок - вложенная область видимости или вложенный контекст 4 если сказать иначе.\nВзаимодействие между скоупами # Как я уже упоминал в прошлой статье про затенение, внутренний скоуп может захватывать переменные из внешнего скоупа. Но в обратную сторону это не сработает. Вы не сможете объявить в внутреннем скоупе переменную и использовать её вне его границ.\nfn main(){ { let light_sensor = 20; println!(\u0026#34;Value of light sensor is {light_sensor}\u0026#34;); } 5 println!(\u0026#34;Value of light sensor is {light_sensor}\u0026#34;); // ERROR!!! } Ошибка - cannot find value 'light_sensor' in this scope.\nВсе! переменная датчика света не существует дальше границ блока где она была объявлена 5 .\nВывод # Вложенный скоуп можно представить как некий портал в который вы можете что-то принести, но вынести из него ничего нельзя. ✨\nВажно запомнить, что:\nПеременные существуют внутри скоупа, в котором они объявлены. Внутренний скоуп может захватывать переменные из внешнего скоупа. Вы не можете использовать переменную вне ее скоупа. Теперь вы знаете как правильно \u0026ldquo;готовить\u0026rdquo; области видимости в Rust! 😉\n","date":"16 мая 2025","externalUrl":null,"permalink":"/rust/rust-scope/","section":"Rust","summary":"","title":"Области видимости в Rust","type":"rust"},{"content":" Затенение (variable shadowing) # Затенение переменных - это концепция Rust, которая позволяет повторно объявить переменную. Оригинальная переменная заменяется новой, что позволяет создавать новую переменную с тем же именем, прописывая ее название после ключевого слова let. При этом старая переменная перестает существовать и будет затенена новой переменной.\nДавайте рассмотрим на примере, как это работает.\nПример использования затенения # Один из наиболее распространенных примеров, где это может потребоваться - это последовательная трансформация данных. В нашем примере: с температурных датчиков в системе умного дома.\nlet temperature = \u0026#34;25.5\u0026#34;; // данные с датчика температуры строкового типа Мы хотим преобразовать строковое значение температуры в число с плавающей точкой, чтобы выполнить некоторые вычисления.\nlet temperature = \u0026#34;25.5\u0026#34;; let temperature: f64 = temperature.parse().unwrap(); // преобразуем строку в f64 Теперь мы можем использовать переменную temperature для вычислений.\nЕсли мы захотим изменить единицу измерения температуры с градусов Цельсия на градусы Фаренгейта, мы можем сделать это следующим образом:\nlet temperature = \u0026#34;25.5\u0026#34;; let temperature: f64 = temperature.parse().unwrap(); let temperature = temperature * 9.0 / 5.0 + 32.0; // преобразуем градусы Цельсия в градусы Фаренгейта Таким образом, мы можем повторно использовать имя переменной temperature для хранения разных значений и типов данных.\nИзменяемость переменных # Если мы захотим сделать нашу переменную изменяемой, то просто добавляем ключевое слово mut. Про изменяемость переменных вы можете прочитать в прошлой главе.\nlet temperature = \u0026#34;25.5\u0026#34;; let temperature: f64 = temperature.parse().unwrap(); let mut temperature = temperature * 9.0 / 5.0 + 32.0; temperature = temperature + 1.0; //теперь можем изменить значение переменной Переменная-константа, объявленная при помощи ключевого слова const, не может быть затенена. Компилятор выведет ошибку E0308 - mismatched types. Затенение в рамках области видимости (scope) # Язык Rust позволяет создавать области видимости, которые могут захватывать значения переменных, объявленных до самой области видимости и очищать их из стека, когда покидается область видимости.\nРассмотрим пример:\nlet brightness = \u0026#34;50\u0026#34;; 1 { 2 let brightness: u8 = brightness.parse().unwrap(); println!(\u0026#34;Brightness level is {brightness}%\u0026#34;); let brightness = brightness + 20; 3 println!(\u0026#34;New brightness level is {brightness}%\u0026#34;); 4 } println!(\u0026#34;Original brightness value is {brightness}\u0026#34;); 5 Вывод компилятора будет следующим:\nBrightness level is 50% New brightness level is 70% Original brightness value is 50 Давайте разберём все подробно 🔎\nНа строке 1 мы объявили переменную brightness со строковым значением \u0026ldquo;50\u0026rdquo;. Внутри блока 2 мы объявили новую переменную brightness с типом u8 и числовым значением 50 после преобразования parse. Затем мы увеличили уровень яркости 3 на 20 и вывели новое значение 4 . После выхода из блока значение переменной brightness снова стало \u0026ldquo;50\u0026rdquo;, как и было ранее. 5 Вот так элегантно и эффективно мы преобразовывали и выводили значение температуры, не используя никакие костыли в виде новых переменных.\nНадеюсь, эта статья помогла вам понять концепцию затенения переменных в Rust и избавит вас от придумывания новых имен для переменных наподобии upd_temperature.\nЯ в своей практике напридумывал таких приличное количество.\n","date":"14 мая 2025","externalUrl":null,"permalink":"/rust/rust-shadowing/","section":"Rust","summary":"","title":"Затенение переменных в Rust","type":"rust"},{"content":"Ошибки - неотъемлемая часть жизни любого разработчика. В этой статье мы поговорим о том как получить больше информации о специфических ошибках в Rust.\nКод ошибки и помощь компилятора # Когда компилятор Rust отображает ошибку, он также предоставляет код ошибки. Например может возникнуть такая:\nFor more information about this error, try 'rustc --explain E0425'\nКомпилятор предлагает использовать команду rustc --explain с кодом ошибки для того чтобы увидеть больше информации об этой ошибке.\nrustc - это команда запуска Rust компилятора. Вы можете использовать также команду cargo вместо неё Давайте попробуем запустить эту команду в терминале. Вывод будет таким:\nAn unresolved name was used.\nErroneous code examples:\nsomething_that_doesnt_exist::foo; // error: unresolved name something_that_doesnt_exist::foo\n// or:\ntrait Foo { fn bar() { Self; // error: unresolved name Self } }\n// or:\nlet x = unknown_variable; // error: unresolved name unknown_variable\nPlease verify that the name wasn't misspelled and ensure that the identifier being referred to is valid for the given situation...\nВ результате мы получаем подробную документацию по этой ошибке с примерами и предложениями по исправлению. Удобно, что вся информация доступна в терминале и нам не нужно покидать среду разработки.\nАльтернативные варианты # Хотя поиск ответа через gpt может казаться быстрым решением, важно быть осторожным и не полагаться на нейронки у которых могут быть галлюцинации. Использование rustc + gpt - более надежный вариант.\nОнлайн-ресурсы # Если вы перейдете по ссылке Rust Code Errors то увидите перечень всех кодов возможных ошибок. Кроме того, на сайте вы можете запустить пример кода и увидеть вывод компилятора для конкретной ошибки.\nСоветы для начинающих # Чтобы действительно научиться работать с ошибками, не стоит просто исправлять их и двигаться дальше. Вместо этого, на первых порах, следуйте простому алгоритму:\nАлгоритм решения проблем на первых этапах может быть следующим:\nИсправьте ошибку. Напишите маленькую программу, которая позволит воспроизвести ту же ошибку компилятора. Подумайте еще раз об этой ошибке и попробуйте объяснить себе, почему она произошла и как принято ее решать. Погуглите и поищите помощи на ресурсах и сервисах, если все еще не до конца разобрались. Следуя этому алгоритму, вы не только исправите ошибку, но и получите более глубокое понимание языка.\nВывод # Теперь, когда у вас выскочит ошибка вы знаете, где можно найти помощь не покидая среду разработки. Компилятор Rust - ваш надежный и верный помощник.\nВ последующих статьях еще не раз предстоит столкнуться с ошибками компилятора. Но вы уже будете вооружены хорошим инструментом.\n","date":"12 мая 2025","externalUrl":null,"permalink":"/rust/rust-err/","section":"Rust","summary":"","title":"Разбираемся с ошибками в Rust","type":"rust"},{"content":" Введение # В Rust все переменные по умолчанию иммутабельные (Immutable), то есть они не могут изменяться. Мутабельные переменные, напротив, имеют способность изменяться (Mutable).\nОбещание себе # Давайте рассмотрим пример. Предположим, вы решили вставать в 6 утра каждый день и ложиться в 22:00. Для этого вы создаете две переменные:\nlet get_up = 6; let bedtime = 22; Вы можете вывести значения этих переменных в консоль с помощью println! (см. Работа с println! и интерполяцией строк в Rust)\nprintln!(\u0026#34;Я планирую вставать в {get_up} часов и ложиться спать в {bedtime}\u0026#34;); Все хорошо, программа работает. Однако, через несколько дней вы решаете, что пока рано делать такие резкие изменения и хотите дать себе поблажку на час. Вы пытаетесь изменить значения переменных:\nget_up = 7; bedtime = 23; Ошибка # И снова выводите план в консоль. Но компилятор Rust не дает вам этого сделать! Давайте рассмотрим сообщение об ошибке:\nerror[E0384]: cannot assign twice to immutable variable `get_up` 1 --\u0026gt; src/main.rs:7:1 | 2 | let get_up = 6; 2 | ------ first assignment to `get_up` ... 7 | get_up = 7; 3 | ^^^^^^^^^^ cannot assign twice to immutable variable | help: consider making this binding mutable | 2 | let mut get_up = 6; 4 | +++ Компилятор сообщает, что вы пытаетесь присвоить новое значение неизменяемой переменной 1 . В Rust переменные по умолчанию иммутабельные. Первая строка 2 ошибки указывает на место, где вы впервые присвоили значение переменной, а вторая строка 3 указывает на место, где вы пытаетесь присвоить новое значение.\nИсправление ошибки # Rust предлагает решение: сделать вашу переменную мутабельной, добавив ключевое слово mut после let 4 . Давайте последуем этому совету и изменим код:\nfn main() { let mut get_up = 6; let mut bedtime = 22; println!(\u0026#34;Я планирую вставать в {get_up} часов и ложиться спать в {bedtime}\u0026#34;); get_up = 7; bedtime = 23; println!(\u0026#34;Я планирую вставать в {get_up} часов и ложиться спать в {bedtime}\u0026#34;); } Программа теперь работает успешно, и вывод соответствует ожиданиям. А у вас все еще есть возможность вернуться к изначальному плану 😉\nStandard Output Я планирую вставать в 6 часов и ложиться спать в 22 Я планирую вставать в 7 часов и ложиться спать в 23 Итоги # Rust — язык со строгими правилами. Он требует от разработчика определяться с тем, будут ли его переменные изменяемыми или нет. По умолчанию все переменные объявленные при помощи ключевого слова let являются иммутабельными (неизменяемыми). Чтобы сделать переменную изменяемой, нужно добавить ключевое слово mut после let. Это помогает писать более безопасный и предсказуемый код (создатели языка Rust нас именно в этом стараются убедить).\n","date":"6 мая 2025","externalUrl":null,"permalink":"/rust/rust-var-mut/","section":"Rust","summary":"","title":"Изменяемые и неизменяемые переменные в Rust","type":"rust"},{"content":" Работа с println! и интерполяцией строк в Rust # Макрос println! позволяет напечатать необходимое выражение через \\n.\n\\n - это аналог ввода через Enter.\nКак это работает # Когда мы используем println!, например:\nprintln!(\u0026#34;sensors\u0026#34;); На консоль будет выведено сообщение \u0026ldquo;sensors\u0026rdquo;.\nДобавление динамических значений в строку # Ранее мы добавляли в строку захардкоженные значения. Теперь мы можем добавить динамические значения, используя фигурные скобки {}. Это называется интерполяцией.\nПример:\nlet room_degree = 26; println!(\u0026#34;Temperature in room is {} degrees.\u0026#34;, room_degree); Rust интерполирует значение переменной room_degree в строку.\nИспользование выражений в интерполяции # Значения для интерполяции могут быть также динамическими выражениями, которые будут вычислены автоматически.\nПример:\nlet room_degree = 26; println!(\u0026#34;Temperature in the room is {} degrees.\u0026#34;, room_degree - 2); В этом случае будет выведен текст: \u0026ldquo;Temperature in the room is 24 degrees.\u0026rdquo;\nИменованные аргументы # Начиная с Rust версии 1.58.0, можно использовать именованные аргументы или как называются в других языках - шаблонные литералы, для упрощения чтения и написания кода.\nПример:\nlet room_degree = 26; println!(\u0026#34;Temperature in the room is {room_degree} degrees.\u0026#34;); Ошибки и особенности # Если мы забудем указать круглые скобки, Rust не будет ничего вычислять, а просто выведет строку.\nПример:\nprintln!(\u0026#34;Temperature in the room is room_degree degrees.\u0026#34;); Выведет: \u0026ldquo;Temperature in the room is room_degree degrees.\u0026rdquo;\nТакже важно не допускать ошибку при указании имени переменной при интерполяции - компилятор будет ругаться.\nИспользование нескольких значений # Если необходимо указать несколько значений в строке, просто прописываем их через запятую.\nПример:\nlet living_room_degree = 26; let kitchen_degree = 24; println!(\u0026#34;Temperature in the living room is {}. And temperature in the kitchen is {}\u0026#34;, living_room_degree, kitchen_degree); Либо воспользуйтесь новым синтаксисом:\nprintln!(\u0026#34;Temperature in the living room is {living_room_degree}. And temperature in the kitchen is {kitchen_degree}\u0026#34;); Позиционные аргументы # Rust был бы не растом если бы не имел ещё один способ для интерполяции строк. Такой способ есть это - позиционные аргументы. Мы можем \u0026ldquo;вставлять\u0026rdquo; значения переменных в строку (аналогия со слотами) указывая номер аргумента в фигурных скобках ( {0}, {1} и т.д. ). Это позволяет использовать одни и те же переменные в нескольких местах.\nПример:\nlet living_room_degree = 26; let kitchen_degree = 24; println!(\u0026#34;Temperature in the living room is {0}. And temperature in the kitchen is {1}. But living room is {0} degrees.\u0026#34;, living_room_degree, kitchen_degree); В этом случае будет выведен текст: \u0026ldquo;Temperature in the living room is 26. And temperature in the kitchen is 24. But living room is 26 degrees.\u0026rdquo;\nФорматирование строк с помощью padding и выравнивания # 🦀 rust skill Хотелось бы еще упомянуть о дополнительной возможности для форматирования строк с помощью println!. Вы можете использовать padding и выравнивание, чтобы \u0026ldquo;украсить\u0026rdquo; вывод данных.\nСинтаксис форматирования # Общий синтаксис форматирования выглядит следующим образом:\n{variable:padding alignment minimum.maximum} Разберем составляющие этого синтаксиса:\nvariable: имя переменной, которую вы хотите вывести. padding: символ, который будет использоваться для заполнения пробелов. alignment: выравнивание текста (влево \u0026lt;, вправо \u0026gt;, или по центру ^). minimum: минимальная длина строки. maximum: максимальная длина строки (предваряется точкой). Примеры использования # Выравнивание целого числа по правой стороне с нулями # let num = 123; println!(\u0026#34;{:0\u0026gt;5}\u0026#34;, num); Выведет: 00123\nВыравнивание строки по центру с padding и минимальной длиной # let title = \u0026#34;Rust\u0026#34;; println!(\u0026#34;{:-^10}\u0026#34;, title); Выведет: ----Rust----\nВыравнивание строк влево и вправо с padding # let left_text = \u0026#34;Left\u0026#34;; let right_text = \u0026#34;Right\u0026#34;; println!(\u0026#34;{:.\u0026lt;15}{:.\u0026gt;15}\u0026#34;, left_text, right_text); Выведет: Left.....................Right\nИспользование именованных аргументов с padding и выравниванием # let city1 = \u0026#34;Москва\u0026#34;; let city2 = \u0026#34;Уфа\u0026#34;; println!(\u0026#34;{city1:-\u0026gt;10} \u0026lt;--\u0026gt; {city2:-\u0026lt;10}\u0026#34;, city1 = city1, city2 = city2); Выведет: -----Москва \u0026lt;--\u0026gt; Уфа-----\n","date":"3 мая 2025","externalUrl":null,"permalink":"/rust/rust-str-interpolation/","section":"Rust","summary":"","title":"Работа с println! и интерполяцией строк в Rust","type":"rust"},{"content":"Переменная в программировании представляет собой область памяти с присвоенным именем, предназначенную для хранения данных. В языке Rust объявление переменной осуществляется с помощью ключевого слова let, после которого указывается имя переменной.\nИменование переменных # В Rust принято использовать стиль snake_case для именования переменных. Это означает, что имя переменной пишется в нижнем регистре, а слова разделяются символом подчеркивания (_). Например, my_variable.\nОбъявление переменных # При объявлении переменной вы можете указать ее тип, например, i32, или доверить компилятору вывести тип самостоятельно на основе присвоенного значения. Синтаксис объявления переменной следующий:\nlet имя_переменной: тип = значение; Здесь имя_переменной — имя переменной, тип — ее тип (опционально), а значение — присваиваемое значение.\nПример:\nlet sensors = 20; В этом примере объявляется переменная sensors и ей присваивается значение 20. Компилятор выводит тип переменной как i32, поскольку 20 является целым числом.\nИнтересный момент: Rust не любит, когда вы объявляете переменную, но не используете её. Go кстати тоже не любит. Но в Rust можно перед названием переменной поставить _ и тогда компилятор проигнорирует такую переменную. Пример: let _ignore_me = 42; Присвоение значений # Вы можете легко присвоить значение одной переменной другой переменной. Например:\nlet ultra = 10; let temp = ultra; Теперь temp имеет значение 10.\nКроме того, переменной можно присвоить результат выражения:\nlet ultra = 10; let temp = 5; let sensors = ultra + temp; В этом случае sensors будет иметь значение 15, которое является результатом сложения ultra и temp.\nИзменяемость переменных # По умолчанию переменные в Rust являются неизменяемыми (immutable). Это означает, что после объявления переменной ее значение не может быть изменено. Если вам необходимо изменить значение переменной, вы можете объявить ее как изменяемую (mutable), используя ключевое слово mut:\nlet mut sensors = 20; sensors = 30; // Теперь sensors имеет значение 30 В этом примере мы объявляем sensors как изменяемую переменную и затем изменяем ее значение на 30.\n","date":"3 мая 2025","externalUrl":null,"permalink":"/rust/rust-var/","section":"Rust","summary":"","title":"Переменные в Rust: основы и примеры использования","type":"rust"},{"content":"","date":"26 марта 2025","externalUrl":null,"permalink":"/tags/kotlin/","section":"Tags","summary":"","title":"Kotlin","type":"tags"},{"content":"","date":"26 марта 2025","externalUrl":null,"permalink":"/til/kotlin/","section":"Сегодня я узнал","summary":"","title":"Kotlin","type":"til"},{"content":"На днях вышел очередной релиз Kotlin 2.1.20, который принес массу интересных обновлений. Давайте разберемся, что нового появилось в этой версии.\n1. Основные изменения # 🔧 K2-компилятор и KAPT\nKAPT по умолчанию на K2: Новый плагин kapt по умолчанию – вроде бы мелочь, но для тех, кто возится с аннотациями и препроцессингом, это может быть существенно. Раньше приходилось возиться с кучей конфигураций, теперь обещают больше стабильности.\nОткат на K1: Добавьте в gradle.properties:\nkapt.use.k2=false ⚡ Kotlin/Native: Производительность\nИнлайн-оптимизация (экспериментально):\nИнлайн-оптимизации в Kotlin/Native – вещь, которую оценят только настоящие перфекционисты. Миллисекунды? Да. Принципиально? Абсолютно. Эта оптимизация превосходит стандартный инлайнер LLVM и ускоряет выполнение программ.\nКак включить:\n-Xbinary=preCodegenInlineThreshold=40 Значение 40 — оптимальный порог для баланса между оптимизацией и временем компиляции. Можно экспериментировать с другими значениями (чем выше, тем агрессивнее оптимизация). Результаты: +9.5% к скорости выполнения (по тестам JetBrains).\n🌐 WebAssembly\nКастомные форматтеры по умолчанию в dev-сборках. Дефолтные кастомные форматтеры в Wasm – это уже интересно. Web-разработчики получают немного больше контроля без лишних плясок с бубном. // build.gradle.kts kotlin { wasmJs { // ... compilerOptions { freeCompilerArgs.add(\u0026#34;-Xwasm-debugger-custom-formatters\u0026#34;) } } } DWARF-отладка (для Wasm VM): -Xwasm-generate-dwarf Что даёт: инспекция переменных, пошаговая отладка. 2. Экспериментальные возможности # 🛠 Поддержка Lombok @SuperBuilder и улучшения @Builder\nKotlin Lombok plugin теперь поддерживает аннотацию @SuperBuilder, упрощая создание билдеров для иерархий классов. Раньше при наследовании приходилось вручную описывать логику для суперклассов — теперь это делается автоматически. Что изменилось:\n@SuperBuilder: Автоматически наследует поля родительского класса. Позволяет инициализировать поля суперкласса при создании объекта. @SuperBuilder public class Parent { private String parentField; } @SuperBuilder public class Child extends Parent { private String childField; } // Использование в Kotlin: val child = Child.builder() .parentField(\u0026#34;value\u0026#34;) .childField(\u0026#34;anotherValue\u0026#34;) .build() @Builder на конструкторах: Теперь работает с конструкторами, предлагая гибкие варианты создания объектов. public class User { @Builder public User(String name, int age) { ... } } 📦 Новый DSL для Multiplatform\nС Gradle 8.7 старый Application plugin несовместим с Kotlin Multiplatform. Появился новый экспериментальный DSL с блоком executable {}:\nkotlin { jvm { @OptIn(ExperimentalKotlinGradlePluginApi::class) binaries { executable { mainClass.set(\u0026#34;foo.MainKt\u0026#34;) } // Можно настроить несколько точек входа executable(KotlinCompilation.MAIN_COMPILATION_NAME, \u0026#34;another\u0026#34;) { mainClass.set(\u0026#34;foo.MainAnotherKt\u0026#34;) } } } } Важно: Требуется @OptIn(ExperimentalKotlinGradlePluginApi::class)\n📤 Кастомные публикации в Gradle\nДобавление своих вариантов публикации для JVM и Multiplatform (экспериментально):\nВозможность добавлять кастомные варианты публикации. Звучит скучно? Для кого-то – да. Для того, кто хочет максимально тонко настроить сборку – почти как панацея.\nplugins { kotlin(\u0026#34;jvm\u0026#34;) // или multiplatform } kotlin { @OptIn(ExperimentalKotlinGradlePluginApi::class) publishing { adhocSoftwareComponent { // Конфигурация кастомных вариантов } } } ⏱️ Трекинг времени в stdlib\nНовые классы Clock и Instant (аналоги из kotlinx-datetime):\n@OptIn(ExperimentalTime::class) fun main() { val now = Clock.System.now() val past = Instant.parse(\u0026#34;2023-01-01T00:00:00Z\u0026#34;) println(\u0026#34;Прошло времени: ${now - past}\u0026#34;) } Конвертеры:\n.toJavaInstant() → java.time.Instant .toJSDate() → JS Date (с потерей точности). 3. Kotlin/Wasm: Миграция на Provider API # Раньше:\nthe\u0026lt;NodeJsExtension\u0026gt;().version = \u0026#34;2.0.0\u0026#34; Теперь:\nthe\u0026lt;NodeJsEnvSpec\u0026gt;().version.set(\u0026#34;2.0.0\u0026#34;) Зачем: Ленивые вычисления и улучшенная интеграция с задачами Gradle.\n🚨 Заменили устаревшее\nУстарело Замена wasmJsRun wasmJsBrowserDevelopmentRun wasmJsBrowserRun wasmJsBrowserDevelopmentRun wasmJsNodeRun wasmJsNodeDevelopmentRun wasmJsBrowserWebpack wasmJsBrowserProductionWebpack или wasmJsBrowserDistribution jsRun jsBrowserDevelopmentRun jsBrowserRun jsBrowserDevelopmentRun jsNodeRun jsNodeDevelopmentRun jsBrowserWebpack jsBrowserProductionWebpack или jsBrowserDistribution 4. Стандартная библиотека # Атомарные типы: Для конкурентных операций. UUID: Улучшенная генерация и сериализация. Трекинг времени: Точное измерение интервалов (см. раздел 2). 5. Compose: Отладка и оптимизации # Исходная информация включена по умолчанию для всех платформ. Поддержка default-аргументов в open-функциях: open class MyComponent { @Composable open fun Content(text: String = \u0026#34;default\u0026#34;) { ... } } Важно: Требуется Kotlin 2.1.20+ для корректной работы. Финальные функции могут быть restartable: Убрано ограничение для final override. Удаление ComposableSingletons из публичного API: Устранение утечек в inline-функциях. 6. Gradle # Поддержка версий: 7.6.3 – 8.11 (с предупреждениями в новых версиях). Изолированные проекты (pre-Alpha): Включите через системное свойство. JS/Wasm пока не поддерживаются. Отключение для Multiplatform: kotlin.kmp.isolated-projects.support=disable Кастомные публикации: Экспериментальный API (см. раздел 2). 7. Установка # IntelliJ IDEA 2023.3+ и Android Studio Iguana: Плагин встроен. Обновление: Пропишите в build.gradle: plugins { kotlin(\u0026#34;jvm\u0026#34;) version \u0026#34;2.1.20\u0026#34; } 8. Breaking Changes # Устарело: withJava() → Java-сорс-сеты создаются автоматически. kotlin-android-extensions → Полная блокировка (ошибка конфигурации). Удалено: Свойство kotlin.incremental.classpath.snapshot.enabled. Итоги # Kotlin 2.1.20 фокусируется на производительности (Native, K2), упрощении конфигурации (Multiplatform DSL) и отладке (Wasm DWARF, Compose). Экспериментальные фичи вроде @SuperBuilder и нового DSL дают заглянуть в будущее языка.\n🔗 https://github.com/JetBrains/kotlin/releases/tag/v2.1.20\nВнимание: Все экспериментальные фичи требуют явного @OptIn!\nP.S. Если заметили баги с K2 — не паникуйте, всегда можно откатиться на K1 через gradle.properties.\nТестируйте и программируйте в удовольствие! 🚀\n","date":"26 марта 2025","externalUrl":null,"permalink":"/til/kotlin/kotlin2_1_20/","section":"Сегодня я узнал","summary":"","title":"Kotlin 2.1.20: Новые возможности и улучшения для разработчиков","type":"til"},{"content":" Введение # Hash Map — это коллекция пар \u0026ldquo;ключ-значение\u0026rdquo;, которая позволяет быстро находить значения по ключу. Представьте ее как справочник, где вы моментально получаете нужную информацию.\nСоздание Hash Map # HashMap::new() используется чтобы создать новый пустой HashMap.\nИмпортируем модуль:\nuse std::collections::HashMap; Указание типов ключей и значений # Требуется явно указать типы используемые для ключа и значения:\nuse std::collections::HashMap; fn main() { let smart_home_devices: HashMap\u0026lt;String, String\u0026gt; = HashMap::new(); println!(\u0026#34;Пустой hash map устройств с указанием типов String: {:?}\u0026#34;, smart_home_devices); } Вывод:\nПустой hash map устройств с указанием типов String: {} Добавление пар \u0026ldquo;ключ-значение\u0026rdquo; с помощью insert # После создания HashMap вы можете наполнить его данными с помощью метода insert. Каждый вызов принимает ключ и значение:\nlet mut smart_home_devices: HashMap\u0026lt;String, String\u0026gt; = HashMap::new(); // Добавляем устройства и их статус smart_home_devices.insert(\u0026#34;гостиная_свет\u0026#34;.to_string(), \u0026#34;включен\u0026#34;.to_string()); smart_home_devices.insert(\u0026#34;спальня_термостат\u0026#34;.to_string(), \u0026#34;22°C\u0026#34;.to_string()); // Обновляем статус устройства smart_home_devices.insert(\u0026#34;гостиная_свет\u0026#34;.to_string(), \u0026#34;выключен\u0026#34;.to_string()); println!(\u0026#34;Статус устройств: {:?}\u0026#34;, smart_home_devices); Вывод:\nСтатус устройств: {\u0026#34;гостиная_свет\u0026#34;: \u0026#34;выключен\u0026#34;, \u0026#34;спальня_термостат\u0026#34;: \u0026#34;22°C\u0026#34;} Важно! чтобы сделать коллекцию изменяемой необходимо добавить mut\nУсловное добавление с entry и or_insert # Иногда вам нужно добавить элемент только в том случае, если ключ еще не существует. API entry в Rust позволяет проверить наличие ключа и добавить значение только при его отсутствии:\nlet mut smart_home_devices: HashMap\u0026lt;String, String\u0026gt; = HashMap::new(); // Добавляем устройства, избегая перезаписи smart_home_devices.entry(\u0026#34;кухня_холодильник\u0026#34;.to_string()) .or_insert(\u0026#34;работает\u0026#34;.to_string()); // Попытка добавить статус для существующего устройства не изменит его smart_home_devices.entry(\u0026#34;кухня_холодильник\u0026#34;.to_string()) .or_insert(\u0026#34;неисправен\u0026#34;.to_string()); // Добавим новое устройство smart_home_devices.entry(\u0026#34;гараж_ворота\u0026#34;.to_string()) .or_insert(\u0026#34;закрыты\u0026#34;.to_string()); println!(\u0026#34;Статус устройств: {:?}\u0026#34;, smart_home_devices); Доступ к значениям в Hash Map # Метод get является основным способом извлечения значений из hash map в Rust:\nuse std::collections::HashMap; fn main() { let mut smart_home_devices = HashMap::new(); // Добавляем устройства smart_home_devices.insert(\u0026#34;гостиная_свет\u0026#34;.to_string(), \u0026#34;включен\u0026#34;.to_string()); smart_home_devices.insert(\u0026#34;спальня_термостат\u0026#34;.to_string(), \u0026#34;22°C\u0026#34;.to_string()); // Получение статуса существующего устройства let living_room_light = smart_home_devices.get(\u0026#34;гостиная_свет\u0026#34;); println!(\u0026#34;Статус света в гостиной: {:?}\u0026#34;, living_room_light); // Получение статуса несуществующего устройства let garage_door = smart_home_devices.get(\u0026#34;гараж_ворота\u0026#34;); println!(\u0026#34;Статус ворот гаража: {:?}\u0026#34;, garage_door); } Вывод:\nСтатус света в гостиной: Some(\u0026#34;включен\u0026#34;) Статус ворот гаража: None Обработка отсутствующих ключей с помощью сопоставления с образцом # use std::collections::HashMap; fn main() { let mut smart_home_devices = HashMap::new(); smart_home_devices.insert(\u0026#34;гостиная_свет\u0026#34;.to_string(), \u0026#34;включен\u0026#34;.to_string()); // Проверка статуса света match smart_home_devices.get(\u0026#34;гостиная_свет\u0026#34;) { Some(status) =\u0026gt; println!(\u0026#34;Статус света: {}\u0026#34;, status), None =\u0026gt; println!(\u0026#34;Статус света не найден\u0026#34;), } // Проверка статуса несуществующего устройства match smart_home_devices.get(\u0026#34;кухня_кондиционер\u0026#34;) { Some(status) =\u0026gt; println!(\u0026#34;Статус кондиционера: {}\u0026#34;, status), None =\u0026gt; println!(\u0026#34;Статус кондиционера: не найден\u0026#34;), } } Вывод:\nСтатус света: включен Статус кондиционера: не найден Предоставление значений по умолчанию с unwrap_or # use std::collections::HashMap; fn main() { let mut smart_home_devices = HashMap::new(); smart_home_devices.insert(\u0026#34;гостиная_свет\u0026#34;.to_string(), \u0026#34;включен\u0026#34;.to_string()); let default_light_status = \u0026#34;выключен\u0026#34;.to_string(); let default_garage_status = \u0026#34;закрыты\u0026#34;.to_string(); // Получение статуса с значением по умолчанию let living_room_light = smart_home_devices.get(\u0026#34;гостиная_свет\u0026#34;).unwrap_or(\u0026amp;default_light_status); let garage_door = smart_home_devices.get(\u0026#34;гараж_ворота\u0026#34;).unwrap_or(\u0026amp;default_garage_status); println!(\u0026#34;Статус света в гостиной: {}\u0026#34;, living_room_light); println!(\u0026#34;Статус ворот гаража: {}\u0026#34;, garage_door); } Вывод:\nСтатус света в гостиной: включен Статус ворот гаража: закрыты Условное обновление с entry и and_modify # use std::collections::HashMap; fn main() { let mut smart_home_devices = HashMap::new(); smart_home_devices.insert(\u0026#34;гостиная_свет\u0026#34;.to_string(), \u0026#34;включен\u0026#34;.to_string()); // Обновление статуса существующего устройства smart_home_devices.entry(\u0026#34;гостиная_свет\u0026#34;.to_string()) .and_modify(|status| *status = \u0026#34;выключен\u0026#34;.to_string()); // Попытка обновить несуществующее устройство (ничего не произойдет) smart_home_devices.entry(\u0026#34;кухня_кондиционер\u0026#34;.to_string()) .and_modify(|status| *status = \u0026#34;работает\u0026#34;.to_string()); println!(\u0026#34;Обновленные статусы устройств: {:?}\u0026#34;, smart_home_devices); } Вывод:\nОбновленные статусы устройств: {\u0026#34;гостиная_свет\u0026#34;: \u0026#34;выключен\u0026#34;} Практический пример # Давайте рассмотрим практический пример, связанный с управлением умным домом. Предположим, у нас есть несколько устройств в доме и нам нужно отслеживать их текущий статус. Например, мы хотим знать, включен ли свет в гостиной. Какая температура установлена на термостате в спальне и работает ли кондиционер на кухне. Если статус какого-то устройства неизвестен, мы хотим выводить соответствующее сообщение.\nУ нас есть список всех устройств, которые могут быть в доме, включая те, статус которых пока неизвестен. Для каждого устройства нужно вывести его название и текущий статус use std::collections::HashMap; fn main() { // Создаем HashMap для хранения статусов устройств let mut home_device_status: HashMap\u0026lt;String, String\u0026gt; = HashMap::new(); // Добавляем статусы для некоторых устройств home_device_status.insert(\u0026#34;гостиная_свет\u0026#34;.to_string(), \u0026#34;включен\u0026#34;.to_string()); home_device_status.insert(\u0026#34;спальня_термостат\u0026#34;.to_string(), \u0026#34;22°C\u0026#34;.to_string()); // Список всех устройств, которые нас интересуют let devices = vec![\u0026#34;гостиная_свет\u0026#34;, \u0026#34;спальня_термостат\u0026#34;, \u0026#34;кухня_кондиционер\u0026#34;]; // Статус по умолчанию, если устройство не найдено let default_device_status = \u0026#34;статус неизвестен\u0026#34;.to_string(); // Перебираем все устройства и выводим их статус for device in devices { let status = home_device_status.get(device).unwrap_or(\u0026amp;default_device_status); println!(\u0026#34;{}: {}\u0026#34;, device, status); } Вывод:\nгостиная_свет: включен спальня_термостат: 22°C кухня_кондиционер: статус неизвестен Почему HashMap так полезен? # 🚀 Быстрый доступ к данным 🔄 Простота обновления и проверки (относительно для языка rust конечно) 🛡️ Безопасность типизации Заключение # HashMap предоставляет мощный способ работы с парами \u0026ldquo;ключ-значение\u0026rdquo;. Они идеально подходят для задач, требующих быстрого поиска и эффективного управления данными. Используйте HashMap, когда:\nВы хотите связать произвольный ключ с произвольным значением. Вам нужен кэш. Вам нужна мапа без дополнительных функций. Официальная документация по hash map\n","date":"5 марта 2025","externalUrl":null,"permalink":"/rust/rust-hashmap/","section":"Rust","summary":"","title":"Hash Map в Rust","type":"rust"},{"content":"","date":"14 февраля 2025","externalUrl":null,"permalink":"/tags/postgresql/","section":"Tags","summary":"","title":"PostgreSQL","type":"tags"},{"content":"","date":"14 февраля 2025","externalUrl":null,"permalink":"/til/postgresql/","section":"Сегодня я узнал","summary":"","title":"PostreSQL","type":"til"},{"content":" Вступление # Работа с массивами в PostgreSQL — это мощный инструмент, который позволяет хранить и обрабатывать наборы данных прямо в базе. Но как правильно посчитать количество элементов в массиве? Разберём два основных способа, которые помогут вам справиться с этой задачей.\n1️⃣ Функция array_length # Функция array_length позволяет узнать длину массива по определённой размерности. Это особенно полезно, если вы работаете с многомерными массивами.\nПример использования:\nselect array_length(array[1, 2, 3], 1); Результат:\n3\nЗдесь мы передаём массив [1, 2, 3] и указываем размерность 1 (поскольку массив одномерный). Функция возвращает количество элементов в массиве.\nДля многомерных массивов:\nselect array_length(array[[1, 2], [3, 4]], 2); Результат:\n2\nВ этом примере мы работаем с двумерным массивом. Указываем размерность 2, чтобы узнать количество элементов во втором измерении (количество столбцов).\n2️⃣ Функция cardinality # Если вам нужно посчитать общее количество элементов в массиве, независимо от его размерности, используйте функцию cardinality. Она подходит как для одномерных, так и для многомерных массивов.\nПример использования:\nselect cardinality(array[1, 2, 3]); Результат:\n3\nЗдесь функция возвращает общее количество элементов в массиве.\nДля многомерных массивов:\nselect cardinality(array[[1, 2], [3, 4]]); Результат:\n4\nВ этом случае cardinality считает все элементы массива, независимо от их вложенности.\nКакой метод выбрать? 🤔 # array_length — используйте, если вам нужно узнать длину массива по определённой размерности. Это особенно полезно для многомерных массивов. cardinality — подходит, если вам нужно общее количество элементов в массиве, независимо от его структуры. Полезные ссылки 🔗 # 📚 Официальная документация PostgreSQL по работе с массивами: https://www.postgresql.org/docs/current/functions-array.html 🧩 Практика без установки PostgreSQL: https://pgplayground.com/ ","date":"14 февраля 2025","externalUrl":null,"permalink":"/til/postgresql/array-len/","section":"Сегодня я узнал","summary":"","title":"Как посчитать количество элементов в массиве в PostgreSQL?","type":"til"},{"content":"","date":"13 февраля 2025","externalUrl":null,"permalink":"/til/tools/","section":"Сегодня я узнал","summary":"","title":"Tools","type":"til"},{"content":"","date":"13 февраля 2025","externalUrl":null,"permalink":"/tags/uml/","section":"Tags","summary":"","title":"UML","type":"tags"},{"content":"","date":"13 февраля 2025","externalUrl":null,"permalink":"/tags/%D0%B2%D0%B8%D0%B7%D1%83%D0%B0%D0%BB%D0%B8%D0%B7%D0%B0%D1%86%D0%B8%D1%8F/","section":"Tags","summary":"","title":"Визуализация","type":"tags"},{"content":"","date":"13 февраля 2025","externalUrl":null,"permalink":"/tags/%D0%B4%D0%B8%D0%B0%D0%B3%D1%80%D0%B0%D0%BC%D0%BC%D1%8B/","section":"Tags","summary":"","title":"Диаграммы","type":"tags"},{"content":"Вы наверняка слышали или даже использовали Mermaid, PlantUML или Graphviz для создания диаграмм. Но сегодня я хочу познакомить вас с D2 — ещё одним крутым скриптовым языком, который превращает текст в диаграммы! 🚀\nD2 разработан компанией Terrastruct и предлагает множество фишек:\n🎨 Поддержка тем, стилизаций и анимации 📝 Встроенная поддержка LaTeX и Markdown, переменных 💻 Подсветка кода для снипетов 🖼 Работа с внешними шрифтами и картинками 🔧 CLI и расширения для популярных IDE и многое другое Если вы любите визуализировать данные или архитектуру, создавать uml диаграммы D2 — это must-try! 🚀\n👉 Подробнее: https://d2lang.com/ 👉 D2 Playground (https://play.d2lang.com/) ","date":"13 февраля 2025","externalUrl":null,"permalink":"/til/tools/d2/","section":"Сегодня я узнал","summary":"","title":"Создавай диаграммы c языком \"D2\"","type":"til"},{"content":" Зачем нужны паттерны проектирования? # Паттерны проектирования — это проверенные временем решения типичных задач, которые возникают при разработке программного обеспечения. Они не являются строгими правилами, а скорее рекомендациями, которые помогают структурировать код, делая его более читаемым, поддерживаемым и расширяемым.\nМожно писать код и без паттернов, но вы скажете себе тысячу раз спасибо если научитесь их применять. Паттерны помогают:\nУпростить поддержку кода: Чёткая структура делает код понятным для других разработчиков. Ускорить разработку: Не нужно изобретать велосипед — можно использовать готовые решения. Сделать код гибким: Паттерны позволяют легко вносить изменения, не ломая существующую логику. Один из самых известных источников по паттернам — книга \u0026ldquo;Банды четырёх\u0026rdquo; (Design Patterns: Elements of Reusable Object-Oriented Software). Однако когда я последний раз открывал её, примеры были приведены на C++ и Smalltalk. Согласитесь, что Smalltalk не самый актуальный язык на сегодня. Поэтому советую искать актуальные примеры на современных языках, таких как Rust, Dart, Python или Go, чтобы лучше понимать, как применять паттерны в реальных проектах.\nПаттерн \u0026ldquo;Строитель\u0026rdquo; в Rust # Паттерн \u0026ldquo;Строитель\u0026rdquo; (Builder) — это порождающий шаблон проектирования, который позволяет пошагово создавать сложные объекты. Он особенно полезен, когда объект имеет множество параметров, часть из которых может быть необязательной. В Rust, где строгая типизация и безопасность памяти являются ключевыми принципами, этот паттерн помогает писать чистый и поддерживаемый код.\nПример из жизни: заказ бургера # Представьте, что вы заказываете бургер в маке (или как говорят в профессиональных кругах \u0026ldquo;макич\u0026rdquo;). Вы можете выбрать:\nБулочку (обязательно) Котлету (обязательно) Сыр (опционально) Салат (опционально) Соусы (опционально) Паттерн \u0026ldquo;Строитель\u0026rdquo; идеально подходит для моделирования такого сценария.\nРеализация на Rust # В Rust паттерн \u0026ldquo;Строитель\u0026rdquo; можно реализовать без создания отдельной структуры для строителя. Вместо этого мы добавляем методы конфигурации прямо к основной структуре.\n#[derive(Debug)] struct Burger { bun: String, patty: String, cheese: Option\u0026lt;String\u0026gt;, salad: bool, sauce: Option\u0026lt;String\u0026gt;, } impl Burger { // Создаем новую реализацию с обязательными параметрами fn new(bun: String, patty: String) -\u0026gt; Self { Burger { bun, patty, cheese: None, salad: false, sauce: None, } } // Добавляем сыр fn add_cheese(mut self, cheese: String) -\u0026gt; Self { self.cheese = Some(cheese); self } // Добавляем салат fn add_salad(mut self) -\u0026gt; Self { self.salad = true; self } // Добавляем соус fn add_sauce(mut self, sauce: String) -\u0026gt; Self { self.sauce = Some(sauce); self } // Валидация и финализация объекта fn build(self) -\u0026gt; Result\u0026lt;Self, String\u0026gt; { if self.bun.is_empty() || self.patty.is_empty() { return Err(\u0026#34;Булочка и котлета обязательны!\u0026#34;.to_string()); } Ok(self) } } fn main() { let burger = Burger::new(\u0026#34;Булочка с кунжутом\u0026#34;.to_string(), \u0026#34;Котлета из говядины\u0026#34;.to_string()) .add_cheese(\u0026#34;Чеддер\u0026#34;.to_string()) .add_salad() .add_sauce(\u0026#34;Барбекю\u0026#34;.to_string()) .build(); match burger { Ok(burger) =\u0026gt; println!(\u0026#34;Ваш бургер: {:?}\u0026#34;, burger), Err(e) =\u0026gt; println!(\u0026#34;Ошибка: {}\u0026#34;, e), } } Как это работает? # Методы конфигурации: Каждый метод (add_cheese, add_salad, add_sauce) возвращает self, что позволяет использовать цепочку вызовов. Валидация в build: Метод build проверяет, что обязательные поля (bun и patty) заполнены. Если всё в порядке, он возвращает Ok(Self), иначе — ошибку. Иммутабельность: Каждый метод создает новый экземпляр структуры с обновленными значениями, что соответствует философии Rust. Преимущества паттерна \u0026ldquo;Строитель\u0026rdquo; # Гибкость: Вы можете добавлять только те параметры, которые нужны, оставляя остальные по умолчанию. Валидация: Метод build гарантирует, что объект будет создан только с корректными данными. Читаемость: Цепочка вызовов методов делает код интуитивно понятным. Подход похож на лего: Мы конструируем объект из кубиков. Когда стоит использовать? # Когда объект имеет много параметров, часть из которых необязательна. Когда важно обеспечить валидацию данных перед созданием объекта. Когда вы хотите сделать код гибким и легко расширяемым. Заключение # Паттерн \u0026ldquo;Строитель\u0026rdquo; (Builder) — это ваш помощник в проектировании объектов. Конечно это не волшебная таблетка, это всего лишь ваш инструмент. В Rust его можно реализовать как с использованием отдельной структуры, так и без неё. Выбор зависит от сложности задачи и ваших предпочтений.\nПаттерны проектирования, такие как \u0026ldquo;Строитель\u0026rdquo; (Builder), помогают писать код, который легче поддерживать и расширять. И хотя можно обойтись и без них, их использование делает разработку более предсказуемой и менее стрессовой. Попробуйте этот паттерн в своих проектах, чтобы убедиться в его эффективности! 🚀\n","date":"9 февраля 2025","externalUrl":null,"permalink":"/rust/rust-builder/","section":"Rust","summary":"","title":"Паттерн \"Строитель\" в Rust","type":"rust"},{"content":"","date":"8 февраля 2025","externalUrl":null,"permalink":"/categories/about-all/","section":"Categories","summary":"","title":"About All","type":"categories"},{"content":"В разработке приложений взаимодействующих с telegram bot api часто возникает необходимость отправки нескольких файлов одновременно. (Я использую этот механизм для отладки иногда)\nTelegram API предоставляет для этого специальный метод sendMediaGroup, который позволяет отправлять до 10 файлов в одном сообщении. В этой статье разберемся, как использовать этот метод с помощью различных инструментов.\nПреимущества использования sendMediaGroup # Все файлы группируются в одно сообщение Уменьшается количество запросов к API Возможность добавления подписей к каждому файлу Поддержка различных типов медиа (фото, документы, видео) Подготовка тестовых файлов # Для демонстрации работы метода создадим два тестовых файла:\necho \u0026#34;Test content 1\u0026#34; \u0026gt; test1.txt echo \u0026#34;Test content 2\u0026#34; \u0026gt; test2.txt Отправка через cURL # Самый простой способ протестировать API – использовать cURL. Вот полный пример команды:\ncurl -X POST https://api.telegram.org/bot\u0026lt;ТВОЙ_TOKEN\u0026gt;/sendMediaGroup \\ -F chat_id=\u0026#34;ТВОЙ_CHAT_ID\u0026#34; \\ -F \u0026#34;media=[{\\\u0026#34;type\\\u0026#34;:\\\u0026#34;document\\\u0026#34;,\\\u0026#34;media\\\u0026#34;:\\\u0026#34;attach://doc1\\\u0026#34;,\\\u0026#34;caption\\\u0026#34;:\\\u0026#34;Test files 1\\\u0026#34;,\\\u0026#34;parse_mode\\\u0026#34;:\\\u0026#34;HTML\\\u0026#34;},{\\\u0026#34;type\\\u0026#34;:\\\u0026#34;document\\\u0026#34;,\\\u0026#34;media\\\u0026#34;:\\\u0026#34;attach://doc2\\\u0026#34;,\\\u0026#34;caption\\\u0026#34;:\\\u0026#34;Test files 2\\\u0026#34;}]\u0026#34; \\ -F doc1=@\u0026#34;test1.txt\u0026#34; \\ -F doc2=@\u0026#34;test2.txt\u0026#34; Разберем параметры запроса:\n-X POST - указывает метод запроса -F - добавляет form-data поле media - JSON-массив с описанием медиафайлов attach://doc1 - указывает на привязку к form-data полю doc1 Реализация на Dart # Для тех, кто разрабатывает на Dart/Flutter, вот пример реализации той же функциональности:\nimport \u0026#39;package:http/http.dart\u0026#39; as http; import \u0026#39;package:http_parser/http_parser.dart\u0026#39;; Future\u0026lt;void\u0026gt; sendMediaGroup() async { final token = \u0026#39;ТВОЙ_TOKEN\u0026#39;; final chatId = \u0026#39;ТВОЙ_CHAT_ID\u0026#39;; final url = Uri.parse(\u0026#39;https://api.telegram.org/bot$token/sendMediaGroup\u0026#39;); final request = http.MultipartRequest(\u0026#39;POST\u0026#39;, url) ..fields[\u0026#39;chat_id\u0026#39;] = chatId ..fields[\u0026#39;media\u0026#39;] = \u0026#39;\u0026#39;\u0026#39; [ {\u0026#34;type\u0026#34;: \u0026#34;document\u0026#34;, \u0026#34;media\u0026#34;: \u0026#34;attach://doc1\u0026#34;, \u0026#34;caption\u0026#34;: \u0026#34;Test files 1\u0026#34;}, {\u0026#34;type\u0026#34;: \u0026#34;document\u0026#34;, \u0026#34;media\u0026#34;: \u0026#34;attach://doc2\u0026#34;, \u0026#34;caption\u0026#34;: \u0026#34;Test files 2\u0026#34;} ] \u0026#39;\u0026#39;\u0026#39; ..files.add(await http.MultipartFile.fromPath(\u0026#39;doc1\u0026#39;, \u0026#39;test1.txt\u0026#39;)) ..files.add(await http.MultipartFile.fromPath(\u0026#39;doc2\u0026#39;, \u0026#39;test2.txt\u0026#39;)); final response = await request.send(); print(await response.stream.bytesToString()); } Важные замечания # Максимальное количество файлов в одной группе - 10 Все файлы должны быть одного типа (например, все документы или все фотографии) Размер каждого файла не должен превышать лимиты Telegram Поддерживаются следующие типы медиа: photo, video, audio, document Заключение # Метод sendMediaGroup - мощный инструмент для отправки нескольких файлов в одном сообщении. Он особенно полезен при разработке ботов, где требуется отправка связанных файлов или создание альбомов медиа-контента.\nСпасибо что читаете! 👾\nПолезные ссылки # Официальная документация Telegram Bot API Limits in Telegram Bot API Документация как создать и работать с чат-ботом Telegram ","date":"8 февраля 2025","externalUrl":null,"permalink":"/about-all/telegram-muliple-files/","section":"Посты обо всем","summary":"","title":"Как отправить несколько файлов одним сообщением через Telegram Bot API","type":"about-all"},{"content":" Введение. Что такое структура # Структура (или struct) в Rust — это пользовательский тип данных, который позволяет группировать связанные данные вместе. Структуры похожи на классы в других языках программирования, но в Rust (как говорит документация) они более гибкие и мощные, так как язык предоставляет различные возможности для работы с ними. Кто бы сомневался 😁 При этом видов структур существует несколько.\nВ этой главе остановлюсь на простой классической C структуре.\nПервый шаг. Проектирование структуры # Прежде чем писать код, важно спроектировать и описать, что мы хотим получить. Давайте возьмем нашу любимую тему по системам для управления умным домом. В нашей системе есть хаб (центральное устройство) и несколько сенсоров, которые подключены к этому хабу.\nДиаграмма классов # 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\" --\u003e \"n\" Sensor : хаб хранит сенсоры Описание структур и полей: # HUB:\nname: String — имя хаба. id: String — уникальный идентификатор хаба. location: String — местоположение хаба. sensors: Vec[Sensor] — список сенсоров, подключенных к хабу. Sensor:\nname: String — имя сенсора. id: String — уникальный идентификатор сенсора. type: String — тип сенсора (например, \u0026ldquo;температура\u0026rdquo;, \u0026ldquo;влажность\u0026rdquo; и т.д.). value: f32 — текущее значение, измеренное сенсором. unit: String — единица измерения (например, \u0026ldquo;°C\u0026rdquo;, \u0026ldquo;%\u0026rdquo; и т.д.). isActive: bool — статус сенсора (активен/неактивен). Шаг 2. Создание структур в Rust # Теперь, когда мы спроектировали наши структуры, давайте перейдем к их реализации в Rust.\nОпределение структур # // // Игнорируем предупреждения о неиспользуемом коде #![allow(dead_code)] #[derive(Debug)] struct Hub { name: String, id: String, location: String, sensors: Vec\u0026lt;Sensor\u0026gt;, } #[derive(Debug)] struct Sensor { name: String, id: String, sensor_type: String, value: f32, unit: String, is_active: bool, } Здесь мы определили две структуры: Hub и Sensor.\nОбратите внимание на использование атрибута #[derive(Debug)], который позволяет нам выводить содержимое структур с помощью макроса println!.\nСинтаксис довольно простой: struct Имя_структуры далее в фигурных скобках через запятую указываются поля.\nС типом String должны быть многие знакомы, это просто UTF-8 строка. Но rust был бы не растом если бы было так просто, но об этом в другой раз. Кому интересно можно прочитать про тип str - https://doc.rust-lang.org/std/primitive.str.html\nVec - это массив который хранит сенсоры (тип объекта который он хранит указывается в угловых скобках). Мощный тип Vec с множеством методов, дартовская библиотека collection позавидует.\nf32 - это обычный float. Подробнее в доке - https://doc.rust-lang.org/std/primitive.f32.html\nbool - это логический тип boolean (true/false) - https://doc.rust-lang.org/std/primitive.bool.html\nC описанием полей закончили. Переходим к тому как с этим всем работать.\nСоздание экземпляров структур # Теперь создадим экземпляр структуры Hub\n// код выше остается таким же ... fn main() { let hub = Hub { name: \u0026#34;Main Hub\u0026#34;.to_string(), id: String::from(\u0026#34;HUB001\u0026#34;), location: \u0026#34;Room 101\u0026#34;.to_string(), sensors: Vec::new(), // Инициализация пустого списка сенсоров }; // Выводим информацию о Hub и его сенсорах println!(\u0026#34;{:#?}\u0026#34;, hub); } При создании экземпляра структуры можно не указывать тип, rust возьмет его вывод на себя, но это не всегда работает.\nЯ специально создал строку несколькими доступными способами. Если задать строку как \u0026ldquo;Room 101\u0026rdquo; это не будет являться строкой типа String, это будет строка типа str - строковый срез. Частая ошибка начинающих путать типы строк, но со временем привыкаешь.\nВывод:\nHub { name: \u0026#34;Main Hub\u0026#34;, id: \u0026#34;HUB001\u0026#34;, location: \u0026#34;Room 101\u0026#34;, sensors: [], } Шаг 3. Сенсоры # Настало время добавить сенсоры.\nfn main() { // Создаем сенсоры let sensor1 = Sensor { name: \u0026#34;Temperature Sensor\u0026#34;.to_string(), id: \u0026#34;TEMP001\u0026#34;.to_string(), sensor_type: \u0026#34;Temperature\u0026#34;.to_string(), value: 25.3, unit: \u0026#34;°C\u0026#34;.to_string(), is_active: true, }; let sensor2 = Sensor { name: \u0026#34;Humidity Sensor\u0026#34;.to_string(), id: \u0026#34;HUM001\u0026#34;.to_string(), sensor_type: \u0026#34;Humidity\u0026#34;.to_string(), value: 60.5, unit: \u0026#34;%\u0026#34;.to_string(), is_active: true, }; // Создаем хаб и добавляем сенсоры let hub = Hub { name: \u0026#34;Main Hub\u0026#34;.to_string(), id: String::from(\u0026#34;HUB001\u0026#34;), location: \u0026#34;Room 101\u0026#34;.to_string(), sensors: vec![sensor1, sensor2], }; // Выводим информацию о хабе и его сенсорах println!(\u0026#34;{:#?}\u0026#34;, hub); } Новые сенсоры замечательно расположились над хабом, а затем нехитрым образом были добавлены к нему.\nВ Rust используется макрос vec! для создания массива.\nС макросами мы уже немного знакомы: макрос println! - вывод на консоль.\nВ квадратных скобках указываются значения массива. Есть вторая форма этого макроса: vec![1; 3] создаст массив из 3-x единиц [1, 1, 1]\nПодробнее про макрос vec! - https://doc.rust-lang.org/std/macro.vec.html\nВывод программы:\nHub { name: \u0026#34;Main Hub\u0026#34;, id: \u0026#34;HUB001\u0026#34;, location: \u0026#34;Room 101\u0026#34;, sensors: [ Sensor { name: \u0026#34;Temperature Sensor\u0026#34;, id: \u0026#34;TEMP001\u0026#34;, sensor_type: \u0026#34;Temperature\u0026#34;, value: 25.3, unit: \u0026#34;°C\u0026#34;, is_active: true, }, Sensor { name: \u0026#34;Humidity Sensor\u0026#34;, id: \u0026#34;HUM001\u0026#34;, sensor_type: \u0026#34;Humidity\u0026#34;, value: 60.5, unit: \u0026#34;%\u0026#34;, is_active: true, }, ], } Заключение # Надеюсь, эта статья была полезной и вы узнали что-то новое о работе с языком Rust а также его структурами. Увидимся и в следующей заметке мы рассмотрим, как добавлять методы к структурам.\n","date":"4 февраля 2025","externalUrl":null,"permalink":"/rust/rust-struct/","section":"Rust","summary":"","title":"Создание и использование структур в Rust: Практическое руководство","type":"rust"},{"content":" Разные подходы для решения одной задачи # Введение: # В программировании приняты несколько парадигм. Cегодня остановлюсь и рассмотрю императивный и функциональные стили.\nДавайте напишем код который выполняет одинаковую задачу, но использует при этом различные подходы. Но до этого краткая справка по стилям.\nИмперативное программирование связано с идеей когда мы описываем операции (инструкции) как действия в обычной жизни. Последовательность неких действий, манипуляций. Есть некое состояние приложения и последовательность операций изменяет это состояние.\nФункциональное программирование плотно интегрировано в современные языки: будь то js, ruby, java, dart и др. Map, reduce, filter наверняка уже где-то встречались 😉\nФункциональный подход, это когда функция возвращает другую функцию. Вот такое краткое определение 😆 которое конечно не отражает всего многообразия этого царства. Но я не хочу тратить ваше время, а поэтому поехали писать код) Если хотите углубиться в функциональную тему, то вот замечательная книга: \u0026ldquo;Грокаем функциональное мышление\u0026rdquo; Эрика Норманда Приложение: # Давайте представим что нам нужно написать приложение для умного дома которое фильтрует и обрабатывает данные от сенсоров.\nОпределим структуру для данных от датчиков # Пусть это будет температурный датчик DS18B20 с тремя ногами и работющий по протоколу 1-wire. Это не так важно для нашего примера, но зато позволяет проникнуть в мир умного дома и немного оживляет повествование 😁\n#[derive(Debug)] struct Sensor { id: String, temperature: f32, } Определили структуру для сенсора, переходим к основному коду.\nПримечание: derive(Debug) trait помогает при отладке кода и позволяет вывести информацию об объекте передав {:?} или {:#?} при форматировании строки. Пример -\u0026gt; println!(\u0026quot;New sensor found: {:?}\u0026quot;, Sensor { id: \u0026quot;123\u0026quot;.to_string(), temperature: 20.0 }); подробнее в доке\nИмперативный стиль # В основной функции обрабатываем список показаний с датчиков, чтобы отсеять те, которые не имеют критический уровень температуры.\nfn main() { let sensors = vec![ \u0026#34;sensor1 22.5\u0026#34;, \u0026#34;sensor2 35.7\u0026#34;, \u0026#34;sensor3 29.8\u0026#34;, \u0026#34;sensor4 41.3\u0026#34;, \u0026#34;sensor5 30.0\u0026#34; ]; // Критическое значение для температуры let critical_threshold = 30.0; // Массив датчиков со значением превышающим critical_threshold let mut critical_sensors = vec![]; // проходимся циклом по датчикам for s in sensors { // разделяем id датчика и значение температуры по пробелу let mut s = s.split(\u0026#39; \u0026#39;); // получаем id датчика let id = s.next(); // получаем температуру датчика let temperature = s.next(); // Проверка на Some в Option if id.is_some() \u0026amp;\u0026amp; temperature.is_some() { // получаем строковое значение id let id = id.unwrap().to_owned(); // получаем f32 число для температуры let temperature = temperature.unwrap().parse::\u0026lt;f32\u0026gt;(); // проверка на ошибку if temperature.is_ok() { // извлекаем значение из Result let temperature = temperature.unwrap(); // проверка, что значение выше порога if temperature \u0026gt; critical_threshold { // если все прошло успешно добавляем значение в массив сенсоров critical_sensors.push(Sensor { id, temperature }); } } } } // проходимся по всем датчикам в массиве critical_sensors for s in critical_sensors { // выводим в консоль найденные датчики println!(\u0026#34;{:?}\u0026#34;, s); } } Запускаем и видим, что ожидалось (если не видите вывода в godbolt - нажмите внизу на output):\nExecution build compiler returned: 0 Program returned: 0 Sensor { id: \u0026#34;sensor2\u0026#34;, temperature: 35.7 } Sensor { id: \u0026#34;sensor4\u0026#34;, temperature: 41.3 } Получился работающий код, представляющий императивный подход. Какие особенности можно отметить:\nбольшое кол-во строчек кода, много инициализаций переменных, аллоцирование памяти под коллекцию critical_sensors. Переходим к функциональному подходу.\nФункциональный стиль # Код до массива critical_sensors будет аналогичным, поэтому его пропущу\n... fn main() { ... // Массив датчиков со значением превышающим critical_threshold // Получаем итератор из Vec sensors let critical_sensors: Vec\u0026lt;Sensor\u0026gt; = sensors.iter() // Передаем по цепочке в функцию map .map(|s| { // разделяем id датчика и значение температуры по пробелу let mut s = s.split(\u0026#39; \u0026#39;); // получаем строковое значение id let id = s.next()?.to_owned(); // получаем f32 число для температуры let temperature = s.next()?.parse::\u0026lt;f32\u0026gt;().ok()?; // Возвращаем Some с данными по сенсору Some(Sensor { id, temperature }) }) // Итератор который сплющивает Some(Some(Sensor)) в Iterator(Some) .flatten() // Отфильтровываем значение температуры чтобы получить те сенсоры где значение выше порога .filter(|s| s.temperature \u0026gt; critical_threshold) // Магический метод collect позволяет собрать все значения из итератора в коллекцию Vec .collect(); // проходимся по всем датчикам в массиве critical_sensors for s in critical_sensors { // выводим в консоль найденные датчики println!(\u0026#34;{:?}\u0026#34;, s); } } Такой код намного проще читать, запускаем и видим вывод аналогичный прежнему:\nProgram returned: 0 Sensor { id: \u0026#34;sensor2\u0026#34;, temperature: 35.7 } Sensor { id: \u0026#34;sensor4\u0026#34;, temperature: 41.3 } Iterator::collect # Плюс использования Iterator::collect что нам не нужно аллоцировать лишнюю коллекцию и добавлять туда элементы в цикле. Метод collect позволяет взять итератор и конвертировать его в коллекцию для удобной дальнейшей работы или вывода.\nЕще один небольшой пример:\nlet input = vec![ \u0026#34;Hello\u0026#34;.to_string(), \u0026#34;World!\u0026#34;.to_string(), \u0026#34;From\u0026#34;.to_string(), \u0026#34;E@Gle\u0026#34;.to_string(), \u0026#34;Blog\u0026#34;.to_string(), ]; let output: Vec\u0026lt;String\u0026gt; = input.iter().cloned().collect(); println!(\u0026#34;{:?}\u0026#34;, output); Бонус # Предлагаю пройтись по методам которые могут вызвать вопросы, а ведь они часто встречаются в исходниках программ написанных на rust\nis_some # Метод is_some вызывается для типа Option и возвращает true, если Option является Some, и false, если None.\nto_owned # Метод to_owned берет копию объекта и передаёт владение вызывающей стороне или как написано в доке преобразует заимствованные данные в собственные. Обычно он используется для создания String из \u0026amp;str.\nunwrap # Метод unwrap извлекает значение из Option, если оно является Some, иначе выбрасывает панику, если Option является None.\nСчитается, что лучше использовать его пореже поскольку с паникой завершается выполнение программы. Про обработку ошибок будет в другой статье.\nflatten # Метод flatten преобразует Option\u0026lt;Option\u0026gt; в Option, удаляя один уровень вложенности. Его также можно использовать с итераторами для удаления значений None.\nok # Метод ok преобразует Result в Option. Если результат Ok, то возвращается Some, в противном случае возвращается None.\nparse::\u0026lt;F\u0026gt;() # Механизм parse::() в Rust используется для преобразования фрагмента строки (\u0026amp;str) в число. Этот метод реализован в \u0026amp;str и может парсить строковый слайс в различные типы, не только f32. Ниже пример показывающий как можно преобразовать \u0026amp;str в u32\nlet four: u32 = \u0026#34;4\u0026#34;.parse().unwrap(); assert_eq!(4, four); Заключение: # Надеюсь, что заметка поможет еще лучше познакомиться с языком Rust и увидеть, что это современный гибкий язык не заточенный на какой-то один стиль написания кода.\nRust собирает в себе лучшее из мира программирования.\nЕсли что-то осталось непонятным, пожалуйста изучите, что делает каждая строка и прочитайте комментарии к коду. И тогда, я верю, белых пятен станет намного меньше. Хочу напомнить: не обязательно скролить блог чтобы читать статьи можно также нажать \u0026ldquo;пробел\u0026rdquo;. Пишите код, практикуйтесь, делитесь ссылкой на блог и удачи! 👾\n","date":"16 июля 2024","externalUrl":null,"permalink":"/rust/rust-collect/","section":"Rust","summary":"","title":"Rust императивный и функциональный, а также о методе collect","type":"rust"},{"content":"","date":"11 июня 2024","externalUrl":null,"permalink":"/tags/music/","section":"Tags","summary":"","title":"Music","type":"tags"},{"content":"Недавно баловался с сервисом генерации песен suno и создал стебный альбом \u0026ldquo;Код ночи\u0026rdquo;. Приятного прослушивания 😄\nссылка на audiomack\nЕсли вы тоже захотите поэксперементировать, то вот пара советов:\nиспользуйте () чтобы создать хор спасибо Илье за лайфхак! чтобы нейронка правильно ставила ударения выделЯйте ударную бУкву используйте теги чтобы разнообразить звучание, которые иногда помогают получить интересные эффекты: [Epic Intro] [Instrumental] [Intro] [Verse] [Drop] [Bridge] [Chorus] [Breakdown] [Epic Outro] [Fade Out and End] не бойтесь смешивать и эксперементировать с жанрами ссылка на список стилей и жанров старайтесь добавить в текст рифму и не используйте большое кол-во строк для припевов не забывайте добавлять паузы иногда полезно искажать буквы чтобы добиться правильного произношения Пример текста с тегами из песни \u0026ldquo;Демка прошла\u0026rdquo;\n[Instrumental] [Intro] (м-м-м-м) [Verse 1] Вот и дэмка прошла Ла-ла, ла-ла, ла-ла Что она нам принесла (Ла-ла, Ла-ла, Ла-ла) [Breakdown] [Verse 2] Этот новый релиз (релиз-релиз, релиз-релиз) Взлёт, падение Вверх, вниз (Вверх-вниз, Вверх-вниз) Скоро новый релиз (релиз-релиз, релиз-релиз) Взлет, падение Вверх, вниз [Drop] [Bridge] [Verse 3] Тум-тум, Тум-тум, Тум-тум Тум-тум, Тум-Тум, Тум-тум [Drop] [Chorus] Времени было немного Подоспела вдруг подмога И веселья стало много Тум-тум, Тум-Тум, Тум-тум [Breakdown] Этот новый релиз (релиз-релиз, релиз-релиз) Взлёт, падение Вверх, вниз (Вверх-вниз, Вверх-вниз) [Drop] [Instrumental Solo] (м-м-м-м) [Epic Outro] [Fade Out and End] Не бойтесь нового, генерируйте новую музыку и песни. У вас все получится и большой творческой удачи! 🙌\n","date":"11 июня 2024","externalUrl":null,"permalink":"/about-all/night-code/","section":"Посты обо всем","summary":"","title":"Выпуск альбома Код Ночи","type":"about-all"},{"content":"","date":"11 мая 2024","externalUrl":null,"permalink":"/series/about-dart/","section":"Series","summary":"","title":"About-Dart","type":"series"},{"content":"","date":"11 мая 2024","externalUrl":null,"permalink":"/tags/dart/","section":"Tags","summary":"","title":"Dart","type":"tags"},{"content":"","date":"11 мая 2024","externalUrl":null,"permalink":"/categories/dart/","section":"Categories","summary":"","title":"Dart","type":"categories"},{"content":" Моим основным языком программирования в настоящее время является Dart. Много можно говорить о его удобстве, красоте и элегантности, но также нельзя забывать и о его недостатках. Вместо долгих рассуждений давайте на практике посмотрим насколько удобным может быть использование Dart. Примечание: Вместо тысячи слов - строки кода на dart. Flutter ждет отдельный раздел\n","date":"11 мая 2024","externalUrl":null,"permalink":"/dart/","section":"Dart","summary":"","title":"Dart","type":"dart"},{"content":" Введение: # Сегодня я расскажу вам о том, как можно объединить несколько потоков данных Stream в Dart, сохраняя при этом их идентичность. Это полезно, если вы создаете систему реального времени, где данные поступают из разных источников и вам важно знать, откуда они пришли. Давайте начнем!\nПроблема # Представьте, что вы разрабатываете приложение для мониторинга различных сенсоров в умном доме. (Люблю эту тему 😁)\nУ вас есть три потока данных:\nтемпература (Temperature) влажность (Humidity) статус датчика движения (Motion) Эти потоки поступают от разных сенсоров:\nStream\u0026lt;int\u0026gt; temperatureStream = getTemperatureStream(); Stream\u0026lt;int\u0026gt; humidityStream = getHumidityStream(); Stream\u0026lt;int\u0026gt; motionStream = getMotionStream(); На первый взгляд, вы можете подумать, что объединить эти потоки довольно просто. Используем класс StreamGroup:\nStream\u0026lt;int\u0026gt; combinedStream = StreamGroup.merge([temperatureStream, humidityStream, motionStream]); combinedStream.listen((event) { print(event); }); И действительно, это работает. Но вот проблема: когда данные начинают поступать из этих объединенных потоков, вы видите что-то вроде этого:\n\u0026gt; 25 \u0026gt; 60 \u0026gt; 1 Решение # Чтобы решить эту проблему, предлагаю метод, который позволит не только объединить потоки, но и сохранить информацию об их источнике. Мы создадим два вспомогательных класса и метод для объединения потоков. Увидел в статье Punnyarthabanerjee подобный подход и решил адаптировать для себя\nШаг 1: Класс StreamIdentity\nЭтот класс будет хранить идентификатор потока и сам поток:\nclass StreamIdentity { final String identifier; final Stream stream; StreamIdentity(this.identifier, this.stream); } Шаг 2: Класс MergedStreamResponse\nЭтот класс будет содержать данные data и информацию о том, из какого потока они пришли StreamIdentity, а также временную метку timestamp:\nclass MergedStreamResponse { final StreamIdentity streamIdentity; final dynamic data; final DateTime timestamp; MergedStreamResponse(this.streamIdentity, this.data) : timestamp = DateTime.now(); } Шаг 3: Метод mergeStream\nЭтот метод объединит все потоки и будет добавлять данные в контроллер вместе с их идентификатором:\nStream\u0026lt;MergedStreamResponse\u0026gt; mergeStream(List\u0026lt;StreamIdentity\u0026gt; streams) { final StreamController\u0026lt;MergedStreamResponse\u0026gt; controller = StreamController(); for (var identity in streams) { identity.stream.listen((event) { controller.add(MergedStreamResponse(identity, event)); }); } return controller.stream; } Применение # Теперь мы можем передать наши потоки в метод mergeStream и обрабатывать данные с учетом их источника:\nvoid main() { Stream\u0026lt;int\u0026gt; temperatureStream = getTemperatureStream(); Stream\u0026lt;int\u0026gt; humidityStream = getHumidityStream(); Stream\u0026lt;int\u0026gt; motionStream = getMotionStream(); List\u0026lt;StreamIdentity\u0026gt; streams = [ StreamIdentity(\u0026#39;temperature\u0026#39;, temperatureStream), StreamIdentity(\u0026#39;humidity\u0026#39;, humidityStream), StreamIdentity(\u0026#39;motion\u0026#39;, motionStream), ]; Stream\u0026lt;MergedStreamResponse\u0026gt; combinedStream = mergeStream(streams); combinedStream.listen((response) { print(\u0026#39;Data from ${response.streamIdentity.identifier} at ${response.timestamp}: ${response.data}\u0026#39;); }); } Теперь данные выводятся с указанием их источника и времени получения:\nData from temperature at 2024-05-10 12:00:00: 25 Data from humidity at 2024-05-10 12:00:01: 60 Data from motion at 2024-05-10 12:00:02: 1 Пример реализации (полный код) # class StreamIdentity { final String identifier; final Stream stream; StreamIdentity(this.identifier, this.stream); } class MergedStreamResponse { final StreamIdentity streamIdentity; final dynamic data; final DateTime timestamp; MergedStreamResponse(this.streamIdentity, this.data) : timestamp = DateTime.now(); } Stream\u0026lt;MergedStreamResponse\u0026gt; mergeStream(List\u0026lt;StreamIdentity\u0026gt; streams) { final StreamController\u0026lt;MergedStreamResponse\u0026gt; controller = StreamController(); for (var identity in streams) { identity.stream.listen((event) { controller.add(MergedStreamResponse(identity, event)); }); } return controller.stream; } void main() { Stream\u0026lt;double\u0026gt; temperatureSensor = getTemperatureStream(); Stream\u0026lt;double\u0026gt; humiditySensor = getHumidityStream(); Stream\u0026lt;int\u0026gt; motionSensor = getMotionStream(); List\u0026lt;StreamIdentity\u0026gt; sensors = [ StreamIdentity(\u0026#39;temperature\u0026#39;, temperatureSensor), StreamIdentity(\u0026#39;humidity\u0026#39;, humiditySensor), StreamIdentity(\u0026#39;motion\u0026#39;, motionSensor), ]; Stream\u0026lt;MergedStreamResponse\u0026gt; combinedStream = mergeStream(sensors); combinedStream.listen((response) { print(\u0026#39;Data from ${response.streamIdentity.identifier} at ${response.timestamp}: ${response.data}\u0026#39;); }); } Stream\u0026lt;double\u0026gt; getTemperatureStream() async* { yield* Stream.periodic(Duration(seconds: 1), (count) =\u0026gt; 20.0 + count); } Stream\u0026lt;double\u0026gt; getHumidityStream() async* { yield* Stream.periodic(Duration(seconds: 2), (count) =\u0026gt; 50.0 + count * 0.5); } Stream\u0026lt;int\u0026gt; getMotionStream() async* { yield* Stream.periodic(Duration(seconds: 3), (count) =\u0026gt; count % 2); } В этом примере данные от датчика температуры, влажностности и датчика движения объединяются и выводятся с указанием их источника и времени получения. Это позволяет легко отслеживать и анализировать данные от каждого сенсора, что значительно упрощает принятие решений.\nЗаключение # Надеюсь, эта статья была полезной и вы узнали что-то новое о работе с потоками в Dart. Если у вас есть вопросы или комментарии, обращайтесь. Стараюсь писать кратко и по делу. Спасибо за внимание и спасибо автору статьи на медиум за оригинальную идею!\n","date":"11 мая 2024","externalUrl":null,"permalink":"/dart/streams-groups/","section":"Dart","summary":"","title":"Эффективная обработка данных из нескольких источников на Dart","type":"dart"},{"content":"","date":"23 апреля 2024","externalUrl":null,"permalink":"/tags/french/","section":"Tags","summary":"","title":"French","type":"tags"},{"content":" Знакомство с француским языком началось с музыки: Zaz, Stromae и других исполнителей. Конечно не обошлось и без произведений классиков.\nНо почему-то после прочтения войны и мира мне не захотелось погружаться в новый язык, а музыка наоборот пробудила желание узнать о чем же поют и конечно попробовать повторить это Р-р-р-р.\nТакже как и по другим темам, буду тут собирать ссылки с интересностями.\nКаналы телеграмма: (еще не успел отобрать, поэтому привожу часть списка и его ждет обновление)\nФранцузский в удовольствие - https://t.me/fransaise Живой французский/Français moderne - https://t.me/frenchyfox Bien à vous - https://t.me/japprendslefrancais J\u0026rsquo;aime le français / французский - https://t.me/jaimelefrancais Table Moscovite : Французский разговорный клуб в Москве с 2012г - https://t.me/TableMoscovite Французский Каждый День | French Every Day - https://t.me/frencheveryday Учим вместе французский язык - https://t.me/franceforme Читаю вещи - https://t.me/jelisdeschoses Ссылки из большого интернета:\nОгромный словарь на всевозможные тематики - https://www.multitran.com/m.exe?l1=4\u0026l2=2 Приятный словарь с произношением слов - https://fr.glosbe.com/ru/fr Удобный контекстный словарь словарь который можно использовать с различными языками - https://context.reverso.net/traduction/ Словарь от создателей deepl, контекстный - https://www.linguee.fr/ Франко-французский толковый словарь, для продвинутого уровня - http://atilf.atilf.fr/ Грамматика, тексты и видео для разных уровней (просматриваю и прослушиваю как подкасты чтобы привыкнуть к звучанию языка) - https://apprendre.tv5monde.com/fr Аудио на разные темы с транскрипцией - https://www.canalacademies.com/education Спряжение глаголов - http://les-verbes.com/ Цифровая библиотека для изучающих французский - https://www.culturetheque.com/fr Игра в слова - https://babadum.com/play/?lang=5\u0026game=1 Подкаст Coffee Break French (new) - https://coffeebreaklanguages.com/coffeebreakfrench/ Подкаст Français Authentique (new) - https://www.francaisauthentique.com/podcasts/ Много грамматики Tex\u0026rsquo;s French Grammar (new) - https://www.laits.utexas.edu/tex/gr/index.html Новости Le Monde: живая практика (new) - https://www.lemonde.fr/ Продвинутый уровень, можно смотреть видео на французском TV5MONDEplus (new) - https://www.tv5mondeplus.com/fr Ссылки на книги:\nИллюстрированный словарь французского и русского языка с указателями (необычное издание, изображение и ссылки на слова) - книга pdf Кобринец О.С. Французская грамматика. Упражнения для тренинга и тесты с красной карточкой - книга djvu Кобринец О.С. Французский за 15 минут. Начальный уровень - книга djvu Матвеев С.А. - Универсальный учебник для изучающих французский язык. Новый подход (Быстрый французский) - книга pdf Чуб Е.С. - Французский как образ жизни (Изучай и улетай. Помощники по изучению языков) - книга pdf Виктория Килеева - Французский язык с нуля, интенсив - книга pdf ","date":"23 апреля 2024","externalUrl":null,"permalink":"/languages/french-lang/","section":"Иностранные языки","summary":"","title":"Французский язык 🇫🇷","type":"languages"},{"content":"","date":"23 апреля 2024","externalUrl":null,"permalink":"/tags/chinese/","section":"Tags","summary":"","title":"Chinese","type":"tags"},{"content":" Мне очень нравится изучать китайский язык, древнейшая цивилизация с богатой историей и культурой. 你好!\nТакже как и по другим темам, буду тут собирать ссылки с интересностями.\nГлавное не забывать их актуализировать. А пока просто перенесу из старого блога.\nКаналы телеграмма:\nКитайский язык (большое кол-во материала, уроки расписанны практически на каждый день) (последнее обновление: много рекламы курсов, мало материалов) - https://t.me/chinarydom Китайчат (чат для изучающих китайский язык) (последнее обновление: иногда проскакивают полезные ссылки) - https://t.me/china_chat Китайский батя (активный чат для любителей китайского языка) - https://t.me/chinesebatya 俄 китайский язык в телеграме (группа для общения людей, знающих или изучающих китайский язык) - https://t.me/ezhong @sinotati аудио-минутки китайского языка (название говорит само за себя) (последнее обновление: канал переехал, наблюдаем) - https://t.me/sinotati Chinese+ Китайский с плюсом (тексты, аудио и видео с субтитрами для изучающих китайский язык) - https://t.me/chineseplus Учим Китайский (Кладовая полезной информации по китайскому языку) - https://t.me/mychinese Как выучить китайский (советы и лайфхаки от учителя китайского языка) - https://t.me/chineselang Learn Chinese (канал на английском. Последняя запись появилась 18 сентября 2018 года) - https://t.me/chineseclass101 Китайские иероглифы (перевод и пошаговое написание иероглифов) - https://t.me/chinazi 汉语 (здесь вы найдете книги, приложения, а также другую различную информация для изучения китайского языка) (Последняя запись появилась в июле 2019 года) - https://t.me/InterestingChinese Китайская угроза (автор канала Олег Даокэдао публикует новости о Китае, новости с политическим уклоном) - https://t.me/daokedao Китайский офис (из жизни офисного работника в китайской компании) - https://t.me/china_off + чат канала https://t.me/china_office Китай на изнанку (Реальные истории о жизни и бизнесе) - https://t.me/chinabackdoor + чат канала https://t.me/chinabackdoor_live Китайский Городовой (Рупор добра и здравого смысла в мире пропаганды и рекламы Китая, политический уклон) - https://t.me/chinabewithyou Китайский плакат (Только самые интересные новости современного китайского общества) - https://t.me/chinaposter Китайский дракон (Факты о Китае: весь мир в культурном шоке) - https://t.me/chinatours4you 读一读: a laowai’s notes (Канал учителя английского из Шанхая) (Последняя запись появилась в декабре 2022 года) - https://t.me/zhongisrussianf 语法 кратко (ничего лишнего - только грамматика и только кратко) - https://t.me/yufashorts Китайский язык. Иероглифы 汉 (Иероглифы простыми словами) - https://t.me/yufashorts Ссылки из большого интернета:\nПроизношение всех китайских слогов - https://www.studychinese.ru/lessons/10/69/ Статья с meduza \u0026ldquo;Как выучить китайский язык?\u0026rdquo; - https://meduza.io/cards/kak-vyuchit-kitayskiy-yazyk Прописи по китайскому языку в формате pdf - http://ling-lingchinese.com/worksheets/ Мнемонический способ запоминания цифр в виде картинок - https://vividchinese.blog/category/numbers/ Англо-китайский словарь с произношением, а также анимациями правильного написания иероглифов - https://www.archchinese.com/chinese_english_dictionary.html Сайт где можно найти китайские фильмы с субтитрами на английском - http://www.gooddrama.to/drama-list#chinese-drama Словарь с произношением, а также анимациями правильного написания иероглифов - https://www.trainchinese.com/ Первый урок для погружения в китайский язык - https://upload.pgu.ru/iblock/f0c/stranitsy-iz-moy-pervyy-ieroglif-1_ilovepdf_compressed.pdf Цикл уроков по написанию черт - https://www.youtube.com/watch?v=Lyplj5vUooI\u0026t=2s Еще один англо-китайский словарь с произношением, с последовательностью написания черт, а также с роликами где упоминаются слова - https://chinese.yabla.com/ ","date":"23 апреля 2024","externalUrl":null,"permalink":"/languages/chinese-lang/","section":"Иностранные языки","summary":"","title":"Китайский язык 🇨🇳","type":"languages"},{"content":"","date":"20 апреля 2024","externalUrl":null,"permalink":"/categories/languages/","section":"Categories","summary":"","title":"Languages","type":"categories"},{"content":"","date":"20 апреля 2024","externalUrl":null,"permalink":"/categories/my-gallery/","section":"Categories","summary":"","title":"My-Gallery","type":"categories"},{"content":"","date":"20 апреля 2024","externalUrl":null,"permalink":"/tags/start/","section":"Tags","summary":"","title":"Start","type":"tags"},{"content":"Сегодня запускаю новый раздел | категорию - Фотографии.\nПодробности на странице раздела ⬇\nПерейти на страницу фотографии\n","date":"20 апреля 2024","externalUrl":null,"permalink":"/about-all/new-photos/","section":"Посты обо всем","summary":"","title":"Запуск нового раздела c фотографиями 🚀","type":"about-all"},{"content":"Сегодня запускаю новый раздел | категорию - Иностранные языки.\nПодробности на странице раздела ⬇\nПерейти на страницу языки\n","date":"20 апреля 2024","externalUrl":null,"permalink":"/about-all/new-languages/","section":"Посты обо всем","summary":"","title":"Запуск нового раздела иностранные языки 🚀","type":"about-all"},{"content":" Одним из моих хобби является изучение различных языков.\nУровень еще далек для написания полноценных статей поэтому я буду выкладывать ссылки на полезные группы, материалы и сайты.\nПриятного просмотра!\n","date":"20 апреля 2024","externalUrl":null,"permalink":"/languages/","section":"Иностранные языки","summary":"","title":"Иностранные языки","type":"languages"},{"content":"","date":"20 апреля 2024","externalUrl":null,"permalink":"/tags/%D1%84%D0%BE%D1%82%D0%BE/","section":"Tags","summary":"","title":"Фото","type":"tags"},{"content":" Я большой любитель фотографии, иногда даже удается поймать интересный кадр 📸\nНа этой странице будут появляться фото снятые недавно или в прошлом. Они будут размещены в виде галереи из плиток.\nEще у меня сохранились статьи из старой группы в вк по фотографии. Их тоже планирую выложить в блог в скором времени.\nПриятного просмотра!\n","date":"20 апреля 2024","externalUrl":null,"permalink":"/my-gallery/","section":"Welcome to my blog! 🎉","summary":"","title":"Фотографии","type":"page"},{"content":"Изучая очередные проекты с открытым исходным кодом заметил интересный проект который предлагает пользователю создать генеалогическое древо и провести генеалогическое исследование.\nКак обычно у приложения есть вики-сайт с документацией\nИнтерфейс чем-то похож на calibre для работы со своими электронными книгами.\nhttps://gramps-project.org\nВпечатления от приложения за 5 минут использования # Запустил, создал новый проект. В левой части окна отобразилась справка (но кто же ее будет читать). Мне не сильно приглянулся интерфейс, крайне скудная цветовая гамма и малопонятные иконки у кнопок. Заметно, что приложение предназначено для настоящих исследователей, которых не напугают никакие преграды и интерфейсы из 2000-x годов (GTK+/GNOME).\nЯ загрузил тестовую базу данных и немного погулял по приложению чтобы изучить его возможности - ссылка на тестовую базу\nНа русском языке к сожалению такую не обнаружил. Возможно плохо искал.\nКод # Покопался в исходниках и посмотрел как в целом устроен проект. Приложение написано на python - репозиторий\nМожно скачивать и устанавливать различные плагины для расширения функционала. Обнаружил в исходниках даже cli. Дальше не стал изучать, что с ним можно сделать, но скорее всего используется для автоматизации рутиных действий.\nРепозиторий хорошо структурирован. Содержит .github/workflows для автоматической сборки проекта под различные архитектуры. Разработчики содержат код и репозиторий в порядке, молодцы!\nВывод # Проект большой, требует достаточно времени чтобы читать документацию и изучать все вкладки, окна приложения.\nЧто не понравилось:\nНе хватает наглядности Нет возможности загрузить шаблоны для отчетов с представлением результата Результат генерации, будь то pdf документ либо сайт сразу не открывается, никаких окон уведомлений нет Большой функционал не обернутый в красивую обложку (этим страдает множество свободных проектов) Нет понимания как интегрировать это приложение с другими инструментами Нет облачного хранилища и возможности поделиться резульатом работы чтобы твои родственники могли дополнить информацию Какие есть плюсы и возможности:\nМожно строить связи Можно строить древо в различных представлениях. Оно будет интерактивным, достаточно удобным Использовать множество фильтров Добавлять геометки Строить отчеты Добавлять заметки Добавлять ссылки Скриншот приложения gramps Ставь лайк если интересны проекты с открытым исходным кодом. (Кнопка Like находится под заголовком поста) ","date":"20 апреля 2024","externalUrl":null,"permalink":"/til/tools/gramps/","section":"Сегодня я узнал","summary":"","title":"Gramps","type":"til"},{"content":"","date":"20 апреля 2024","externalUrl":null,"permalink":"/tags/opensource/","section":"Tags","summary":"","title":"OpenSource","type":"tags"},{"content":"","date":"20 апреля 2024","externalUrl":null,"permalink":"/tags/%D1%81%D0%B5%D0%BC%D0%B5%D0%B9%D0%BD%D0%BE%D0%B5-%D0%B4%D1%80%D0%B5%D0%B2%D0%BE/","section":"Tags","summary":"","title":"Семейное Древо","type":"tags"},{"content":"","date":"30 марта 2024","externalUrl":null,"permalink":"/tags/cad/","section":"Tags","summary":"","title":"Cad","type":"tags"},{"content":"","date":"30 марта 2024","externalUrl":null,"permalink":"/til/cad/","section":"Сегодня я узнал","summary":"","title":"CAD","type":"til"},{"content":"Открыл для себя замечательную свободную программу для построения параметрических моделей, которые потом можно распечатать на 3d принтере.\nДля создания моделей используется собственный скриптовый язык. Скрипты писать довольно легко, главное изучить синтаксис и запомнить базовые примитивы для построения. В этом поможет туториал\nПодсверка синтаксиса, консоль с ошибками, возможность изменять параметры модели через удобную панель - все присутствует.\nhttps://openscad.org/index.html\nВот такая машина получилась 😀 ","date":"30 марта 2024","externalUrl":null,"permalink":"/til/cad/openscad/","section":"Сегодня я узнал","summary":"","title":"OpenSCAD","type":"til"},{"content":"","date":"29 марта 2024","externalUrl":null,"permalink":"/series/smart-home/","section":"Series","summary":"","title":"Smart Home","type":"series"},{"content":"","date":"29 марта 2024","externalUrl":null,"permalink":"/series/smart_home/","section":"Series","summary":"","title":"Smart_home","type":"series"},{"content":"","date":"29 марта 2024","externalUrl":null,"permalink":"/tags/technology/","section":"Tags","summary":"","title":"Technology","type":"tags"},{"content":"","date":"29 марта 2024","externalUrl":null,"permalink":"/categories/til/","section":"Categories","summary":"","title":"Til","type":"categories"},{"content":" Вступление # \u0026ldquo;Вот умный дом, который построил Джек\u0026rdquo; - название, которое тут же заставляет вспомнить всем нам знакомое английское стихотворение. Но в нашем случае, Джеком становятся все разработчики умных домов в России, а дом - это образ всей отрасли, которую они строят вместе и часто отдельно друг от друга. Рассмотрим, какие проблемы и сложности возникают на этом пути.\nПроблемы развития умных домов в России # Умный дом - это система, которая обеспечивает управление и автоматизацию домашнего хозяйства. Она предполагает объединение домашних устройств для удобства и комфорта жильцов. Однако, несмотря на то, что технология умных домов находится на волне популярности, ее развитие в России сталкивается с серьезными трудностями.\nНедостаток квалифицированных кадров # Само понятие \u0026ldquo;умный дом\u0026rdquo; подразумевает применение передовых технологий. Однако, нет секрета, что в России наблюдается дефицит кадров, способных разрабатывать и внедрять подобные решения. Это приводит к тому, что многие проекты остаются нереализованными, а рынок не может оценить весь потенциал этой технологии.\nРазрозненность стандартов # Еще одна проблема заключается в отсутствии единых стандартов разработки устройств для умных домов. Каждая компания-производитель стремится создать свою уникальную систему, которая не совместима с продукцией других производителей. Это затрудняет процесс внедрения технологии, поскольку потребителю приходится выбирать между различными \u0026ldquo;экосистемами\u0026rdquo; умных домов.\nЗаключение # Пока что умный дом, который строит Джек, выглядит скорее как сложный пазл, чем готовое к проживанию здание. Отсутствие квалифицированных кадров и разрозненность стандартов замедляют развитие технологии в России. Но, несмотря на все сложности, интерес к умным домам не угасает. Надееюсь, что в ближайшем будущем ситуация изменится, и каждый сможет построить свой умный дом.\nДжек продолжает свою работу. И нам остается верить, что он сумеет справиться со всеми сложностями, ведь именно от него зависит, насколько умным будет наш дом завтра.\nПродолжение следует.\nПрисоединяйтесь, чтобы узнать больше об умном доме.\n","date":"29 марта 2024","externalUrl":null,"permalink":"/about-all/smart-home/1711651438323-smart-home-ru/","section":"Посты обо всем","summary":"","title":"Вот умный дом, который построил Джек: Обзор","type":"about-all"},{"content":" Несколько лет назад я имел уникальную возможность работать в компании, где мы с нуля создавали концепцию умного дома. В предстоящем цикле заметок хочу поделиться своими мыслями и впечатлениями по этой, безусловно, увлекательной теме. Моя цель - структурировать собранную информацию, вспомнить те вызовы и проблемы, с которыми мы столкнулись. Описать важные вопросы, которые, как мне представляется, затрагивают общую ситуацию в стране в области умного дома.\nПервая статья цикла: Вот умный дом, который построил Джек: Обзор 29 марта 2024\u0026middot;286 слов\u0026middot;2 минут\u0026middot; loading \u0026middot; loading Technology ","date":"29 марта 2024","externalUrl":null,"permalink":"/about-all/smart-home/","section":"Посты обо всем","summary":"","title":"Вот умный дом, который построил Джек: проблемы развития технологии в России","type":"about-all"},{"content":"Сегодня запускаю новый раздел | категорию - TIL.\nПодробности на странице раздела ⬇\nПерейти в TIL\n","date":"29 марта 2024","externalUrl":null,"permalink":"/about-all/new-til/","section":"Посты обо всем","summary":"","title":"Запуск нового раздела TIL 🚀","type":"about-all"},{"content":"","date":"29 марта 2024","externalUrl":null,"permalink":"/about-all/","section":"Посты обо всем","summary":"","title":"Посты обо всем","type":"about-all"},{"content":" TIL переводится с английского как \u0026ldquo;Сегодня я узнал\u0026rdquo; - Today I learned. Как-то гуляя по redit я наткнулся на странное слово или сокращение TIL, под этим тегом были собраны миллионы заметок на различные темы. От программирования до астрономии.\nНемного погуглил и узнал, что это большое движение и множество людей ведут свои заметки записывая, что они сегодня узнали. Так, что вот мой TIL\n","date":"29 марта 2024","externalUrl":null,"permalink":"/til/","section":"Сегодня я узнал","summary":"","title":"Сегодня я узнал","type":"til"},{"content":" Вступление # Умный дом не просто собрание устройств под одной крышей, это сложная экосистема, работающая как единое целое. И для создания такой системы требуются высококвалифицированные специалисты.\nВ России наблюдается дефицит кадров, способных разрабатывать и внедрять подобные решения. Это приводит к тому, что многие проекты остаются нереализованными, а рынок не может оценить весь потенциал этой технологии.\nМой опыт работы # По своему опыту могу сказать, что сегодня в области умных домов работают специалисты самых разных профессий. У нас в команде были специалисты из нефтяной отрасли, программисты, работавшие с промышленными контроллерами. Именно тогда я узнал о таких вещах как: программируемые логические контроллеры (ПЛК) и отраслевые стандарты на это оборудование.\nВажность обучения и развития специалистов # Важность обучения и развития специалистов в области умных домов трудно переоценить. Это сложная и многогранная область, которая включает в себя не только разработку программного обеспечения, но и знание принципов работы различных домашних устройств, сценариев использования, а также способность интегрировать эти устройства в единую систему.\nОбразование и умные дома в России # Сегодня, к сожалению, высшее образование в России не предлагает специализированных программ, которые бы готовили специалистов по умным домам. Большинство специалистов приходят в эту сферу из других областей IT и учатся на ходу. Эта область не раскрыта, не получила широкого распространения. Вендоры сами вынуждены приходить в институты и колледжи и предлагать провести занятия, объяснить студентам перспективы умных домов и глубже погрузить в предмет. Конечно вендоры делают акцент на своих устройствах и собственной инфраструктуре.\nТакже на своем опыте убедился, что в технических колледжах добавляют в предметы робототехнику и электронику, закупают распбери и ардуины, но квалификации преподавательского состава не хватает чтобы все это нормально настроить и собрать интересные проекты. Хочется верить, что просто мне не повезло и в стране есть другие примеры.\nВозможности для обучения и развития # Недостаток квалифицированных кадров – это возможность для тех, кто готов учиться и развиваться. Ведь спрос на эти услуги будет только расти, а значит и специалисты в этой области будут востребованы.\nАнонс следующей заметки # В следующей заметке хочу рассмотреть еще одну важную проблему в развитии умных домов в России - разрозненность стандартов. Присоединяйтесь, чтобы узнать больше об умном доме.\n","date":"29 марта 2024","externalUrl":null,"permalink":"/about-all/smart-home/1711659284793-smart-home-ru-2/","section":"Посты обо всем","summary":"","title":"Умный дом, который построил Джек: Кадры","type":"about-all"},{"content":"Буквально недавно был анонсирован набор уроков для Swift и SwiftUI - https://developer.apple.com/tutorials/develop-in-swift\n","date":"29 марта 2024","externalUrl":null,"permalink":"/til/swift/develop_in_swift_tutorials/","section":"Сегодня я узнал","summary":"","title":"Develop in swift tutorials","type":"til"},{"content":"","date":"29 марта 2024","externalUrl":null,"permalink":"/tags/swift/","section":"Tags","summary":"","title":"Swift","type":"tags"},{"content":" ","date":"29 марта 2024","externalUrl":null,"permalink":"/til/swift/","section":"Сегодня я узнал","summary":"","title":"Swift","type":"til"},{"content":" Генерация статического контента для сайта docs flutter Static assets for embedding into docs.flutter.io Assets for api docs repo\nflutter/assets-for-api-docs Static assets for embedding into docs.flutter.io Dart 122 108 Много примеров, интересные решения для генерации анимаций, картинок. Стоит изучить этот репозиторий\n","date":"28 марта 2024","externalUrl":null,"permalink":"/til/flutter/1711660895366-flutter/","section":"Сегодня я узнал","summary":"","title":"Assets-for-api-docs","type":"til"},{"content":" ","date":"28 марта 2024","externalUrl":null,"permalink":"/til/flutter/","section":"Сегодня я узнал","summary":"","title":"Flutter","type":"til"},{"content":" Официальный сайт с документацией Упражнения и тренировки. Знаменитый rustlings Дорожная карта для изучающих Rust Rust for C Programmers A Gentle Introduction To Rust Еще один короткий справочник для разработчиков на C Курс по Rust от гугл 🦀 hard skill не для начинающих! Rustonomicon - курс по Rust Unsafe Рекомендуемые крейты (crates) Очень полезный справочник по rust в виде Cheat Sheet ","date":"28 марта 2024","externalUrl":null,"permalink":"/rust/links/","section":"Rust","summary":"","title":"Ссылки на полезные ресурсы по языку rust","type":"rust"},{"content":" Введение в Rust: установка, запуск и структура проекта # Rust - это мощный системный язык программирования, в котором скорость и безопасность по управлению памятью стоит на первом месте. Разберемся, как установить и запустить Rust, а также рассмотрим из чего состоит базовый проект.\nУстановка Rust # Люблю правило одной кнопки когда не приходится редактировать кучу конфигов и гулять по сайтам в поиске правильного порядка дейстий и ссылок.\nДля установки Rust на вашем компьютере просто перейдите на официальный сайт Rust и следуйте инструкциям говорят все статьи. Но мы сделаем иначе.\nНа Linux и macOS системах запускаем:\ncurl https://sh.rustup.rs -sSf | sh для macOS можно запустить:\nbrew install rustup для windows скачиваем экзешник по ссылке и следуем сообщениям.\nТаким несложным способом можно установить rust и его туллинг.\nПервое приложение # Теперь пришло время создать наше первое приложение на расте.\nПереходим в необходимую директорию (я люблю создавать главную папку dev в ней workspace/rust, которая содержит все проекты разбитые по технологиям). После выбора директории запускаем команду cargo new\ncargo new hello_world По умолчанию флаг \u0026ndash;bin уже установлен и не нужно его указывать дополнительно. Он нужен для сборки бинарника. Также можно собрать библиотеку передав параметр \u0026ndash;lib\nПосмотрим, что у нас получилось\n$ cd hello_world $ tree . . ├── Cargo.toml └── src └── main.rs 1 directory, 2 files Первый файл Cargo.toml. Откроем его\n[package] name = \u0026#34;hello_world\u0026#34; version = \u0026#34;0.1.0\u0026#34; edition = \u0026#34;2021\u0026#34; [dependencies] Это так называемый манифест, как его назвали на официальном сайте. Типичный файл конфигурации проекта, а также зависимостей для большинства современных языков.\nЗаглянем в директорию src и посмотрим на файл main.rs\nfn main() { println!(\u0026#34;Hello, world!\u0026#34;); } Чтобы скомпилировать и запустить нашу программу, введите команду:\n$ cargo build Собрали, можно запускать:\n$ ./target/debug/hello_world Hello, world! Вывело стандартное приветствие, мы молодцы! Будем продолжать.\nХотите команду поменьше? Можно запустить программу через команду cargo run\nЗаключение # Вот и все основы для начала работы с Rust. Этот язык имеет множество возможностей и дает большую свободу действий, так что вперед изучать и экспериментировать!\n","date":"16 марта 2024","externalUrl":null,"permalink":"/rust/start-rust/","section":"Rust","summary":"","title":"Basic rust","type":"rust"},{"content":" Использование Match-Pattern matching в Rust # Введение: # В языке программирования Rust, match является мощной конструкцией, которая позволяет сравнивать значение с серией шаблонов и выполнять код в зависимости от совпадения. Рассмотрим различные виды и применения match.\nТипы паттерн матчинга: # Простой Match: Простой match в Rust сравнивает значение с серией шаблонов и выполняет код, соответствующий первому совпавшему шаблону. let value = 1; match value { 1 =\u0026gt; println!(\u0026#34;one\u0026#34;), 2 =\u0026gt; println!(\u0026#34;two\u0026#34;), _ =\u0026gt; println!(\u0026#34;anything\u0026#34;), } Match с Деструктуризацией: match также может использоваться для деструктуризации значений в более сложных типах данных, таких как кортежи, структуры или перечисления. let pair = (0, -2); match pair { (0, y) =\u0026gt; println!(\u0026#34;First is `0` and `y` is `{:?}`\u0026#34;, y), (x, 0) =\u0026gt; println!(\u0026#34;`x` is `{:?}` and last is `0`\u0026#34;, x), _ =\u0026gt; println!(\u0026#34;It doesn\u0026#39;t matter what they are\u0026#34;), } Match с Условиями: Условия в match позволяют уточнить шаблон с помощью произвольных условий. Это позволяет добавить дополнительные условия для шаблона. match pair { (x, y) if x == y =\u0026gt; println!(\u0026#34;These are twins\u0026#34;), (x, y) if x + y == 0 =\u0026gt; println!(\u0026#34;These are inverses\u0026#34;), (x, _) if x % 2 == 1 =\u0026gt; println!(\u0026#34;The first one is odd\u0026#34;), _ =\u0026gt; println!(\u0026#34;No correlation...\u0026#34;), } Match с Перечислениями: match также часто используется с перечислениями. Это позволяет обрабатывать разные варианты перечисления и извлекать связанные данные. enum Color { Red, Blue, Green, RGB(u32, u32, u32), } let color = Color::RGB(122, 17, 40); match color { Color::Red =\u0026gt; println!(\u0026#34;Red\u0026#34;), Color::Blue =\u0026gt; println!(\u0026#34;Blue\u0026#34;), Color::Green =\u0026gt; println!(\u0026#34;Green\u0026#34;), Color::RGB(r, g, b) =\u0026gt; println!(\u0026#34;RGB: {}, {}, {}\u0026#34;, r, g, b), } Еще один интересный пример: # Пусть у нас есть enum Exercise, которое представляет различные типы упражнений, доступных в приложении.\nenum Exercise { Run { distance: f64 }, Swim { duration: u32 }, Yoga, } Мы можем использовать match для обработки различных типов упражнений и выполнения различных действий в зависимости от типа упражнения.\nfn process_exercise(exercise: Exercise) { match exercise { Exercise::Run { distance } =\u0026gt; { println!(\u0026#34;Ты пробежал {} км. сегодня. Отлично!\u0026#34;, distance); }, Exercise::Swim { duration } =\u0026gt; { println!(\u0026#34;Ты плавал {} минут сегодня. Продолжай в том же духе!\u0026#34;, duration); }, Exercise::Yoga =\u0026gt; { println!(\u0026#34;Отличная работа, ты выполнил свою норму йоги на сегодня!\u0026#34;); } } } В этом примере match позволяет нам безопасно обработать различные типы упражнений и извлечь связанные с ними данные.\nПолный код приложения: ссылка на онлайн компилятор\nenum Exercise { Run { distance: f64 }, Swim { duration: u32 }, Yoga, } fn process_exercise(exercise: Exercise) { match exercise { Exercise::Run { distance } =\u0026gt; { println!(\u0026#34;Ты пробежал {} км. сегодня. Отлично!\u0026#34;, distance); }, Exercise::Swim { duration } =\u0026gt; { println!(\u0026#34;Ты плавал {} минут сегодня. Продолжай в том же духе!\u0026#34;, duration); }, Exercise::Yoga =\u0026gt; { println!(\u0026#34;Отличная работа, ты выполнил свою норму йоги на сегодня!\u0026#34;); } } } fn main() { process_exercise(Exercise::Run{distance:100.0}); process_exercise(Exercise::Swim{duration:100}); process_exercise(Exercise::Yoga); } Шаблоны match позволяют обрабатывать различные варианты и извлекать связанные данные, все это безопасно и эффективно. Это дает нам возможность легко обрабатывать различные случаи и извлекать данные из сложных типов, сохраняя при этом чистоту и ясность кода.\nКогда мы используем match, мы не только упрощаем наш код, но и делаем его более надежным. С помощью match мы можем быть уверены, что обработали все возможные варианты, иначе компилятор выдаст предупреждение.\nЗаключение: # match в Rust - это мощный инструмент, который делает код более ясным и безопасным. Он позволяет обрабатывать различные случаи и извлекать данные из сложных типов в удобной и безопасной манере.\nТакже я очень рад что механизм паттерн матчинга появился с версии 3 в dart. Там используется ключевое слово switch. Еще нельзя будет не упомянуть что в языке Elixir шаблоны match используются постоянно.\nЕсли найду еще интересные варианты применения этого механизма обязательно опубликую.\n","date":"16 марта 2024","externalUrl":null,"permalink":"/rust/match/","section":"Rust","summary":"","title":"Pattern matсhing","type":"rust"},{"content":" Привет и добро пожаловать на мой блог!\nРаньше я его вел на платформе Ghost, но домен был для меня недоступен и лишь недавно удалось его восстановить. Вместо того, чтобы поднимать всю систему снова, я решил что в этот раз будет интереснее попробовать что-то новое. И остановился на статическом генераторе сайтов Hugo.\nСпросите, а хорошие старые статьи из прошлого блога пропали? Вовсе нет, они ни куда не делись. Постепенно я начну переносить их сюда.\nЧто же я буду здесь публиковать? Помимо статей на тему технологий таких как Flutter/Dart, Java/Kotlin, Golang, Elixir, Linux, Docker, Rust и прочих, также буду рассказывать о своих хобби. Подкину пару полезных штук, лайфхаков, и просто поделюсь своими размышлениями о жизни, технологиях и окружающем мире.\nСпасибо за твое внимание и за то, что заглянул на эту страничку.\nПриятного чтения! :waving_hand:\n","date":"13 марта 2024","externalUrl":null,"permalink":"/about-all/start/","section":"Посты обо всем","summary":"","title":"Мой первый пост после перезапуска 🚀","type":"about-all"},{"content":" Недавние Android 15 и 16 KB страница памяти: что изменится, как подготовить Android и Flutter-приложения 25 сентября 2025\u0026middot;964 слов\u0026middot;5 минут\u0026middot; loading \u0026middot; loading Android Flutter Dev Rust async и Tokio: В ожидании будущего 1776 слов\u0026middot;9 минут\u0026middot; loading \u0026middot; loading Rust Dev Как работает Vec в Rust: устройство, методы, оптимизация 2268 слов\u0026middot;11 минут\u0026middot; loading \u0026middot; loading Rust Dev BufReader в Rust: ускоряем ввод-вывод 1861 слово\u0026middot;9 минут\u0026middot; loading \u0026middot; loading Rust Dev Поиск данных в файле - Cli хелпер для мастера с помощью Rust 1812 слов\u0026middot;9 минут\u0026middot; loading \u0026middot; loading Rust Dev Ввод данных от пользователя в Rust: как использовать stdin 344 слов\u0026middot;2 минут\u0026middot; loading \u0026middot; loading Rust Dev Arc в Rust: потокобезопасное разделённое владение 574 слов\u0026middot;3 минут\u0026middot; loading \u0026middot; loading Rust Dev Rc в Rust: Руководство по разделённому владению и RefCell 771 слово\u0026middot;4 минут\u0026middot; loading \u0026middot; loading Rust Dev Заимствование (Borrowing) в Rust 796 слов\u0026middot;4 минут\u0026middot; loading \u0026middot; loading Rust Dev Владение (Ownership) в Rust 772 слов\u0026middot;4 минут\u0026middot; loading \u0026middot; loading Rust Dev Директивы компилятора в Rust 312 слов\u0026middot;2 минут\u0026middot; loading \u0026middot; loading Rust Dev Псевдонимы типов в Rust или как сделать код понятнее 414 слов\u0026middot;2 минут\u0026middot; loading \u0026middot; loading Rust Dev Создание и работа с константами в Rust 495 слов\u0026middot;3 минут\u0026middot; loading \u0026middot; loading Rust Dev Области видимости в Rust 342 слов\u0026middot;2 минут\u0026middot; loading \u0026middot; loading Rust Dev Затенение переменных в Rust 463 слов\u0026middot;3 минут\u0026middot; loading \u0026middot; loading Rust Dev Разбираемся с ошибками в Rust 392 слов\u0026middot;2 минут\u0026middot; loading \u0026middot; loading Rust Dev Изменяемые и неизменяемые переменные в Rust 419 слов\u0026middot;2 минут\u0026middot; loading \u0026middot; loading Rust Dev Работа с println! и интерполяцией строк в Rust 567 слов\u0026middot;3 минут\u0026middot; loading \u0026middot; loading Rust Dev Переменные в Rust: основы и примеры использования 303 слов\u0026middot;2 минут\u0026middot; loading \u0026middot; loading Rust Dev Kotlin 2.1.20: Новые возможности и улучшения для разработчиков 26 марта 2025\u0026middot;712 слов\u0026middot;4 минут\u0026middot; loading \u0026middot; loading Kotlin Hash Map в Rust 733 слов\u0026middot;4 минут\u0026middot; loading \u0026middot; loading Rust Dev Как посчитать количество элементов в массиве в PostgreSQL? 14 февраля 2025\u0026middot;247 слов\u0026middot;2 минут\u0026middot; loading \u0026middot; loading PostgreSQL Создавай диаграммы c языком \"D2\" 13 февраля 2025\u0026middot;98 слов\u0026middot;1 минута\u0026middot; loading \u0026middot; loading Диаграммы Визуализация UML Паттерн \"Строитель\" в Rust 641 слово\u0026middot;4 минут\u0026middot; loading \u0026middot; loading Rust Dev Как отправить несколько файлов одним сообщением через Telegram Bot API 8 февраля 2025\u0026middot;343 слов\u0026middot;2 минут\u0026middot; loading \u0026middot; loading About All Создание и использование структур в Rust: Практическое руководство 770 слов\u0026middot;4 минут\u0026middot; loading \u0026middot; loading Rust Dev Rust императивный и функциональный, а также о методе collect 1030 слов\u0026middot;5 минут\u0026middot; loading \u0026middot; loading Rust Dev Выпуск альбома Код Ночи 11 июня 2024\u0026middot;223 слов\u0026middot;2 минут\u0026middot; loading \u0026middot; loading Music Эффективная обработка данных из нескольких источников на Dart 610 слов\u0026middot;3 минут\u0026middot; loading \u0026middot; loading Dart Dev Французский язык 🇫🇷 23 апреля 2024\u0026middot;347 слов\u0026middot;2 минут\u0026middot; loading \u0026middot; loading French Китайский язык 🇨🇳 23 апреля 2024\u0026middot;433 слов\u0026middot;3 минут\u0026middot; loading \u0026middot; loading Chinese Запуск нового раздела c фотографиями 🚀 20 апреля 2024\u0026middot;17 слов\u0026middot;1 минута\u0026middot; loading \u0026middot; loading Start Запуск нового раздела иностранные языки 🚀 20 апреля 2024\u0026middot;18 слов\u0026middot;1 минута\u0026middot; loading \u0026middot; loading Start Фотографии 20 апреля 2024\u0026middot; loading \u0026middot; loading My-Gallery Фото Gramps 20 апреля 2024\u0026middot;324 слов\u0026middot;2 минут\u0026middot; loading \u0026middot; loading Семейное Древо OpenSource OpenSCAD 30 марта 2024\u0026middot;62 слов\u0026middot;1 минута\u0026middot; loading \u0026middot; loading Cad Вот умный дом, который построил Джек: Обзор 29 марта 2024\u0026middot;286 слов\u0026middot;2 минут\u0026middot; loading \u0026middot; loading Technology Запуск нового раздела TIL 🚀 29 марта 2024\u0026middot;16 слов\u0026middot;1 минута\u0026middot; loading \u0026middot; loading Start Умный дом, который построил Джек: Кадры 29 марта 2024\u0026middot;348 слов\u0026middot;2 минут\u0026middot; loading \u0026middot; loading Technology Develop in swift tutorials 29 марта 2024\u0026middot;12 слов\u0026middot;1 минута\u0026middot; loading \u0026middot; loading Swift Assets-for-api-docs 28 марта 2024\u0026middot;40 слов\u0026middot;1 минута\u0026middot; loading \u0026middot; loading Flutter Ссылки на полезные ресурсы по языку rust 60 слов\u0026middot;1 минута\u0026middot; loading \u0026middot; loading Rust Dev Basic rust 326 слов\u0026middot;2 минут\u0026middot; loading \u0026middot; loading Rust Dev Pattern matсhing 606 слов\u0026middot;3 минут\u0026middot; loading \u0026middot; loading Rust Dev Мой первый пост после перезапуска 🚀 13 марта 2024\u0026middot;132 слов\u0026middot;1 минута\u0026middot; loading \u0026middot; loading About All Start ","date":"20 февраля 2024","externalUrl":null,"permalink":"/posts/","section":"Welcome to my blog! 🎉","summary":"","title":"Все посты","type":"page"},{"content":"","externalUrl":null,"permalink":"/authors/","section":"Authors","summary":"","title":"Authors","type":"authors"}]