[{"content":"","date":"17 July 2026","externalUrl":null,"permalink":"/tags/ai/","section":"Tags","summary":"","title":"Ai","type":"tags"},{"content":"","date":"17 July 2026","externalUrl":null,"permalink":"/tags/android/","section":"Tags","summary":"","title":"Android","type":"tags"},{"content":"","date":"17 July 2026","externalUrl":null,"permalink":"/tags/ios/","section":"Tags","summary":"","title":"Ios","type":"tags"},{"content":"","date":"17 July 2026","externalUrl":null,"permalink":"/tags/kotlin/","section":"Tags","summary":"","title":"Kotlin","type":"tags"},{"content":"","date":"17 July 2026","externalUrl":null,"permalink":"/tags/mobile/","section":"Tags","summary":"","title":"Mobile","type":"tags"},{"content":"","date":"17 July 2026","externalUrl":null,"permalink":"/til/mobile/","section":"Сегодня я узнал","summary":"","title":"Mobile Dev","type":"til"},{"content":"","date":"17 July 2026","externalUrl":null,"permalink":"/tags/swift/","section":"Tags","summary":"","title":"Swift","type":"tags"},{"content":"На этой неделе собрал большую подборку — от официальных WWDC-сессий и лабораторий до русскоязычных докладов про навигацию, пагинацию и AI.\n🍎 iOS / SwiftUI # Inside Apple Intelligence and Xcode: Special Presentation — обзор новых возможностей Apple Intelligence прямо в Xcode. WWDC26: SwiftData Group Lab — групповая лаборатория WWDC26 по SwiftData. WWDC26: Machine Learning \u0026amp; AI Group Lab — ML и AI лаборатория с командой Apple. Safari Style Swipeable Tabbar Using SwiftUI — реализация свайпаемого таббара в стиле Safari. Adaptive TabView for iOS 27 (iPhone) Using SwiftUI — адаптивный TabView для iOS 27. Build a Swift Terminal Developer Toolkit with TUIkit — строим терминальный тулкит для разработчика на Swift. Малоизвестный приём ASO: как смена основного языка в App Store расширяет индексацию — лайфхак по ASO-оптимизации. Как перевезти 250+ SPM модулей из динамики в статику и не сойти с ума — практика миграции SPM модулей. 🤖 Android / Kotlin # Android 17 AOSP is here — что нового в Android 17 AOSP. What\u0026rsquo;s new in Jetpack Compose — обзор новинок Jetpack Compose. What\u0026rsquo;s new in Android XR development — разработка для Android XR. Lifecycle 2.11 Just Changed Android ViewModels FOREVER! — революционные изменения в ViewModel в Lifecycle 2.11. Migrating 25-year-old legacy code to Kotlin — миграция 25-летнего легаси на Kotlin. Building androidskills.dev — как создавался androidskills.dev. What is Android Skills? — что такое Android Skills и как его использовать. История, принципы и концепции библиотеки навигации Decompose — глубокое погружение в Decompose. Пагинация: от идеи до реализации — пагинация на практике. Стратегии ускорения старта на практике — оптимизация времени холодного старта. Адаптация мобильного приложения под ТВ-платформы — адаптация мобилки под Android TV. Баттл codegen-инструментов от разработчиков дизайн-систем — сравнение codegen-инструментов. ⚡ Производительность приложений # App Performance Optimization Tips (I/O Connect \u0026lsquo;26) — большой доклад по оптимизации Android-приложений: память, R8, Bitmap, LMK, старт и UI state.\n2:58 Работа с памятью на Android 9:16 Ограничения памяти в Android 17 10:12 Мониторинг и профилирование 15:24 R8 и оптимизация памяти 20:34 Работа с растровыми изображениями и управление изображениями 27:48 Отказоустойчивость и демон Low Memory Killer 30:24 Отслеживание завершений работы приложений и нагрузки на память 35:06 Ускорение запуска приложения с помощью профилей 36:06 Сохранение состояния UI с помощью rememberSaveable (Compose) 🤝 Алгоритмы и карьера # Как пройти алгоритмическое собеседование в Яндексе — советы по подготовке к алго-интервью. 🧠 AI в разработке # Kotlin Benchmark for AI Coding Agents — JetBrains — JetBrains выпустили публичный бенчмарк для AI-агентов на реальных Kotlin-задачах из open-source проектов. 105 задач, верификация в контейнерах, основан на SWE-bench. Внедрение AI в инженерные команды — как внедрить AI в команду разработчиков. Эволюция скилов в агентской разработке: сценарии использования и сравнение с МСР — агентская разработка и MCP. Почему AI-агенты тормозят в больших кодовых базах из-за grep и как это исправить — проблема поиска в больших репозиториях. Оживляем мобильное приложение с помощью LLM-агента — интеграция LLM в мобильное приложение. Обратная сторона AI в мобильной разработке — критический взгляд на AI в мобилке. Как ИИ меняет разработчика, менеджера и всю индустрию — Денис Неклюдов — интервью о трансформации индустрии. Why AI Will Likely Make Your Codebase BETTER — почему AI делает кодовую базу лучше. Build a Self-Healing CI/CD Pipeline with AI — самовосстанавливающийся CI/CD пайплайн с AI. ","date":"17 July 2026","externalUrl":null,"permalink":"/til/mobile/mobile-dev-videos-2026-07/","section":"Сегодня я узнал","summary":"","title":"Видео и подкасты недели: мобильная разработка","type":"til"},{"content":"","date":"July 16, 2026","externalUrl":null,"permalink":"/en/series/about-rust/","section":"Series","summary":"","title":"About-Rust","type":"series"},{"content":"","date":"July 16, 2026","externalUrl":null,"permalink":"/en/tags/advanced/","section":"Tags","summary":"","title":"Advanced","type":"tags"},{"content":"","date":"July 16, 2026","externalUrl":null,"permalink":"/en/categories/","section":"Categories","summary":"","title":"Categories","type":"categories"},{"content":"","date":"July 16, 2026","externalUrl":null,"permalink":"/en/tags/closures/","section":"Tags","summary":"","title":"Closures","type":"tags"},{"content":" Introduction # Closures (or lambda expressions) are anonymous functions that we can create on the fly and pass to other functions. But their main superpower is the ability to capture environment, meaning they remember variables from the scope where they were declared.\nIn programming languages with automatic garbage collection (GC), developers don\u0026rsquo;t even think about how closures store these variables. But in Rust, where memory is managed by the compiler, the rules for closures are governed by strict ownership and borrowing semantics.\nIn this article, we will break down how closures work in Rust, how they differ from ordinary functions, how they are divided into Fn, FnMut, and FnOnce traits, and how the compiler generates them \u0026ldquo;under the hood.\u0026rdquo;\n1. Function Pointers (fn) # Before we talk about closures, let\u0026rsquo;s recall ordinary functions.\nIn Rust, a function\u0026rsquo;s name can act as a pointer to it. The type of such a pointer is written in lowercase: for example, fn(i32) -\u0026gt; i32.\nHow it is structured in memory: The fn pointer is a \u0026ldquo;thin\u0026rdquo; pointer. It stores only one address — the address of the first instruction of the compiled code in memory. Such functions have no state. They cannot look at outer variables or read anything that wasn\u0026rsquo;t passed to them in the arguments. 2. Closures: Functions with a backpack # A closure is an anonymous function that has an invisible \u0026ldquo;backpack\u0026rdquo; (context) behind its back, where it can put external variables.\nlet coefficient = 10; let multiply = |x| x * coefficient; // The closure has captured coefficient! Closure arguments are written inside two vertical bars |...|, followed by the body. Curly braces are only needed for multi-line code.\nType Inference # The Rust compiler automatically infers what data types the closure accepts and returns, analyzing the place of its first invocation. However, if you want, you can specify the types manually:\nlet multiply = |x: i32| -\u0026gt; i32 { x * coefficient }; Capturing Environment and the move keyword # By default (by reference): The closure tries to be as efficient as possible. It doesn\u0026rsquo;t copy variables; it borrows them. The compiler decides what kind of reference is needed: a shared reference for reading (\u0026amp;T) or a mutable reference for writing (\u0026amp;mut T). Forced move (move): If the closure must outlive the surrounding code (for example, we return it from a function or pass it to another thread), we need to move ownership of the variables inside the closure. For this, write the keyword move before the vertical bars: let print_data = move || println!(\u0026#34;Data: {}\u0026#34;, data); 3. Three Closure Traits: Fn, FnMut, and FnOnce # Depending on what exactly the closure does with the variables in its \u0026ldquo;backpack,\u0026rdquo; it automatically implements one of three traits:\ngraph TD FnOnce --\u003e FnMut FnMut --\u003e Fn style FnOnce fill:#f9f,stroke:#333,stroke-width:2px,color:#000 style FnMut fill:#ccf,stroke:#333,stroke-width:1px,color:#000 style Fn fill:#cfc,stroke:#333,stroke-width:1px,color:#000 FnOnce (The Consumer) What it does: Takes captured variables by value and consumes them (destroys or passes them on). Limitation: Since the data is consumed on the first call, you can call such a closure only once. FnMut (The Mutator) What it does: Modifies captured variables (uses an exclusive reference \u0026amp;mut self). Limitation: Can be called any number of times, but only sequentially (cannot run concurrently from different threads without synchronization). Fn (The Reader) What it does: Only reads data (uses a shared reference \u0026amp;self). Limitation: No limitations! Can be called repeatedly and concurrently from different threads. Trait Hierarchy # They form a nested structure:\nAny closure of type Fn automatically knows how to work as FnMut and FnOnce. Regular function pointers (fn) have no state, so they always implement the top trait Fn. 4. How Closures Work in Code # Passing to Functions # Since the exact type of a closure is anonymous, we use generics or impl Trait:\nfn execute(mut action: impl FnMut()) { action(); } Returning from Functions # You cannot return a closure directly — the compiler doesn\u0026rsquo;t know its exact size. Therefore, we return it via impl Trait with the mandatory move keyword (to take the captured variables with us):\nfn create_adder(x: i32) -\u0026gt; impl Fn(i32) -\u0026gt; i32 { move |y| x + y } Storing in Structs # If a struct needs to store a closure, we have two ways:\nGenerics (fast): The struct is statically bound to the closure type at compile time. No overhead. struct Runner\u0026lt;F: Fn()\u0026gt; { action: F } Box (flexible): The closure is hidden in the heap under a dynamic pointer. Allows storing different closures in the same struct type. struct Runner { action: Box\u0026lt;dyn Fn()\u0026gt; } 5. What happens under the hood? # Closures in Rust are just syntactic sugar. Under the hood, the compiler turns each closure into a regular structure:\nAn anonymous struct is created, whose fields are the captured variables. The implementation of the call (methods of traits call, call_mut, or call_once) is generated for this structure. At the site of the closure call, the compiler simply substitutes a regular method call of this structure. 6. Lifetimes on the fly (HRTBs) # Sometimes a complex situation arises: a closure accepts a reference to an object, but the lifetime of this reference is born and dies right inside the calling function. In such cases, the closure must be able to work with references of any lifetime.\nFor this, Higher-Rank Trait Bounds (or HRTBs) syntax is used via the for\u0026lt;'a\u0026gt; keyword:\nfn run_with_ref\u0026lt;F\u0026gt;(f: F) where F: for\u0026lt;\u0026#39;a\u0026gt; Fn(\u0026amp;\u0026#39;a str) { let local = String::from(\u0026#34;data\u0026#34;); f(\u0026amp;local); // The lifetime of the reference is limited only to this function! } The construction for\u0026lt;'a\u0026gt; Fn(\u0026amp;'a str) tells the compiler: \u0026ldquo;This closure knows how to accept a string reference with absolutely any lifetime 'a that we give it at the moment of call.\u0026rdquo;\nLet\u0026rsquo;s see how closures of different types work in the interactive examples below:\nClosures in Rust ← Step 1/4 → Run ▶ 1fn call_with_five(f: fn(i32) -\u0026gt; i32) { 2 println!(\u0026#34;Вызов fn pointer: {}\u0026#34;, f(5)); 3} 4 5fn main() { 6 // 1. Указатель на обычную функцию 7 fn add_one(x: i32) -\u0026gt; i32 { x + 1 } 8 call_with_five(add_one); 9 10 // 2. Замыкание с захватом внешнего окружения 11 let coefficient = 10; 12 let closure = |x| x * coefficient; 13 14 // call_with_five(closure); // Ошибка! Замыкание с состоянием не приводится к fn pointer. 15 println!(\u0026#34;Вызов замыкания: {}\u0026#34;, closure(5)); 16} 17 Указатели на функции и замыкания\nУказатель на функцию fn является \u0026ldquo;тонким\u0026rdquo; указателем: он хранит только адрес машинного кода. Замыкание (анонимная функция) может захватывать внешние переменные и нести с собой состояние. Замыкания получают уникальный анонимный тип, генерируемый компилятором. Execution result: \u0026times; 1fn call_fn\u0026lt;F: Fn()\u0026gt;(f: F) { f(); } 2fn call_fn_mut\u0026lt;F: FnMut()\u0026gt;(mut f: F) { f(); } 3fn call_fn_once\u0026lt;F: FnOnce()\u0026gt;(f: F) { f(); } 4 5fn main() { 6 let mut greeting = \u0026#34;Hello\u0026#34;.to_string(); 7 8 // Fn: только читает окружение 9 let closure_fn = || println!(\u0026#34;Fn: {}\u0026#34;, greeting); 10 call_fn(closure_fn); 11 12 // FnMut: изменяет окружение 13 let closure_mut = || { 14 greeting.push_str(\u0026#34; World\u0026#34;); 15 println!(\u0026#34;FnMut: {}\u0026#34;, greeting); 16 }; 17 call_fn_mut(closure_mut); 18 19 // FnOnce: поглощает (перемещает) владение greeting 20 let closure_once = move || { 21 let consumed = greeting; 22 println!(\u0026#34;FnOnce: {}\u0026#34;, consumed); 23 }; 24 call_fn_once(closure_once); 25 // call_fn_once(closure_once); // Ошибка! Переменная closure_once уже израсходована. 26} 27 Трейты Fn, FnMut и FnOnce\nFn: захват по разделяемой ссылке \u0026amp;self. Допускает вызовы в нескольких потоках параллельно. FnMut: захват по эксклюзивной ссылке \u0026amp;mut self. Позволяет модифицировать состояние. FnOnce: поглощает состояние. Может быть вызвано ровно один раз. Execution result: \u0026times; 1fn get_counter(mut start: u32) -\u0026gt; impl FnMut() -\u0026gt; u32 { 2 move || { 3 start += 1; 4 start 5 } 6} 7 8fn main() { 9 let mut next = get_counter(100); 10 println!(\u0026#34;Счетчик: {}\u0026#34;, next()); 11 println!(\u0026#34;Счетчик: {}\u0026#34;, next()); 12} 13 Возврат замыканий из функций\nТак как типы замыканий анонимны, мы используем синтаксис impl Trait. Мы обязаны использовать ключевое слово move, чтобы передать владение захваченными переменными. Иначе возвращаемое замыкание ссылалось бы на локальные переменные функции, которые уничтожаются при выходе. Execution result: \u0026times; 1// ?hidden:start 2fn process_borrowed\u0026lt;F\u0026gt;(f: F) 3where 4 F: for\u0026lt;\u0026#39;a\u0026gt; Fn(\u0026amp;\u0026#39;a str), 5{ 6 let local_string = String::from(\u0026#34;Локальные данные\u0026#34;); 7 f(\u0026amp;local_string); // Ссылка живет только внутри функции 8} 9// ?hidden:end 10 11fn main() { 12 // Замыкание принимает ссылку на строку, время жизни которой определено внутри process_borrowed 13 let print_len = |s: \u0026amp;str| println!(\u0026#34;Длина строки: {}\u0026#34;, s.len()); 14 process_borrowed(print_len); 15} 🚀 Слайд 4\nExecution result: \u0026times; Check Your Knowledge! # Take a short interactive test to reinforce your understanding of closures in Rust.\n","date":"July 16, 2026","externalUrl":null,"permalink":"/en/rust/rust-closures/","section":"Rust","summary":"","title":"Closures in Rust: Functions with their own Memory","type":"rust"},{"content":"","date":"July 16, 2026","externalUrl":null,"permalink":"/en/tags/dev/","section":"Tags","summary":"","title":"Dev","type":"tags"},{"content":"","date":"July 16, 2026","externalUrl":null,"permalink":"/en/categories/rust/","section":"Categories","summary":"","title":"Rust","type":"categories"},{"content":"","date":"July 16, 2026","externalUrl":null,"permalink":"/en/rust/","section":"Rust","summary":"","title":"Rust","type":"rust"},{"content":"","date":"July 16, 2026","externalUrl":null,"permalink":"/en/tags/rust/","section":"Tags","summary":"","title":"Rust","type":"tags"},{"content":"","date":"July 16, 2026","externalUrl":null,"permalink":"/en/series/","section":"Series","summary":"","title":"Series","type":"series"},{"content":"","date":"July 16, 2026","externalUrl":null,"permalink":"/en/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":"July 16, 2026","externalUrl":null,"permalink":"/en/","section":"Welcome to my blog! 🎉","summary":"","title":"Welcome to my blog! 🎉","type":"page"},{"content":"","date":"16 July 2026","externalUrl":null,"permalink":"/tags/errors/","section":"Tags","summary":"","title":"Errors","type":"tags"},{"content":" Введение # Ошибки в коде случаются у всех. Вопрос лишь в том, как язык программирования помогает нам с ними справляться. За годы развития IT-индустрии разработчики перепробовали много разных подходов: от простых возвращаемых циферок-кодов до сложных и тяжелых исключений.\nRust пошел своим путем. Вместо скрытых падений и неявного поведения он предлагает нам два четких инструмента: невосстановимые ошибки (когда продолжать работу бессмысленно) и восстановимые ошибки через специальные типы-контейнеры Option\u0026lt;T\u0026gt; и Result\u0026lt;T, E\u0026gt;.\nДавайте разберем, как работают ошибки в разных языках, научимся пользоваться методами стандартной библиотеки Rust и разберемся, зачем нужны сторонние библиотеки вроде thiserror и anyhow.\n1. Как с ошибками борются в других языках? # Язык C: Коды ошибок # В C функции обычно возвращают числовой код ошибки (например, -1 при сбое и 0 при успехе), а полезные данные передаются через указатели в аргументах.\nint get_data(input in, output *out) { if (something_wrong) { return -1; // Ошибка } return 0; // Все ок } В чем проблема: Разработчик может легко проигнорировать возвращаемый код и работать дальше с пустыми данными. Успешный результат и ошибка смешиваются в рамках одного и того же типа int. C++, Java, C#, Python, JS: Исключения (Exceptions) # В этих языках для сообщения об ошибке используют конструкцию throw / raise, которая прерывает выполнение кода и летит вверх, пока кто-то не поймает её в блок catch (или except).\nvoid process() { try { do_work(); } catch (const std::exception\u0026amp; e) { // Ловим ошибку здесь } } В чем проблема: Ошибку все еще легко случайно проигнорировать, написав пустой блок catch. Исключения сильно замедляют программу в момент их выброса (оверхед на раскрутку стека). Сигнатура функции не говорит о том, какие именно исключения она может выбросить. Код становится непредсказуемым. Язык Go: Возврат пары значений # В Go функции возвращают кортеж (результат, ошибка).\nres, err := doSomething() if err != nil { return nil, err } В чем проблема: Ошибку легко заглушить, присвоив ее символу _. Приходится писать огромное количество шаблонного кода if err != nil на каждом шагу. Haskell, Scala, TypeScript # Используют так называемые алгебраические типы данных (ADT) и обертки для значений (Option/Either). Именно эта идея легла в основу Rust, позволив сделать обработку ошибок безопасной и красивой.\n2. Невосстановимые ошибки: Паника (panic!) # Иногда в программе происходит то, к чему мы вообще не готовы: например, выход за границы массива или деление на ноль. В Rust такие ситуации приводят к панике.\nЧто такое паника: Это мгновенная остановка выполнения текущего потока. Главное правило: Программа не должна пытаться обрабатывать панику в обычном коде. Лучшее решение при панике — упасть, чтобы не испортить данные в базе данных или файлы на диске. Как вызвать: С помощью макроса panic!(\u0026quot;Сообщение об ошибке\u0026quot;) или методов извлечения значения .unwrap() / .expect(), если они вызваны на пустом значении. 3. Восстановимые ошибки: Обертки Option и Result # Если ошибка ожидаема (например, файл не найден или пользователь ввел буквы вместо числа), мы используем типы-контейнеры.\nТип Option\u0026lt;T\u0026gt; # Используется, когда значение может просто отсутствовать.\npub enum Option\u0026lt;T\u0026gt; { None, // Значения нет Some(T), // Значение есть и внутри лежит тип T } Благодаря этому, компилятор заставит вас обработать случай None перед тем, как вы сможете получить доступ к данным внутри Some.\nТип Result\u0026lt;T, E\u0026gt; # Используется, когда операция может завершиться ошибкой, и нам важно знать её причину.\npub enum Result\u0026lt;T, E\u0026gt; { Ok(T), // Успех, внутри лежит результат типа T Err(E), // Сбой, внутри лежит ошибка типа E } Тип Result помечен специальным атрибутом #[must_use]. Если функция возвращает Result, а вы проигнорируете его (не сохраните в переменную и не обработаете), компилятор выдаст предупреждение.\n4. Шпаргалка по методам Option и Result # Для работы со значениями внутри Option и Result стандартная библиотека предоставляет множество удобных методов.\nГруппа методов Для Option\u0026lt;T\u0026gt; Для Result\u0026lt;T, E\u0026gt; Что делает / Нюансы Жёсткое извлечение (Паника при сбое) unwrap() expect(\u0026amp;str) unwrap() expect(\u0026amp;str) unwrap_err() Достают значение. Если внутри None/Err — программа падает. В expect можно передать сообщение для лога. unwrap_err достает ошибку, паникуя при успехе. Безопасное извлечение по умолчанию unwrap_or(default) unwrap_or_else(f) unwrap_or_default() unwrap_or(default) unwrap_or_else(f) unwrap_or_default() Возвращают значение или дефолтный вариант. В _or_else дефолт вычисляется лениво через функцию f. Простые проверки is_some() is_none() is_ok() is_err() Возвращают true или false. Не забирают владение значением. Трансформация содержимого map(f) map(f) map_err(f) Применяют функцию f к значению внутри, если оно есть. Противоположное состояние не трогают. map_err меняет только тип ошибки. Логические цепочки (И) and(opt) and_then(f) and(res) and_then(f) and_then (часто называют flat_map) принимает функцию, которая возвращает новый Option или Result. Позволяет выстраивать цепочки без вложенности. Логические цепочки (ИЛИ) or(opt) or_else(f) or(res) or_else(f) Возвращают оригинал, если он успешный. Если там None/Err — возвращают альтернативу. Преобразования типов transpose() ok_or(err) ok_or_else(f) transpose() ok() err() transpose меняет вложенность: Option\u0026lt;Result\u0026gt; превращает в Result\u0026lt;Option\u0026gt;. ok_or превращает Option в Result, заменяя None на указанную ошибку. ok() и err() делают из Result опциональные значения. 5. Лаконичный проброс ошибок: Оператор ? # Чтобы не писать постоянно конструкции match или if let, в Rust есть оператор ?. Он ставится в конце выражения, возвращающего Option или Result.\nlet parsed = val_str.parse::\u0026lt;i32\u0026gt;()?; Как он работает:\nЕсли внутри Result лежит Ok, оператор ? просто достает чистое значение и код идет дальше. Если внутри лежит Err, выполнение функции немедленно прекращается, и эта ошибка возвращается наверх в вызывающий код. Магия приведения типов: Под капотом компилятор автоматически вызывает метод From::from(error). Это значит, что если ваша функция возвращает большую общую ошибку, а локальная функция вернула маленькую специфичную ошибку, Rust сам превратит её в нужный тип, если для них реализована конвертация. 6. Создание своих типов ошибок # В реальных проектах нам часто нужно детально описывать свои ошибки.\nСтандартный трейт std::error::Error # Любой наш тип ошибки должен реализовывать трейты форматирования:\nDebug — технический вывод для логов (выводится через {:?}, легко сгенерировать с помощью #[derive(Debug)]). Display — красивый текст для людей (выводится через {}). Библиотеки-помощники # Чтобы не писать тонны рутинного кода для вывода ошибок, разработчики используют готовые крейты:\nanyhow (для приложений): Отлично подходит для конечных программ (application-level). Позволяет свести все возможные ошибки в проекте к одному типу anyhow::Error и не расписывать их вручную. thiserror (для библиотек): Идеально подходит для библиотек и переиспользуемых модулей. Автоматически создает красивый вывод ошибок (Display) и приведения типов на основе простых аннотаций прямо над перечислением. snafu: Еще один мощный инструмент для гибкой настройки ошибок с сохранением контекста. Давайте посмотрим, как работать с Option, Result, оператором ? и библиотекой thiserror на интерактивных примерах кода:\nОбработка ошибок в Rust ← Шаг 1/3 → Запуск ▶ 1fn main() { 2 let raw_input: Option\u0026lt;\u0026amp;str\u0026gt; = Some(\u0026#34;42\u0026#34;); 3 4 // Преобразуем строку в число и умножаем на 2 5 let processed = raw_input 6 .and_then(|s| s.parse::\u0026lt;i32\u0026gt;().ok()) 7 .map(|num| num * 2); 8 9 println!(\u0026#34;Обработанное значение: {:?}\u0026#34;, processed); 10} 11 Комбинаторы Option и Result\nВместо матчинга можно лаконично преобразовывать данные с помощью map и and_then. Метод map применяет функцию к значению внутри Some или Ok. Метод and_then принимает замыкание, возвращающее новый Option/Result — удобно для цепочки зависимых операций. Результат выполнения: \u0026times; 1// ?hidden:start 2use std::num::ParseIntError; 3 4fn parse_and_double(val_str: \u0026amp;str) -\u0026gt; Result\u0026lt;i32, ParseIntError\u0026gt; { 5 // Если parse вернет Err, выполнение прервется и вернется Err 6 let parsed = val_str.parse::\u0026lt;i32\u0026gt;()?; 7 Ok(parsed * 2) 8} 9// ?hidden:end 10 11fn main() { 12 match parse_and_double(\u0026#34;21\u0026#34;) { 13 Ok(res) =\u0026gt; println!(\u0026#34;Успех: {}\u0026#34;, res), 14 Err(e) =\u0026gt; println!(\u0026#34;Ошибка разбора: {}\u0026#34;, e), 15 } 16 17 if parse_and_double(\u0026#34;invalid\u0026#34;).is_err() { 18 println!(\u0026#34;Функция корректно вернула ошибку при невалидном вводе.\u0026#34;); 19 } 20} 21 Оператор проброса ошибок ?\nОператор ? разворачивает Ok(T) или немедленно возвращает Err(E) из текущей функции. Он также автоматически приводит типы ошибок с помощью типажа From (From::from). Это позволяет прозрачно трансформировать локальные ошибки в общую ошибку функции. Результат выполнения: \u0026times; 1// ?hidden:start 2use thiserror::Error; 3 4#[derive(Error, Debug)] 5enum DataError { 6 #[error(\u0026#34;Ошибка ввода-вывода при обращении к файлу: {0}\u0026#34;)] 7 Disconnect(#[from] std::io::Error), 8 9 #[error(\u0026#34;Некорректный формат данных в строке: {0}\u0026#34;)] 10 InvalidFormat(String), 11} 12 13fn read_data() -\u0026gt; Result\u0026lt;String, DataError\u0026gt; { 14 // Симулируем чтение с ошибкой формата 15 Err(DataError::InvalidFormat(\u0026#34;Не хватает заголовка\u0026#34;.to_string())) 16} 17// ?hidden:end 18 19fn main() { 20 match read_data() { 21 Ok(data) =\u0026gt; println!(\u0026#34;Данные: {}\u0026#34;, data), 22 Err(e) =\u0026gt; println!(\u0026#34;Произошла ошибка: {}\u0026#34;, e), 23 } 24} 25 Кастомные ошибки с thiserror\nКрейт thiserror генерирует реализации трейта Error и форматирования Display. Мы аннотируем варианты макросом #[error(...)] для пользовательского вывода. Атрибут #[from] автоматически реализует типаж From для конвертации ошибок. Результат выполнения: \u0026times; Проверь свои знания! # Пройдите короткий интерактивный тест, чтобы закрепить понимание обработки ошибок в Rust.\n","date":"16 July 2026","externalUrl":null,"permalink":"/rust/rust-errors-handling/","section":"Rust","summary":"","title":"Обработка ошибок в Rust: паника, Option, Result и как с ними дружить","type":"rust"},{"content":"","date":"16 July 2026","externalUrl":null,"permalink":"/tags/theory/","section":"Tags","summary":"","title":"Theory","type":"tags"},{"content":" Введение # В мире программирования типы данных — это не просто скучные ярлыки, которые говорят компьютеру, сколько памяти выделить под переменную. Система типов определяет то, как мы пишем код, как думаем о логике нашей программы и насколько спокойно мы спим по ночам, не боясь внезапных падений в продакшене.\nВ этой статье мы простыми словами разберем, как устроена система типов, чем Rust отличается от других языков и как крутая фича под названием Type-Driven Development (проектирование через типы) помогает нам переложить заботу о логических ошибках на плечи компилятора.\n1. Как языки работают с типами? # Все системы типов в языках программирования можно разложить по двум осям: когда происходит проверка и насколько строгие правила.\ngraph TD A[Система типов] --\u003e B[Когда проверяем?] A --\u003e C[Насколько строгие правила?] B --\u003e B1[При компиляции Статическая] B --\u003e B2[Во время работы Динамическая] C --\u003e C1[Никакой магии Строгая / Сильная] C --\u003e C2[Авто-преобразования Слабая] style B1 fill:#f9f,stroke:#333,stroke-width:2px,color:#000 style C1 fill:#f9f,stroke:#333,stroke-width:2px,color:#000 Проверка типов: до запуска или во время работы? # Статическая типизация (проверка ДО запуска):\nКак это работает: Компилятор проверяет типы всех переменных и аргументов функций еще на этапе сборки. Если вы попытаетесь передать строку туда, где ожидается число, программа просто не соберется. Где используется: Rust, Go, C++, C#, Java, Haskell. Плюсы: Ошибки ловятся моментально на вашем компьютере, а не у пользователя. Плюс программа работает быстрее, ведь в процессе работы не нужно тратить время на проверку типов. Динамическая типизация (проверка ВО ВРЕМЯ работы):\nКак это работает: Переменным все равно, что хранить. Типы проверяются на лету, прямо во время выполнения программы. Где используется: Python, JavaScript, Ruby, PHP. Плюсы: Код пишется очень быстро и гибко, не нужно описывать типы. Минусы: Легко пропустить опечатку или передать не тот объект, и программа упадет посреди ночи у реального клиента. Гибриды: Некоторые динамические языки добавляют подсказки типов (type hints), как в Python. Они помогают редактору кода подсвечивать ошибки, но сам интерпретатор Python во время работы их игнорирует. В языке Julia типы проверяются динамически, но оптимизируются для высокой скорости.\nПравила игры: строгие или мягкие? # Строгая (сильная) типизация:\nСуть: Язык не делает никаких поблажек и неявных преобразований. Если вам нужно сложить целое число 42 и вещественное 3.14, вам придется явно сказать компилятору привести их к общему знаменателю. Никакого «само рассосется». Где используется: Rust, Python. Слабая типизация:\nСуть: Язык пытается «помочь» и неявно преобразует типы на ходу. Например, в JavaScript сложение строки \u0026quot;5\u0026quot; и числа 10 даст строку \u0026quot;510\u0026quot;, а в языке C компилятор может молча привести указатель к числу. Где используется: JavaScript, C. Note Разделение на строгую и слабую типизацию — это не четкое «да» или «нет», а целый спектр. Но Rust находится на самом строгом его краю: здесь компилятор бьет по рукам за любые попытки смешать несовместимые типы без вашего явного указания.\n2. Проектирование через типы (Type-Driven Development) # Благодаря строгой статической типизации в Rust, компилятор работает как автоматический контролер качества. Мы можем спроектировать наши типы данных так, чтобы некорректное состояние программы было невозможно написать физически. Это называется принципом Make illegal states unrepresentable.\nПример из жизни: Этапы заказа # Представьте, что мы пишем логику интернет-магазина. Мы не хотим, чтобы программа случайно отправила клиенту заказ, который еще не оплачен.\nВ других языках мы бы писали проверки:\nif order.status == Status::Paid { ship(order); } else { // Что делать, если статус не тот? Ошибка в рантайме? } В Rust мы можем пойти другим путем — создать отдельные типы для каждого состояния заказа:\nNewOrder (Новый заказ) PaidOrder (Оплаченный заказ) ShippedOrder (Отправленный заказ) Мы пишем функцию ship(order: PaidOrder) -\u0026gt; ShippedOrder. Эта функция физически не примет NewOrder! Попытка передать туда новый неоплаченный заказ вызовет ошибку компиляции. Нам не нужно писать юнит-тесты на эту логику — компилятор сам гарантирует, что шаги процесса нельзя перепутать.\nДавайте посмотрим, как это выглядит в коде, на интерактивных слайдах ниже:\nСистема типов и Type-Driven Development ← Шаг 1/3 → Запуск ▶ 1// ?compile_fail 2fn main() { 3 let integer: i32 = 42; 4 let float: f64 = 3.14; 5 6 // Ошибка! Типы не соответствуют друг другу. 7 let result = integer + float; 8} 9 Строгая типизация: Неявное приведение\nВ Rust отсутствует автоматическое (неявное) приведение типов. Попытка сложить целое число i32 и вещественное f64 приведет к ошибке компилятора E0308. Это предотвращает случайные потери точности в вычислениях. Результат выполнения: \u0026times; 1fn main() { 2 let integer: i32 = 42; 3 let float: f64 = 3.14; 4 5 let result = (integer as f64) + float; 6 println!(\u0026#34;Результат сложения: {}\u0026#34;, result); 7} 8 Строгая типизация: Явное приведение\nДля безопасного приведения типов используется оператор as. Мы явно приводим i32 к f64 перед выполнением сложения. Теперь компилятор уверен в наших намерениях, и код успешно выполняется. Результат выполнения: \u0026times; 1// ?hidden:start 2struct NewOrder { 3 id: u64, 4} 5 6struct ShippedOrder { 7 id: u64, 8 tracking_number: String, 9} 10 11impl NewOrder { 12 fn new(id: u64) -\u0026gt; Self { 13 NewOrder { id } 14 } 15 16 fn ship(self, tracking: \u0026amp;str) -\u0026gt; ShippedOrder { 17 ShippedOrder { 18 id: self.id, 19 tracking_number: tracking.to_string(), 20 } 21 } 22} 23 24impl ShippedOrder { 25 fn deliver(self) { 26 println!(\u0026#34;Заказ {} успешно доставлен с трек-номером {}!\u0026#34;, self.id, self.tracking_number); 27 } 28} 29// ?hidden:end 30 31fn main() { 32 let order = NewOrder::new(1024); 33 34 // 1. Мы не можем вызвать order.deliver() напрямую — компилятор выдаст ошибку! 35 // 2. Сначала мы должны перевести заказ в состояние отправленного (ShippedOrder): 36 let shipped = order.ship(\u0026#34;RU-777-XYZ\u0026#34;); 37 38 // 3. Теперь мы можем безопасно вызвать доставку: 39 shipped.deliver(); 40} 41 Type-Driven Development: Безопасное состояние\nМы кодируем правила логики программы в типах NewOrder и ShippedOrder. Метод ship определен только для NewOrder, возвращая ShippedOrder. Метод deliver определен только для ShippedOrder. Ошибиться невозможно: компилятор не позволит вызвать доставку для нового неотправленного заказа. Результат выполнения: \u0026times; Проверь свои знания! # Пройдите короткий интерактивный тест, чтобы закрепить понимание классификации систем типов и Type-Driven Development.\n","date":"16 July 2026","externalUrl":null,"permalink":"/rust/rust-type-system/","section":"Rust","summary":"","title":"Система типов в Rust: почему компилятор — ваш лучший друг","type":"rust"},{"content":"","date":"15 July 2026","externalUrl":null,"permalink":"/tags/rust-basic/","section":"Tags","summary":"","title":"Rust-Basic","type":"tags"},{"content":" Введение. Что такое кортеж # В языке Rust есть несколько базовых композитных типов, позволяющих группировать значения. Один из наиболее простых и часто используемых — это кортеж (tuple). Кортежи особенно удобны, когда нужно вернуть несколько значений из функции без создания отдельной структуры.\nОпределение и синтаксис # Кортеж — это композитный сложный тип данных фиксированной длины. После объявления его размер нельзя изменить. Синтаксические правила: Определяются путем перечисления типов через запятую внутри круглых скобок (). Состав типов: Перечисляемые в кортеже типы могут быть как абсолютно одинаковыми, так и полностью различаться. Вложенность: Допускается создание вложенных кортежей (кортеж как элемент другого кортежа). Давайте разберём различные варианты объявления кортежей, способов доступа к их элементам и работу с единичным типом на интерактивных слайдах ниже:\nРабота с кортежами в Rust ← Шаг 1/3 → Запуск ▶ 1fn main() { 2 // Пример кортежа с разными типами 3 let t1: (u32, u8, f64) = (500, 1, 6.28); 4 5 // Пример кортежа с одинаковыми типами 6 let t2: (u32, u32, u32) = (10, 20, 30); 7 8 // Пример вложенного кортежа 9 let t3: (u32, (u64, u8), u32) = (100, (50, 2), 200); 10 11 println!(\u0026#34;t1: {:?}\u0026#34;, t1); 12 println!(\u0026#34;t2: {:?}\u0026#34;, t2); 13 println!(\u0026#34;t3: {:?}\u0026#34;, t3); 14} Объявление кортежей в Rust\nКортеж создается путем перечисления значений через запятую в круглых скобках (). Типы элементов могут полностью отличаться (t1) или быть одинаковыми (t2). Кортежи могут содержать другие кортежи в качестве элементов (t3), формируя вложенные структуры. Результат выполнения: \u0026times; 1fn main() { 2 let x: (i32, f64, u8) = (500, 6.4, 1); 3 4 // Доступ к элементам по порядковому номеру (индексу) 5 let five_hundred = x.0; 6 let six_point_four = x.1; 7 let one = x.2; 8 9 println!(\u0026#34;Элементы: {}, {}, {}\u0026#34;, five_hundred, six_point_four, one); 10 11 // Деструктуризация кортежа 12 let (a, b, c) = x; 13 println!(\u0026#34;Деструктуризация: a = {}, b = {}, c = {}\u0026#34;, a, b, c); 14} Доступ к элементам и деструктуризация\nДоступ к конкретным элементам кортежа осуществляется через «точку» и его индекс: x.0, x.1, x.2. Индексация начинается с нуля. Кортеж также можно разобрать на отдельные переменные с помощью деструктуризации: let (a, b, c) = x;. Результат выполнения: \u0026times; 1// ?hidden:start 2#[allow(unused_variables)] 3// ?hidden:end 4fn main() { 5 // Пример блока кода, вычисляющегося в единичный тип (): 6 let x: () = { 7 println!(\u0026#34;Печать внутри блока кода\u0026#34;); 8 // Выражение с точкой с запятой в конце возвращает () 9 }; 10 11 // Функция, не возвращающая значения, возвращает () 12 let y: () = implicit_unit(); 13 14 println!(\u0026#34;x: {:?}\u0026#34;, x); 15 println!(\u0026#34;y: {:?}\u0026#34;, y); 16} 17 18// Функция неявно возвращает () 19fn implicit_unit() { 20 // Нет выражения возврата 21} Единичный тип (Unit-type)\nПустой кортеж () представляет собой единичный тип. Блоки кода, заканчивающиеся точкой с запятой ;, вычисляются в (). Функции, не имеющие явного возвращаемого типа, неявно возвращают (). Результат выполнения: \u0026times; Доступ к элементам и вывод типажей # Доступ к конкретному элементу кортежа осуществляется через «точку» и его порядковый номер (индекс), начиная с нуля: например, x.0, x.1. Компилятор Rust автоматически выводит (реализует) некоторые стандартные типажи (traits) для кортежей (такие как Debug, Clone, PartialEq и т.д.), если все типы, входящие в состав кортежа, также реализуют эти типажи. Крайний случай: Пустой кортеж (Unit-тип) # Пустой кортеж записывается как (). Альтернативное название: «Единичный» тип (unit type). Свойства и поведение: Функции, у которых явно не указан возвращаемый тип (ничего не возвращают), на самом деле неявно возвращают пустой кортеж (). Ряд выражений языка вычисляются в пустой кортеж. К ним относятся циклы (например, for), а также блоки кода, завершающиеся выражением с точкой с запятой ;. Проверь свои знания! # Пройдите короткий интерактивный тест, чтобы закрепить понимание кортежей в Rust.\n","date":"15 July 2026","externalUrl":null,"permalink":"/rust/rust-tuples/","section":"Rust","summary":"","title":"Кортежи (Tuples) в Rust","type":"rust"},{"content":" Введение # Писать безопасный и быстрый код — это здорово, но иногда полезно понимать, как именно наши данные располагаются на физическом уровне в оперативной памяти. В Rust компилятор берет на себя управление памятью, оптимизируя размеры типов и выравнивание. В этой статье мы разберем основные понятия Type Layout в Rust.\nПоведение компилятора по умолчанию # Управление памятью: Компилятор Rust автоматически рассчитывает и задает типу такие размеры (size) и выравнивание (alignment), которые гарантированно удовлетворяют техническим требованиям всех его внутренних полей на целевой архитектуре процессора. Паддинг (Padding): Компилятор имеет право автоматически вставлять неявные байты заполнения (паддинг) в структуру для обеспечения корректного выравнивания данных в памяти. Переупорядочивание полей: По умолчанию Rust не гарантирует сохранение порядка полей, написанного в коде. Компилятор оставляет за собой право произвольно менять порядок полей в кортежах, структурах и перечислениях для оптимизации размера и минимизации паддинга. Давайте изучим выравнивание, размеры типов, использование атрибутов представления #[repr(...)] и работу с тегами перечислений на интерактивных слайдах ниже:\nРасположение типов в памяти в Rust ← Шаг 1/3 → Запуск ▶ 1use std::mem::{size_of, align_of}; 2 3#[allow(dead_code)] 4struct Foo { 5 a: u8, 6 b: u32, 7 c: u16, 8} 9 10fn main() { 11 println!(\u0026#34;Размер Foo: {} байт\u0026#34;, size_of::\u0026lt;Foo\u0026gt;()); 12 println!(\u0026#34;Выравнивание Foo: {} байт\u0026#34;, align_of::\u0026lt;Foo\u0026gt;()); 13 14 // Если бы поля лежали в памяти строго по порядку (a, b, c): 15 // 1 байт (a) + 3 байта паддинга + 4 байта (b) + 2 байта (c) + 2 байта паддинга = 12 байт. 16 // Однако компилятор Rust переупорядочил поля в памяти (b, c, a): 17 // 4 байта (b) + 2 байта (c) + 1 байт (a) + 1 байт паддинга = 8 байт! 18} Выравнивание и размер по умолчанию\nКомпилятор Rust автоматически вставляет байты заполнения (padding) для правильного выравнивания. По умолчанию Rust свободно меняет порядок полей в памяти для минимизации размера структуры. Для Foo размер уменьшается с 12 до 8 байт благодаря оптимизации переупорядочивания полей. Результат выполнения: \u0026times; 1use std::mem::size_of; 2 3#[repr(C)] 4#[allow(dead_code)] 5struct StructC { 6 a: u8, 7 b: u32, 8 c: u16, 9} 10 11#[repr(align(16))] 12#[allow(dead_code)] 13struct StructAlign { 14 a: u32, 15} 16 17#[repr(transparent)] 18#[allow(dead_code)] 19struct Wrapper(u32); 20 21fn main() { 22 println!(\u0026#34;Размер StructC (repr(C)): {} байт\u0026#34;, size_of::\u0026lt;StructC\u0026gt;()); 23 println!(\u0026#34;Размер StructAlign (repr(align(16))): {} байт\u0026#34;, size_of::\u0026lt;StructAlign\u0026gt;()); 24 println!(\u0026#34;Размер Wrapper (repr(transparent)): {} байт\u0026#34;, size_of::\u0026lt;Wrapper\u0026gt;()); 25} Атрибуты представления #[repr(\u0026hellip;)]\n#[repr(C)] выстраивает поля строго по порядку объявления (как в C/C++), запрещая переупорядочивание. #[repr(align(N))] задает минимальное выравнивание структуры в памяти (в байтах). #[repr(transparent)] гарантирует, что структура-обертка имеет точно такое же представление, как и ее единственное поле. Результат выполнения: \u0026times; 1#[repr(u32)] // Задаем тип тега (дискриминанта) в памяти как u32 2#[allow(dead_code)] 3enum Status { 4 Active = 42, 5 Inactive = 24, 6 Pending = 134, 7} 8 9fn main() { 10 let val = Status::Active; 11 12 // Приведение перечисления к целому числу с помощью оператора `as` 13 let num = val as u32; 14 15 println!(\u0026#34;Тег Status::Active в памяти равен: {}\u0026#34;, num); 16} Дискриминанты перечислений\nМы можем явно задавать числовые значения (дискриминанты) вариантам enums. Атрибут #[repr(u32)] (или другие целочисленные типы) фиксирует тип хранения тега в памяти. Варианты можно приводить к соответствующему числу с помощью оператора as. Результат выполнения: \u0026times; Детали реализации и шаги: # Шаг 1. Выравнивание и размер по умолчанию:\nКаждому типу данных требуется выравнивание в памяти: адрес начала данных должен быть кратен выравниванию типа (обычно степени двойки: 1, 2, 4, 8 байт). Из-за этого между полями могут образовываться пустые байты — паддинг. По умолчанию (представление repr(Rust)) компилятор переупорядочивает поля структуры Foo так, чтобы они занимали 8 байт вместо 12 байт, размещая сначала самые строгие по выравниванию типы. Шаг 2. Управление расположением данных с помощью атрибутов: Для явного изменения логики размещения данных в памяти и обеспечения совместимости с другими языками используются специальные атрибуты представления:\n#[repr(C)] — отключает оптимизацию переупорядочивания полей Rust и выстраивает их строго в том порядке, в котором они объявлены (совместимо с ABI языка C). #[repr(align(N))] — принудительно устанавливает минимальное выравнивание типа в памяти (например, 16 байт). #[repr(transparent)] — гарантирует, что тип имеет точно такое же представление в памяти и ABI, как и его единственное некорректное поле (используется для newtype-оберток). Шаг 3. Явное тегирование перечислений (Enum Discriminants):\nДля перечислений можно жестко фиксировать тип числового тега (дискриминанта) в памяти и присваивать явные числовые значения каждому варианту. Это позволяет осуществлять безопасное приведение типов (кастинг) из перечисления в целочисленный тип с помощью ключевого слова as. Проверь свои знания! # Пройдите короткий интерактивный тест, чтобы закрепить понимание расположения типов данных в памяти.\n","date":"15 July 2026","externalUrl":null,"permalink":"/rust/rust-type-layout/","section":"Rust","summary":"","title":"Низкоуровневое представление типов в памяти в Rust (Type Layout)","type":"rust"},{"content":" Введение # Написание выразительного кода требует лаконичных инструментов управления потоком выполнения. В Rust для борьбы с глубокой вложенностью кода (так называемым «callback hell» или «лестницей скобок») используются мощные операторы:\nОператор ? — для быстрой передачи ошибок и отсутствия значений наверх по стеку вызовов. Конструкция let-else — для безопасного развертывания опровержимых шаблонов с гарантированным расходящимся выходом в случае неудачи. Давайте изучим работу этих операторов на интерактивных примерах кода:\nОператоры let-else и ? ← Шаг 1/2 → Запуск ▶ 1fn find_number(option: Option\u0026lt;i32\u0026gt;) -\u0026gt; Option\u0026lt;i32\u0026gt; { 2 // Оператор ? возвращает значение внутри Some(x), либо делает досрочный возврат None 3 let num = option?; 4 Some(num * 2) 5} 6 7fn parse_and_double(s: \u0026amp;str) -\u0026gt; Result\u0026lt;i32, std::num::ParseIntError\u0026gt; { 8 // Оператор ? возвращает i32, либо немедленно пробрасывает ошибку Err вверх 9 let val = s.parse::\u0026lt;i32\u0026gt;()?; 10 Ok(val * 2) 11} 12 13fn main() { 14 let result_opt = find_number(Some(21)); 15 let none_opt = find_number(None); 16 17 let result_res = parse_and_double(\u0026#34;10\u0026#34;); 18 let err_res = parse_and_double(\u0026#34;abc\u0026#34;); 19 20 println!(\u0026#34;opt success: {:?}, opt none: {:?}\u0026#34;, result_opt, none_opt); 21 println!(\u0026#34;res success: {:?}, res err: {:?}\u0026#34;, result_res, err_res); 22} Оператор ? для пропагации ошибок\nОператор ? используется для быстрого извлечения значений из Option и Result. Для Option: если результат Some(x), извлекает x; если None, делает досрочный возврат None из функции. Для Result: если результат Ok(x), извлекает x; если Err(e), делает досрочный возврат Err(e.into()). Результат выполнения: \u0026times; 1fn handle_event(event: Option\u0026lt;String\u0026gt;) { 2 // let-else извлекает значение из Some, либо выполняет расходящийся блок else 3 let Some(name) = event else { 4 println!(\u0026#34;Событие не содержит имени, завершаем функцию.\u0026#34;); 5 return; // Обязательный расходящийся выход 6 }; 7 8 println!(\u0026#34;Обработка события: {}\u0026#34;, name); 9} 10 11fn main() { 12 handle_event(Some(\u0026#34;Click\u0026#34;.to_string())); 13 handle_event(None); 14 15 // Пример let-else в цикле с continue 16 for i in 0..5 { 17 let opt = if i % 2 == 0 { Some(i) } else { None }; 18 19 let Some(val) = opt else { 20 continue; // Пропускаем нечетные числа 21 }; 22 println!(\u0026#34;Четное число: {}\u0026#34;, val); 23 } 24} Конструкция let-else (Diverging let)\nlet-else позволяет сопоставить опровержимый шаблон (например, Some(x)) и связать переменную в текущей области видимости. Если шаблон не совпал, выполняется блок else, который обязан быть расходящимся (diverging). Расходящийся блок else не имеет права просто вернуть управление; он обязан завершиться с помощью return, break, continue или panic!(). Результат выполнения: \u0026times; Оператор ? (Error \u0026amp; Option Propagation) # Оператор ? используется для быстрой пропагации (передачи наверх) ошибок или отсутствующих значений. Он разворачивает успешный результат, а в случае неудачи — осуществляет немедленный досрочный возврат из текущей функции.\n1. Работа с типом Option # Если результатом выражения является Some(r), оператор ? извлекает и возвращает r. Если результат None, происходит досрочный выход из функции с возвратом None.\n// Эквивалентные конструкции: fn foo() -\u0026gt; Option\u0026lt;u32\u0026gt; { let value = bar()?; // Использование оператора ? Some(value) } // Что происходит под капотом: let value = match bar() { Some(r) =\u0026gt; r, None =\u0026gt; return None, }; 2. Работа с типом Result # Если результатом является Ok(r), возвращается r. Если Err(err), происходит досрочный возврат из функции значения Err(err.into()) (с автоматическим приведением типов ошибок через типаж From/Into).\n// Эквивалентные конструкции: fn foo() -\u0026gt; Result\u0026lt;u32, Error\u0026gt; { let value = bar()?; // Использование оператора ? Ok(value) } // Что происходит под капотом: let value = match bar() { Ok(r) =\u0026gt; r, Err(err) =\u0026gt; return Err(err.into()), }; Конструкция let - else (Diverging let) # Синтаксис let-else позволяет связать переменную по шаблону в случае успеха, либо выполнить расходящийся (diverging) блок кода в случае неудачи.\nСинтаксис: let pattern = expression else { ... };. Обратите внимание на обязательное наличие точки с запятой ; после закрывающей фигурной скобки. Критическое правило блока else: Блок else обязан быть расходящимся (diverging), то есть он не имеет права возвращать управление в текущую область видимости обычным путем. Он обязан завершаться операторами: return (выход из функции) break (выход из цикла) continue (переход на следующую итерацию цикла) panic!() (завершение потока с ошибкой) Различие шаблонов # Обычный let работает только с неопровержимыми (irrefutable) шаблонами, которые всегда совпадают (например, кортежи, ссылки или простые переменные). Для опровержимых (refutable) шаблонов, таких как Some(x), необходим let-else или if let.\n// Примеры неопровержимых шаблонов для классического let: let a = 1 + 2; let (a, b) = some_tuple; // Использование let-else для опровержимых шаблонов: let Some(x) = some_option else { return; }; let Some(x) = some_option else { break; }; let Some(x) = some_option else { continue; }; let Some(x) = some_option else { panic!(); }; Практический пример обработки потока событий в цикле # Использование let-else значительно уменьшает уровень вложенности кода по сравнению с match или if let:\nloop { // Если get_event() вернул Err, прерываем цикл let Ok(event) = get_event() else { break; }; // Если event равен None, переходим к следующей итерации let Some(event) = event else { continue; }; // Переменная event гарантированно развернута и валидна в этой точке handle_event(event); } Проверь свои знания! # Пройдите короткий интерактивный тест, чтобы закрепить понимание let-else и ?.\n","date":"15 July 2026","externalUrl":null,"permalink":"/rust/rust-flow-control/","section":"Rust","summary":"","title":"Операторы let-else и пропагации ошибок в Rust","type":"rust"},{"content":" Введение. Что такое перечисление # Перечисления (Enums) в Rust — это тип данных, который позволяет описать сущность, принимающую одно из нескольких возможных значений (вариантов).\nЕсли вы раньше программировали, например, на Java или C++, то наверняка пользовались enums для создания списка констант (например, дней недели). Но в Rust перечисления намного мощнее! Они позволяют хранить разнородные данные внутри каждого варианта, благодаря чему код становится чистым и безопасным.\nВарианты конструирования (внутренний синтаксис) # В отличие от многих других языков, где перечисления — это просто список именованных целых чисел, в Rust варианты enums могут содержать в себе данные различного типа и структуры. Один enum может содержать три вида вариантов:\nЮнит-варианты: без связанных данных (аналогично юнит-структурам). Кортежные варианты: со связанными безымянными данными в круглых скобках (аналогично структурам-кортежам). Структурные варианты: с именованными полями в фигурных скобках {} (аналогично обычным структурам). Давайте посмотрим, как объявляются варианты, что такое незаселенный тип и как извлечь данные с помощью match на интерактивных слайдах ниже:\nРабота с перечислениями в Rust ← Шаг 1/3 → Запуск ▶ 1#[allow(dead_code)] 2#[derive(Debug)] 3enum WebEvent { 4 // Юнит-вариант (без связанных данных) 5 PageLoad, 6 // Кортежный вариант (со связанными безымянными данными) 7 KeyPress(char), 8 // Структурный вариант (с именованными полями) 9 Click { x: i64, y: i64 }, 10} 11 12fn main() { 13 let load = WebEvent::PageLoad; 14 let press = WebEvent::KeyPress(\u0026#39;q\u0026#39;); 15 let click = WebEvent::Click { x: 320, y: 240 }; 16 17 println!(\u0026#34;load: {:?}\u0026#34;, load); 18 println!(\u0026#34;press: {:?}\u0026#34;, press); 19 println!(\u0026#34;click: {:?}\u0026#34;, click); 20} Варианты конструирования перечислений\nПеречисления (enums) могут объединять данные разных форматов под одним типом. Юнит-варианты не несут связанных данных (PageLoad). Кортежные варианты хранят неименованные данные в круглых скобках (KeyPress). Структурные варианты хранят именованные поля в фигурных скобках (Click). Результат выполнения: \u0026times; 1// Определение незаселенного типа (uninhabited type) 2// Перечисление без вариантов 3#[allow(dead_code)] 4enum EmptyEnum {} 5 6fn main() { 7 // Экземпляр EmptyEnum создать невозможно, так как нет вариантов! 8 println!(\u0026#34;EmptyEnum определен, но не может быть инициализирован.\u0026#34;); 9} Незаселенные типы (Uninhabited Types)\nПеречисление без единого варианта называется незаселенным типом. Создать экземпляр такого типа в безопасном коде невозможно. Используется в системе типов для обозначения невозможных ситуаций (например, ошибки, которая никогда не произойдет). Результат выполнения: \u0026times; 1#[allow(dead_code)] 2enum Message { 3 Quit, 4 Move { x: i32, y: i32 }, 5 Write(String), 6} 7 8fn main() { 9 let msg = Message::Move { x: 10, y: 20 }; 10 11 // Извлечение данных через сопоставление паттернов (pattern matching) 12 match msg { 13 Message::Quit =\u0026gt; { 14 println!(\u0026#34;Событие выхода\u0026#34;); 15 } 16 Message::Move { x, y } =\u0026gt; { 17 println!(\u0026#34;Перемещение в координаты x = {}, y = {}\u0026#34;, x, y); 18 } 19 Message::Write(text) =\u0026gt; { 20 println!(\u0026#34;Запись сообщения: {}\u0026#34;, text); 21 } 22 } 23} Извлечение данных с помощью match\nМеханизм match гарантирует безопасный доступ к данным внутри вариантов enums. Компилятор Rust требует обязательной обработки всех вариантов перечисления (исчерпывающее сопоставление). Данные деструктурируются прямо в шаблоне ветки match. Результат выполнения: \u0026times; Детали реализации: # Шаг 1. Варианты конструирования: Мы создали перечисление WebEvent, демонстрирующее все три типа вариантов. Важная деталь: все поля и данные внутри вариантов перечисления автоматически являются публичными, писать pub перед ними не нужно. Шаг 2. Пустые перечисления: В Rust можно объявить enum вообще без вариантов (например, enum EmptyEnum {}). Создать объект такого типа в обычном коде невозможно. Зачем это нужно? Обычно так помечают состояния, которые никогда не должны произойти физически, помогая компилятору оптимизировать проверки. Шаг 3. Достаем данные (Pattern Matching): Так как разные варианты перечисления могут содержать абсолютно разные типы данных, компилятор не разрешит обратиться к ним напрямую. Нам нужно проверить, какой именно вариант сейчас активен. Самый удобный способ сделать это — конструкция match. Важная деталь: Конструкция match в Rust является исчерпывающей (exhaustive). Это означает, что компилятор выдаст ошибку, если хотя бы один из вариантов enums не будет обработан в ветках match.\nПроверь свои знания! # Пройдите короткий интерактивный тест, чтобы закрепить понимание перечислений в Rust.\n","date":"15 July 2026","externalUrl":null,"permalink":"/rust/rust-enums/","section":"Rust","summary":"","title":"Перечисления (Enums) в Rust","type":"rust"},{"content":" Введение. Блок кода (Code Block) и выражения # Блок кода в Rust является фундаментальной синтаксической единицей. Он не просто группирует инструкции, но и сам по себе является выражением, возвращающим значение.\nСинтаксис: Ограничивается фигурными скобками { ... }. Внутри блока может содержаться набор инструкций, завершающихся точкой с запятой ;. Возвращаемое значение: Если последняя строка блока является выражением без точки с запятой в конце, её результат становится возвращаемым значением всего блока кода. Если последняя строка завершается точкой с запятой ; (или блок кода пуст), блок возвращает тип () (пустой кортеж / unit-тип). Давайте изучим блоки кода, синтаксис if-else и сопоставления на интерактивных слайдах:\nВетвление и блоки кода ← Шаг 1/3 → Запуск ▶ 1fn main() { 2 // Блок кода вычисляется в значение последнего выражения 3 let y = { 4 let x = 1; 5 let z = 2; 6 x + z // Нет точки с запятой в конце -\u0026gt; возвращает 3 7 }; 8 9 // Блок кода завершающийся точкой с запятой возвращает () 10 let u = { 11 let x = 10; 12 println!(\u0026#34;x = {}\u0026#34;, x); 13 }; 14 15 println!(\u0026#34;y = {}, u = {:?}\u0026#34;, y, u); 16} Блоки кода (Code Blocks)\nБлок кода ограничивается фигурными скобками {}. Если последнее выражение блока не заканчивается ;, его результат возвращается из блока. Блок кода, оканчивающийся на ;, или пустой блок возвращает unit-тип (). Результат выполнения: \u0026times; 1fn main() { 2 let x = 150; 3 4 // В Rust if-else является выражением и может возвращать значение 5 let y = if x \u0026gt; 100 { 6 2024 7 } else { 8 2025 9 }; 10 11 println!(\u0026#34;y = {}\u0026#34;, y); 12 13 // ОШИБКА КОМПИЛЯЦИИ: типы веток if-else должны быть одинаковыми! 14 // let z = if x \u0026gt; 100 { 2024 } else { \u0026#34;2025\u0026#34; }; 15} Условные выражения if-else\nКруглые скобки вокруг условия ставить не нужно (например, if x \u0026gt; 100 вместо if (x \u0026gt; 100)). Условие обязано возвращать строго логический тип bool. Ветви if и else обязаны возвращать значения одного и того же типа. В Rust нет классического тернарного оператора, его роль играет if-else. Результат выполнения: \u0026times; 1fn main() { 2 let some_val: Option\u0026lt;i32\u0026gt; = Some(150); 3 4 // Использование if let для простого сопоставления с шаблоном 5 if let Some(x) = some_val { 6 println!(\u0026#34;Совпало! x = {}\u0026#34;, x); 7 } 8 9 let another_val: Option\u0026lt;i32\u0026gt; = Some(50); 10 11 // Начиная с Rust 1.88 поддерживаются цепочки if let с \u0026amp;\u0026amp; (let_chains) 12 if let Some(x) = some_val \u0026amp;\u0026amp; let Some(y) = another_val \u0026amp;\u0026amp; x \u0026gt; y { 13 println!(\u0026#34;Обе переменные извлечены, и x ({}) \u0026gt; y ({})\u0026#34;, x, y); 14 } 15} Конструкция if let и цепочки условий\nif let — лаконичный аналог match для проверки соответствия конкретному шаблону. Переменная, связанная в шаблоне, видна только внутри блока if let. В современных версиях Rust (начиная с 1.88) можно комбинировать несколько let и логических выражений через \u0026amp;\u0026amp;. Результат выполнения: \u0026times; Условные конструкции и ветвление # Базовое выражение if и else # Конструкция if проверяет истинность условия и выполняет соответствующую ветку кода.\nПравила синтаксиса: Выражение условия не нужно оборачивать в круглые скобки ( ). Использование круглых скобок вокруг самого выражения условия (например, if (a \u0026gt; 5)) считается нарушением стиля. Результат условного выражения строго обязан иметь тип bool. Использование целых чисел (как в C/C++) приведет к ошибке компиляции. // ОШИБКА КОМПИЛЯЦИИ: count имеет тип отличный от bool if count { ... } // КОРРЕКТНО: выражения возвращают bool if count \u0026gt; 0 { ... } if count != 0 { ... } Операторы сравнения и логические операторы # Используются для формирования условных выражений:\n\u0026gt; (больше), \u0026gt;= (больше либо равно) \u0026lt; (меньше), \u0026lt;= (меньше либо равно) == (равенство), != (неравенство) \u0026amp;\u0026amp; (логическое И), || (логическое ИЛИ) if как выражение (Assigning from if) # В Rust if-else является выражением, а значит, может возвращать значение, которое можно присвоить переменной.\nКритическое правило типов: Все ветви конструкции if-else при использовании в качестве выражения обязаны возвращать один и тот же тип данных. В Rust нет классического тернарного оператора ? :. Вместо него используется if-else. // Корректно: обе ветки возвращают i32 let y = if x \u0026gt; 100 { 2024 } else { 2025 }; // ОШИБКА КОМПИЛЯЦИИ (Несовпадение типов: i32 и \u0026amp;str): let y = if x \u0026gt; 100 { 2024 } else { \u0026#34;2025\u0026#34; }; Если ветка else отсутствует, то выражение if обязано возвращать unit-тип (), так как если условие не выполнится, результатом по умолчанию будет ():\nlet u = if x \u0026gt; 100 { foo() }; // Допустимо, только если foo() возвращает () Конструкция if let и сопоставление с шаблоном # Базовый if let # Используется как лаконичная альтернатива match, когда нас интересует только один конкретный шаблон.\n// Для Option if let Some(x) = foo_opt() { bar(x); // x доступен только внутри этого блока } else { // Выполняется, если результат None } Цепочки if let (let-chains) # Начиная с версии Rust 1.88, поддерживается синтаксис объединения нескольких let-шаблонов и логических условий через оператор \u0026amp;\u0026amp; в одном выражении if.\n// Сопоставление с шаблоном + дополнительное ограничение на переменную x if let Some(x) = foo() \u0026amp;\u0026amp; x \u0026gt; 100 { todo!() } // Множественные сопоставления с шаблонами в одном условии if let Some(x) = foo() \u0026amp;\u0026amp; let Some(y) = bar() \u0026amp;\u0026amp; x \u0026gt; y { todo!() } Проверь свои знания! # Пройдите короткий интерактивный тест, чтобы закрепить понимание блоков кода и ветвлений в Rust.\n","date":"15 July 2026","externalUrl":null,"permalink":"/rust/rust-conditionals/","section":"Rust","summary":"","title":"Условные конструкции и ветвление в Rust","type":"rust"},{"content":" Введение # В языке Rust есть три основных вида циклов: while, loop и for. Для управления итерациями используются стандартные операторы:\nbreak — немедленно прерывает выполнение цикла. continue — пропускает оставшуюся часть тела цикла и переходит к следующей итерации. Давайте детально рассмотрим все виды циклов, способы обхода коллекций и работу с метками на интерактивных слайдах:\nЦиклы в Rust ← Шаг 1/3 → Запуск ▶ 1fn main() { 2 let mut n = 0; 3 // Цикл while 4 while n \u0026lt; 3 { 5 println!(\u0026#34;while: n = {}\u0026#34;, n); 6 n += 1; 7 } 8 9 // Цикл loop с возвратом значения через break 10 let mut counter = 0; 11 let result = loop { 12 counter += 1; 13 if counter == 5 { 14 break counter * 2; // Возвращает 10 из цикла loop 15 } 16 }; 17 println!(\u0026#34;Результат loop: {}\u0026#34;, result); 18 19 // Симуляция do-while через loop 20 let mut x = 0; 21 loop { 22 x += 1; 23 println!(\u0026#34;do-while: x = {}\u0026#34;, x); 24 if x \u0026gt;= 3 { 25 break; 26 } 27 } 28} Циклы while, loop и возврат значений\nwhile выполняет тело, пока истинно логическое условие. loop представляет собой бесконечный цикл. Выражение loop может возвращать значение наружу: для этого нужно указать его после оператора break. Отсутствующий в Rust цикл do-while можно сымитировать проверкой условия выхода в конце бесконечного loop. Результат выполнения: \u0026times; 1fn main() { 2 let arr = [10, 20, 30]; 3 4 // Итерация по ссылкам (\u0026amp;arr не потребляет коллекцию) 5 for val in \u0026amp;arr { 6 println!(\u0026#34;Ссылка: {}\u0026#34;, val); 7 } 8 9 // Итерация с получением индекса через .enumerate() 10 for (idx, val) in arr.into_iter().enumerate() { 11 println!(\u0026#34;Индекс {}: элемент {}\u0026#34;, idx, val); 12 } 13 14 let mut mut_arr = [1, 2, 3]; 15 // Изменяемая итерация для модификации элементов 16 for val in \u0026amp;mut mut_arr { 17 *val *= 10; 18 } 19 println!(\u0026#34;Измененный массив: {:?}\u0026#34;, mut_arr); 20} Диапазоны, цикл for и итерация\nЦикл for итерируется по объектам, реализующим типаж IntoIterator. for item in \u0026amp;arr выполняет неизменяемое заимствование (итерируется по ссылкам \u0026amp;T). for item in \u0026amp;mut arr позволяет изменять элементы на месте. Метод .enumerate() возвращает кортеж (index, item) для одновременного доступа к счетчику и элементу. Результат выполнения: \u0026times; 1fn main() { 2 // Метки циклов позволяют управлять внешними циклами из вложенных 3 \u0026#39;outer: for i in 1..=3 { 4 for j in 1..=3 { 5 if i * j \u0026gt; 4 { 6 println!(\u0026#34;Выход из внешнего цикла при i={}, j={}\u0026#34;, i, j); 7 break \u0026#39;outer; 8 } 9 } 10 } 11 12 // Метки именованных блоков кода 13 let val = \u0026#39;tag: { 14 let x = 10; 15 if x \u0026gt; 5 { 16 break \u0026#39;tag 100; // Досрочный выход из блока с возвратом значения 100 17 } 18 x 19 }; 20 println!(\u0026#34;Значение из блока: {}\u0026#34;, val); 21} Метки циклов и блоков (Labels)\nМетки начинаются с апострофа (например, 'outer) и позволяют управлять внешними циклами с помощью break или continue. Обычные блоки кода {} также могут иметь метки. Из них можно досрочно выйти с помощью break 'tag и вернуть значение. Использование break без метки внутри обычного блока кода (не цикла) вызовет ошибку компиляции. Результат выполнения: \u0026times; Виды циклов в Rust # 1. Цикл while # Выполняет тело цикла до тех пор, пока условие истинно (true).\nlet mut n = 0; while n \u0026lt; 10 { foo(n); n += 1; } 2. Цикл while let # Повторяет выполнение цикла, пока выражение успешно сопоставляется с шаблоном (например, для разбора Option или Result).\n// Цикл работает, пока foo() возвращает Some(x) while let Some(x) = foo() { bar(x); } 3. Цикл loop и возврат значений # Бесконечный цикл, который выполняется до тех пор, пока не будет встречен явный оператор break.\nИспользование как выражения: loop может возвращать значение через оператор break. Это значение можно присвоить переменной. let event = loop { let event = get_event(); if let Some(e) = event { break e; // Возвращает значение e из цикла loop } }; 4. Реализация цикла do ... while # В Rust нет встроенного ключевого слова do-while. Его поведение можно сымитировать двумя способами:\nЧерез бесконечный loop и проверку условия в конце: loop { let x = foo(); if x == 0 { break; } } Используя блок кода в качестве условия цикла while: while { let x = foo(); x != 0 // Условие продолжения цикла } {} // Пустое тело цикла Диапазоны (Ranges) и цикл for # Типы диапазонов (Range Types) # Диапазоны представляют собой структуры из модуля std::ops. Нижняя граница (если она указана) всегда включается в диапазон.\nСинтаксис Описание типа Включение границ Внутреннее представление .. Неограниченный диапазон Нет границ RangeFull ..10 Ограниченный сверху диапазон Исключая верхнюю границу (0-9) RangeTo ..=10 Ограниченный сверху диапазон, включительно Включая верхнюю границу (0-10) RangeToInclusive 0.. Ограниченный снизу диапазон От 0 до бесконечности RangeFrom 0..10 Ограниченный диапазон Включая 0, исключая 10 (0-9) Range 0..=10 Ограниченный диапазон, включительно Включая обе границы 0 и 10 (0-10) RangeInclusive Пример: Цикл for n in 1..10 выполнится ровно 9 раз (для значений от 1 до 9 включительно).\nАнатомия цикла for # Цикл for используется для итерации по элементам коллекции или диапазона.\nТребование к типу: Выражение справа от in обязано реализовывать типаж std::iter::IntoIterator. В зависимости от необходимости владения данными или ссылками, применяются разные подходы:\nПотребление коллекции (Move): for item in arr { ... } (неявно вызывает arr.into_iter()). Итерация по разделяемым ссылкам (\u0026amp;T): for item in \u0026amp;arr { ... } (неявно вызывает (\u0026amp;arr).into_iter()). Итерация по изменяемым ссылкам (\u0026amp;mut T): for item in \u0026amp;mut arr { ... } (неявно вызывает (\u0026amp;mut arr).into_iter()). Получение индекса при итерации (.enumerate()) # Для одновременного получения индекса элемента и самого элемента используется метод .enumerate(), который возвращает кортеж вида (index, item).\nlet arr: [u8; 3] = [10, 20, 30]; for (idx, item) in arr.into_iter().enumerate() { println!(\u0026#34;{idx}: {item}\u0026#34;); } Метки циклов и блоков (Labels) # Метки позволяют управлять выполнением конкретного внешнего цикла или блока изнутри вложенных структур. Названия меток всегда начинаются с одиночной кавычки (апострофа), например: 'label.\nМетки в циклах # Применяются для выхода из внешнего цикла при нахождении во вложенном.\n\u0026#39;outer: loop { \u0026#39;inner: loop { if need_exit() { break \u0026#39;outer; // Полный выход из внешнего цикла } } } Метки в именованных блоках кода # Обычные блоки кода { ... } также могут иметь метки. Это позволяет досрочно прерывать выполнение текущего блока и возвращать значение во внешнюю переменную с помощью break.\nlet y = \u0026#39;tag: { let x = foo(); if x \u0026lt; 0 { break \u0026#39;tag 0; // Досрочный выход из блока с возвратом значения 0 } x }; Ограничение компилятора: Оператор break без указания явной метки внутри обычного обособленного блока кода (не являющегося циклом) вызовет ошибку компиляции.\nПроверь свои знания! # Пройдите короткий интерактивный тест, чтобы закрепить понимание циклов в Rust.\n","date":"15 July 2026","externalUrl":null,"permalink":"/rust/rust-loops/","section":"Rust","summary":"","title":"Циклы и диапазоны в Rust","type":"rust"},{"content":" Вступление # В прошлой главе (Каналы в Rust при поддержке Tokio) мы разобрались, как работают каналы, и помогли роботу RUST-Y перевести связь корабля Vectoria на асинхронный tokio::sync::mpsc, избавившись от опасных блокировок главного потока.\nТеперь перед экипажем стоит критически важная задача: состыковаться с дрейфующим медицинским модулем и спасти находящегося там доктора Арчи. Чтобы сделать это безопасно, бортовым системам нужно обрабатывать множество событий одновременно — принимать телеметрию датчиков, слушать ручные команды капитана Новы с мостика и следить за таймером аварийного отключения.\nВ этой главе мы поможем RUST-Y построить настоящую асинхронную систему обработки событий на базе Tokio. Мы разберем:\nКак одновременно слушать разные асинхронные источники с помощью макроса tokio::select!. Как организовать рассылку уведомлений всем системам сразу с помощью tokio::sync::broadcast. Как управлять пулом динамически запускаемых задач и обрабатывать их результаты через tokio::task::JoinSet. В конце статьи вас ждет интерактивный мини-квиз. Внимательно разбирайте примеры кода, чтобы без ошибок ответить на вопросы по select!, broadcast и JoinSet. Пролог. Сближение # Медицинский модуль медленно вращался в холодном свете планеты Лира-Бета. Защитный контур модуля («Страж») перешел в аварийный режим и блокировал любые попытки внешней стыковки, считая их угрозой.\nНа мостике Vectoria Капитан Нова внимательно следил за показателями сближения.\n— «Зори, какая дистанция?» — спросил Нова. — «Триста метров. Сближение стабильное, но если автоматика модуля посчитает наш маневр агрессивным, она задействует магнитные выталкиватели. Мы потеряем контакт навсегда.» — «RUST-Y!» — капитан повернулся к роботу. — «Нам нужен надежный диспетчер событий. Мы должны непрерывно считывать телеметрию сближения, слушать ручные команды отмены от Зори и контролировать таймер безопасности. Если телеметрия покажет перегрузку или придет команда отмены — мы должны среагировать мгновенно.»\nRUST-Y бодро защелкал реле: — «Капитан! Я переписал код. Больше никаких блокировок потока! Я использую асинхронные каналы Tokio. Но как мне объединить их все в один цикл обработки?»\nНова улыбнулся и указал на голографический экран: — «Для этого у нас есть макрос tokio::select!. Он работает как стрелочник на железной дороге — ждет, какая ветка сработает первой, и моментально передает управление туда. Давай посмотрим.»\nЧасть 1. Мультиплексирование и макрос tokio::select! # Когда нам нужно ожидать выполнения нескольких асинхронных операций одновременно, мы используем tokio::select!. Этот макрос принимает список выражений (Futures) и выполняет ветку той Future, которая завершилась первой.\nДавайте посмотрим на схему, которую спроектировал RUST-Y для управления стыковкой:\nСлушаем несколько каналов с tokio::select! Запуск ▶ 1// ?hidden:start 2use tokio::sync::mpsc; 3use tokio::time::{sleep, Duration, interval}; 4 5#[derive(Debug)] 6enum Command { 7 AdjustShields(u32), 8 Evacuate, 9} 10 11#[derive(Debug)] 12enum Telemetry { 13 Temperature(f32), 14 ReactorStatus(bool), 15} 16// ?hidden:end 17 18#[tokio::main] 19async fn main() { 20 let (cmd_tx, mut cmd_rx) = mpsc::channel(10); 21 let (telemetry_tx, mut telemetry_rx) = mpsc::channel(10); 22 23 // Имитируем отправку команд в фоне 24 tokio::spawn(async move { 25 sleep(Duration::from_millis(150)).await; 26 let _ = cmd_tx.send(Command::AdjustShields(80)).await; 27 sleep(Duration::from_millis(250)).await; 28 let _ = cmd_tx.send(Command::Evacuate).await; 29 }); 30 31 // Имитируем отправку телеметрии в фоне 32 tokio::spawn(async move { 33 let _ = telemetry_tx.send(Telemetry::Temperature(36.6)).await; 34 sleep(Duration::from_millis(200)).await; 35 let _ = telemetry_tx.send(Telemetry::ReactorStatus(true)).await; 36 }); 37 38 let mut heartbeat = interval(Duration::from_millis(100)); 39 // Пропустим первый мгновенный тик, чтобы не засорять вывод 40 heartbeat.tick().await; 41 42 loop { 43 tokio::select! { 44 // Читаем команды 45 Some(cmd) = cmd_rx.recv() =\u0026gt; { 46 println!(\u0026#34;[МОСТИК] Получена команда: {:?}\u0026#34;, cmd); 47 if let Command::Evacuate = cmd { 48 println!(\u0026#34;[МОСТИК] Внимание! Начинаем эвакуацию!\u0026#34;); 49 break; 50 } 51 } 52 // Читаем телеметрию 53 Some(tel) = telemetry_rx.recv() =\u0026gt; { 54 println!(\u0026#34;[МОСТИК] Телеметрия: {:?}\u0026#34;, tel); 55 } 56 // Периодический сигнал 57 _ = heartbeat.tick() =\u0026gt; { 58 println!(\u0026#34;[МОСТИК] Системы жизнеобеспечения в норме...\u0026#34;); 59 } 60 } 61 } 62} 63 Мультиплексирование через tokio::select!\nСлушаем несколько источников событий одновременно. Ветки выбираются по мере готовности данных. Работает для каналов, таймеров и любых Future. Результат выполнения: \u0026times; Как это устроено под капотом? # tokio::select! { Some(cmd) = cmd_rx.recv() =\u0026gt; { ... } Some(tel) = telemetry_rx.recv() =\u0026gt; { ... } _ = heartbeat.tick() =\u0026gt; { ... } } Ожидание первого события: tokio::select! опрашивает все указанные ветки. Как только в канале cmd_rx появляется команда, или в telemetry_rx приходит значение, или срабатывает таймер heartbeat.tick(), макрос выполняет соответствующий блок кода. Безопасность при отмене (Cancellation Safety): Это ключевое понятие асинхронного Rust. Когда одна из ветвей tokio::select! завершается, все остальные ветви уничтожаются (dropped). Это означает, что невыбранные Future прекращают свое существование. mpsc::Receiver::recv является безопасным к отмене: если мы удалим Future ожидания сообщения, само сообщение в канале не потеряется — оно останется в буфере до следующей попытки чтения. Внимание: Не все операции безопасны к отмене! Например, чтение из сетевого сокета tokio::io::AsyncReadExt::read при отмене может привести к потере части уже прочитанных байт. Защита от голодания (Starvation): Если бы select! всегда проверял ветки по порядку (сверху вниз), то при высокой частоте сообщений в первом канале до второго канала очередь могла бы никогда не дойти. Чтобы этого избежать, Tokio при одновременной готовности нескольких веток выбирает одну из них псевдослучайным образом. Часть 2. Оповещение всех систем через tokio::sync::broadcast # Стыковка подходила к финальной фазе. Внезапно датчики зафиксировали вспышку на поверхности планеты — в сторону модуля и Vectoria двигалось облако ионизированной плазмы.\n— «Капитан! Нам нужно объявить тревогу и немедленно передать команду подготовки к удару на все модули корабля!» — крикнул Зори. — «RUST-Y, отправь сигнал экстренного старта!» — скомандовал Нова.\nРобот засуетился: — «Но капитан! Наш канал mpsc работает по принципу «один отправитель — один получатель». Если я отправлю команду в mpsc, ее получит либо модуль жизнеобеспечения, либо двигатели, но не оба сразу! Нам нужно разослать сообщение абсолютно всем!»\n— «Для этого используй broadcast канал», — спокойно ответил Нова. — «Он работает как радиостанция: один передает, а слушают все, кто настроен на эту волну.»\nОповещение систем через broadcast-канал Запуск ▶ 1// ?hidden:start 2use tokio::sync::broadcast; 3use tokio::time::{sleep, Duration}; 4// ?hidden:end 5 6#[tokio::main] 7async fn main() { 8 // Создаем broadcast-канал с буфером на 2 сообщения 9 let (tx, mut rx1) = broadcast::channel::\u0026lt;String\u0026gt;(2); 10 let mut rx2 = tx.subscribe(); 11 12 // Первый получатель: Модуль жизнеобеспечения (быстрый) 13 tokio::spawn(async move { 14 loop { 15 match rx1.recv().await { 16 Ok(msg) =\u0026gt; println!(\u0026#34;[ЖИЗНЕОБЕСПЕЧЕНИЕ] Получено: {}\u0026#34;, msg), 17 Err(broadcast::error::RecvError::Closed) =\u0026gt; break, 18 Err(e) =\u0026gt; println!(\u0026#34;[ЖИЗНЕОБЕСПЕЧЕНИЕ] Ошибка: {:?}\u0026#34;, e), 19 } 20 } 21 }); 22 23 // Второй получатель: Двигательный отсек (медленный) 24 tokio::spawn(async move { 25 loop { 26 // Имитируем долгую обработку, из-за чего буфер переполнится 27 sleep(Duration::from_millis(300)).await; 28 match rx2.recv().await { 29 Ok(msg) =\u0026gt; println!(\u0026#34;[ДВИГАТЕЛИ] Получено: {}\u0026#34;, msg), 30 Err(broadcast::error::RecvError::Lagged(n)) =\u0026gt; { 31 println!(\u0026#34;[ДВИГАТЕЛИ] Отстали! Пропущено {} сообщений.\u0026#34;, n); 32 } 33 Err(broadcast::error::RecvError::Closed) =\u0026gt; break, 34 } 35 } 36 }); 37 38 // Отправляем сообщения с мостика 39 sleep(Duration::from_millis(50)).await; 40 let _ = tx.send(\u0026#34;Сигнал 1: Прогрев систем\u0026#34;.to_string()); 41 let _ = tx.send(\u0026#34;Сигнал 2: Калибровка щитов\u0026#34;.to_string()); 42 let _ = tx.send(\u0026#34;Сигнал 3: Запуск реактора\u0026#34;.to_string()); 43 let _ = tx.send(\u0026#34;Сигнал 4: Экстренный старт!\u0026#34;.to_string()); 44 45 // Даем время задачам завершиться 46 sleep(Duration::from_millis(1000)).await; 47} 48 Канал tokio::sync::broadcast\nОтправка сообщений по схеме \u0026ldquo;многие ко многим\u0026rdquo; (Multi-producer Multi-consumer). Если буфер переполняется из-за медленного получателя, Tokio выбрасывает RecvError::Lagged. Идеально подходит для общих событий, таких как сигналы остановки или глобальные уведомления. Результат выполнения: \u0026times; Особенности канала broadcast: # // Каждая подписка создает новый приемник: let mut rx = tx.subscribe(); // При чтении медленный получатель отловит ошибку отставания: Err(RecvError::Lagged(n)) =\u0026gt; { println!(\u0026#34;Пропущено {} сообщений!\u0026#34;, n); } Схема Multi-producer Multi-consumer (MPMC): Любое сообщение, отправленное через Sender, клонируется и доставляется всем активным Receiver. Получатели создаются путем вызова метода tx.subscribe() на строке 4 . Проблема медленного получателя (Lagging): Поскольку буфер канала имеет фиксированный размер (в нашем примере — 2 сообщения), быстрый отправитель может переполнить его, если один из получателей работает медленно. В нашем коде Двигательный отсек имитирует долгую работу через sleep. Когда буфер переполняется, медленный получатель при очередной попытке чтения получит ошибку RecvError::Lagged(n) на строке 5 . Это сигнализирует о том, что n сообщений были пропущены, так как кольцевой буфер перезаписал старые данные. Получатель должен корректно обработать эту ошибку и продолжить чтение актуальных данных. Часть 3. Динамические задачи и tokio::task::JoinSet # Стыковочный шлюз модуля доктора Арчи оказался заблокирован обломком обшивки. Нам нужно запустить несколько автономных дронов-зондов, чтобы они обследовали соседние сектора и нашли спасательный челнок доктора, на котором он мог катапультироваться.\nКоличество зондов может меняться в зависимости от плотности космического мусора.\n— «Если мы запустим зонды и будем ждать их через join_all, нам придется ждать завершения работы последнего, даже если первый найдет челнок за секунду», — заметил Зори. — «Верно», — согласился Нова. — «Нам нужен инструмент, который позволит собирать результаты по мере их готовности. И как только цель будет найдена — мы должны немедленно отозвать (отменить) все остальные зонды, чтобы сберечь энергию.»\nRUST-Y открыл терминал: — «Я знаю! Мы используем tokio::task::JoinSet!»\nПоиск челнока через JoinSet Запуск ▶ 1// ?hidden:start 2use tokio::task::JoinSet; 3use tokio::time::{sleep, Duration}; 4// ?hidden:end 5 6async fn scan_sector(sector_id: u32) -\u0026gt; (u32, \u0026amp;\u0026#39;static str) { 7 // Разное время сканирования секторов для имитации асинхронности 8 let delay = match sector_id { 9 1 =\u0026gt; 150, 10 2 =\u0026gt; 50, 11 3 =\u0026gt; 250, 12 _ =\u0026gt; 100, 13 }; 14 sleep(Duration::from_millis(delay)).await; 15 16 match sector_id { 17 2 =\u0026gt; (sector_id, \u0026#34;Обнаружен спасательный челнок доктора!\u0026#34;), 18 _ =\u0026gt; (sector_id, \u0026#34;Сектор пуст\u0026#34;), 19 } 20} 21 22#[tokio::main] 23async fn main() { 24 let mut set = JoinSet::new(); 25 26 // Запускаем 3 задачи сканирования секторов динамически 27 for sector_id in 1..=3 { 28 set.spawn(scan_sector(sector_id)); 29 } 30 31 // Обрабатываем результаты по мере их завершения 32 while let Some(res) = set.join_next().await { 33 match res { 34 Ok((sector, status)) =\u0026gt; { 35 println!(\u0026#34;[ЗОНД] Сектор {} завершил сканирование: {}\u0026#34;, sector, status); 36 if status.contains(\u0026#34;челнок\u0026#34;) { 37 println!(\u0026#34;[МОСТИК] Цель найдена! Прекращаем сканирование остальных секторов.\u0026#34;); 38 set.abort_all(); // Отменяем все оставшиеся задачи 39 break; 40 } 41 } 42 Err(e) =\u0026gt; println!(\u0026#34;[ОШИБКА] Задача прервана: {:?}\u0026#34;, e), 43 } 44 } 45} 46 Менеджер задач tokio::task::JoinSet\nДинамический пул конкурентных задач, возвращающий результаты по мере их готовности. Позволяет легко прервать все оставшиеся задачи через set.abort_all(). Решает проблему утечки ресурсов при отмене и упрощает сбор результатов по сравнению с join_all. Результат выполнения: \u0026times; Почему JoinSet — это отличное решение? # // Запускаем асинхронную задачу в пул JoinSet: set.spawn(async move { ... }); // Получаем первый готовый результат: if let Some(res) = set.join_next().await { // Отменяем все остальные задачи: set.abort_all(); } Динамический пул задач: Мы можем добавлять новые задачи (Future) прямо во время работы пула с помощью set.spawn(). Получение результатов по готовности: Метод set.join_next().await возвращает результат первой завершившейся задачи. Нам не нужно ждать все остальные. Мгновенная отмена через abort_all(): Если мы нашли то, что искали, вызов set.abort_all() отправляет сигнал отмены (cancellation) всем оставшимся задачам. Это изящно решает проблему утечки ресурсов и лишней работы. Эпилог. Доктор на борту # Зонд №2 прислал координаты спасательного челнока. RUST-Y моментально отменил остальные задачи, а гравитационный луч Vectoria бережно притянул челнок к основному шлюзу.\nЧерез несколько минут доктор Арчи уже пил горячий чай в кают-компании, а Зори и RUST-Y обсуждали новую асинхронную шину событий корабля.\n— «Значит так», — подытожил RUST-Y. — «Если нужно объединить несколько разных источников в один поток — используем tokio::select!. Если нужно разослать сообщение всем системам корабля — берем broadcast. А если запускаем кучу однотипных зондов и хотим гибко ими управлять — нет ничего лучше JoinSet.»\nНова одобрительно кивнул: — «Именно так. Главное — понимать сильные стороны каждого инструмента и не забывать про безопасность отмены.»\nПроверь свои знания! # Давайте проверим, насколько хорошо вы разобрались с отправкой и приемом множества событий в Tokio.\n","date":"14 July 2026","externalUrl":null,"permalink":"/rust/tokio-kak-otpravit-i-prinyat-mnozhestvo-sobytij/","section":"Rust","summary":"","title":"Tokio: как отправить и принять множество событий","type":"rust"},{"content":"There is a joke among photographers that a monopod is just a tripod on a strict diet. Indeed, it is a lightweight, compact, and highly mobile single-legged camera support. It is indispensable when you need to shoot sports or wildlife with heavy telephoto lenses, or simply want to relieve your arms during long hours of event coverage.\nHowever, monopods have a catch: unlike traditional tripods, they cannot stand on their own. If you use them incorrectly, your camera will still sway, leading to blurry, soft photos.\nIn this guide, we will break down the most common beginner mistakes and look at three clever stances to turn your monopod into a rock-solid support.\n❌ The #1 Beginner Mistake # Most amateur photographers stand the monopod completely vertical, directly in front of them.\nThis posture creates a straight vertical line. While the monopod does a great job supporting the heavy weight of the camera, it is useless at countering forward/backward or left/right sway. The slightest gust of wind or your own breathing will introduce micro-shake at slower shutter speeds.\nTo make the monopod work, you must create a rigid triangular system where your own legs and body act as additional points of support.\n🛠 Three Proper Stances # 1. The \u0026ldquo;Human Tripod\u0026rdquo; Stance (Recommended) # This is the most reliable stance, transforming the monopod and your body into a mock tripod:\nStand with your feet shoulder-width apart. Extend the monopod forward and plant the foot on the ground centered in front of you. Lean the monopod slightly back toward yourself, so the camera is pressed firmly against your face/viewfinder. This creates a stable triangle: your two legs + the monopod. The subtle backward tension makes the entire setup incredibly rigid. 2. The Inner Foot Arch Lock # This method is perfect if you are shooting on slippery surfaces like ice, tiles, or mud, where the foot of the monopod might slide around:\nPlace your left foot slightly forward. Anchor the foot of the monopod against the inside arch of your right (rear) shoe. Your shoe acts as a physical stopper. Bring the shaft of the monopod flush against the inside of your left thigh, lightly pressing it with your leg. Lean the camera against your face. Anchored by your shoe and thigh, horizontal sway is reduced to zero. 3. The \u0026ldquo;Arachnid\u0026rdquo; Stance (Crossed Legs) # A popular stance among sports reporters:\nTake a step forward with your left foot. Place the monopod foot behind and to the right of your left shoe (crossing your leg and the monopod). Push the monopod back against your left calf, using your right leg behind for support. This creates a strong lateral tension that eliminates horizontal swaying. 💡 Useful Shooting Tips # Secure the strap: Wrap the camera strap around your wrist or the monopod. If someone trips over the monopod, the camera won\u0026rsquo;t crash to the floor. Use a tilt head: A simple tilt head (which only moves up and down) is ideal for a monopod. To pan left or right, you can simply rotate the monopod itself. Watch your breath: Even with a perfect stance, press the shutter release button smoothly on a half-exhale to minimize hand tremors. A monopod is a wonderful tool for active shooting. Try practicing these stances, and you will find you can easily take sharp, blur-free shots at shutter speeds 2 to 3 stops slower than handheld!\n","date":"April 28, 2026","externalUrl":null,"permalink":"/en/photography/monopod-guide/","section":"Photography","summary":"","title":"How to Use a Monopod Correctly: Secrets to a Sharp Shot","type":"photography"},{"content":"","date":"April 28, 2026","externalUrl":null,"permalink":"/en/tags/monopod/","section":"Tags","summary":"","title":"Monopod","type":"tags"},{"content":"","date":"April 28, 2026","externalUrl":null,"permalink":"/en/categories/photography/","section":"Categories","summary":"","title":"Photography","type":"categories"},{"content":" This section contains articles and thoughts on photography, composition, and shooting techniques, migrated from my old social media group.. Enjoy reading!\n","date":"April 28, 2026","externalUrl":null,"permalink":"/en/photography/","section":"Photography","summary":"","title":"Photography","type":"photography"},{"content":"","date":"April 28, 2026","externalUrl":null,"permalink":"/en/tags/technique/","section":"Tags","summary":"","title":"Technique","type":"tags"},{"content":"","date":"April 28, 2026","externalUrl":null,"permalink":"/en/tags/tips/","section":"Tags","summary":"","title":"Tips","type":"tags"},{"content":"","date":"April 28, 2026","externalUrl":null,"permalink":"/en/tags/tripod/","section":"Tags","summary":"","title":"Tripod","type":"tags"},{"content":"","date":"28 April 2026","externalUrl":null,"permalink":"/tags/%D0%BC%D0%BE%D0%BD%D0%BE%D0%BF%D0%BE%D0%B4/","section":"Tags","summary":"","title":"Монопод","type":"tags"},{"content":"","date":"28 April 2026","externalUrl":null,"permalink":"/tags/%D1%81%D0%BE%D0%B2%D0%B5%D1%82%D1%8B/","section":"Tags","summary":"","title":"Советы","type":"tags"},{"content":"","date":"28 April 2026","externalUrl":null,"permalink":"/tags/%D1%82%D0%B5%D1%85%D0%BD%D0%B8%D0%BA%D0%B0/","section":"Tags","summary":"","title":"Техника","type":"tags"},{"content":"","date":"28 April 2026","externalUrl":null,"permalink":"/tags/%D1%88%D1%82%D0%B0%D1%82%D0%B8%D0%B2/","section":"Tags","summary":"","title":"Штатив","type":"tags"},{"content":"","date":"April 27, 2026","externalUrl":null,"permalink":"/en/tags/equipment/","section":"Tags","summary":"","title":"Equipment","type":"tags"},{"content":"","date":"April 27, 2026","externalUrl":null,"permalink":"/en/tags/experience/","section":"Tags","summary":"","title":"Experience","type":"tags"},{"content":"There is a very popular myth among beginner photographers: “Once I buy an expensive DSLR or mirrorless camera, my photos will instantly become masterpieces!”\nBut reality is harsh. The art of photography is built on understanding light, composition, and knowing how to control your equipment. An expensive camera in the hands of someone who doesn\u0026rsquo;t know how to use it becomes nothing more than a heavy, overpriced point-and-shoot camera with a bunch of confusing buttons.\nIn this post, I want to talk about why experience and practice matter far more than gear, and share one piece of crucial advice that 90% of photo enthusiasts somehow ignore.\n📷 Experience vs. Gear # A great photo can be captured with absolutely any camera—even an old smartphone or a budget camera from a decade ago. What truly matters is:\nUnderstanding light: knowing how to shoot during the golden hour, working with backlighting, or leveraging shadows. Composition: mastering the rule of thirds, leading lines, framing, and capturing the decisive moment. Regular practice: constantly experimenting and learning from your mistakes. Without these skills, even a multi-thousand-dollar camera will produce flat, uninspiring shots. And when expectations fall short, your passion for photography will quickly fade.\n📖 A Photographer\u0026rsquo;s Secret Weapon: The Manual # Do you know what the most underrated book in photography is? Your camera’s instruction manual.\nMost people buy a camera, throw the box and booklets in a closet, and immediately run out to hit the shutter button. As a result, they use a high-end camera like a basic phone in \u0026ldquo;Auto\u0026rdquo; mode, never unlocking its true potential.\nStudying the manual is the easiest way to jumpstart your photography journey. Take some time to figure out the essential controls:\nButtons and Dials: understand what each dial does on the camera body and how to change settings quickly. Flash control: learn how to turn it on, off, and adjust its power so subjects don\u0026rsquo;t look washed out. Focal length: learn how zooming affects perspective and facial proportions. Focusing modes: learn how to switch between single-point autofocus for portraits and continuous tracking autofocus for action shots. Note You don\u0026rsquo;t need to memorize everything. Hold the camera in your hands, open the manual, and test settings step-by-step as you read. This is the fastest way to understand how the menu functions.\nWithout a basic understanding of your gear\u0026rsquo;s settings, you won\u0026rsquo;t be able to step beyond generic snapshots. Master your camera, learn to push it to its limits—and only then will you feel true creative control over your shots.\nGet out there, read the manual, and start practicing! 🚀\n","date":"April 27, 2026","externalUrl":null,"permalink":"/en/photography/practice-and-gear/","section":"Photography","summary":"","title":"Practice Over Pixels: Why You Should Read Your Camera Manual","type":"photography"},{"content":"","date":"27 April 2026","externalUrl":null,"permalink":"/tags/%D0%BE%D0%BF%D1%8B%D1%82/","section":"Tags","summary":"","title":"Опыт","type":"tags"},{"content":"","date":"April 26, 2026","externalUrl":null,"permalink":"/en/tags/color-correction/","section":"Tags","summary":"","title":"Color-Correction","type":"tags"},{"content":"","date":"April 26, 2026","externalUrl":null,"permalink":"/en/tags/processing/","section":"Tags","summary":"","title":"Processing","type":"tags"},{"content":"Have you ever noticed a distracting, unwanted color tint creep into your photos during editing? Most often, it\u0026rsquo;s an unwanted red color cast that ruins skin tones or makes landscapes look unnatural.\nHow do we fix this? Simply dragging the white balance temperature slider can mess up all other colors in the image. This is where the most powerful toning tool comes to the rescue: Curves.\nIn this quick guide, migrated from my old VK group, I will share a simple, time-tested technique to eliminate red color casts and apply a premium color tone using channel-by-channel adjustments.\n🛠 The Curves Toning Recipe # The entire process relies on fine-tuning individual RGB color channels. Instead of moving the master RGB curve (which changes brightness/contrast), we switch to specific color channels.\nStep 1. Compensating Red via the Green Channel # Red and green are complementary colors on the color wheel. To neutralize a red cast, we can boost green:\nSwitch to the Green channel. Click the center of the diagonal line (midtones) to place a point. Slightly nudge it upward. Be extremely gentle—we only need a tiny adjustment, otherwise the image will look muddy green. Step 2. Neutralizing Green and Styling in the Blue Channel # Boosting green might introduce a greenish cast. We balance this and add a beautiful tone in the blue channel:\nSwitch to the Blue channel. Create a subtle S-curve: Pull the shadow point (bottom-left area) slightly down to introduce a warm yellow hue to the dark areas. Push the highlight point (top-right area) slightly up to add a cool blue hue to the bright areas. This neutralizes the excess green and creates a gorgeous color contrast between highlights and shadows. Tip Keyboard Shortcuts: In Adobe Photoshop, you can quickly open the Curves panel by pressing Ctrl + M (on Windows) or Cmd + M (on macOS).\n💡 Why This Works # Adjusting curves allows you to target pixel values within specific tonal ranges. Unlike generic saturation sliders, channel curves let you:\nRemove unwanted tints precisely where they appear (e.g., in midtones, without affecting deep blacks). Add a cinematic contrast by split-toning shadows and highlights separately. Keep transitions between half-tones natural and smooth. Try this simple trick on any image with an annoying warm red cast, and watch it instantly transform into a clean, professional shot!\n","date":"April 26, 2026","externalUrl":null,"permalink":"/en/photography/curves-color-toning/","section":"Photography","summary":"","title":"The Magic of Curves: How to Fix Color Casts and Tone Your Photos","type":"photography"},{"content":"","date":"26 April 2026","externalUrl":null,"permalink":"/tags/%D0%BA%D0%BE%D1%80%D1%80%D0%B5%D0%BA%D1%86%D0%B8%D1%8F/","section":"Tags","summary":"","title":"Коррекция","type":"tags"},{"content":"","date":"26 April 2026","externalUrl":null,"permalink":"/tags/%D0%BE%D0%B1%D1%80%D0%B0%D0%B1%D0%BE%D1%82%D0%BA%D0%B0/","section":"Tags","summary":"","title":"Обработка","type":"tags"},{"content":"","date":"April 25, 2026","externalUrl":null,"permalink":"/en/tags/composition/","section":"Tags","summary":"","title":"Composition","type":"tags"},{"content":"Composition is how the elements of a frame are arranged relative to each other. One of the simplest and most effective ways to improve your shots is to use the rule of thirds.\nWhat is the rule of thirds? # Imagine your frame is divided into a 3x3 grid by two horizontal and two vertical lines. The rule of thirds suggests placing important elements of the scene along these lines or at their intersections (active points).\nNote Most modern cameras and smartphones have a built-in grid display feature. Turn it on in the settings to train your eye right while shooting!\nIntersections (Active Zones) # The four intersection points of the grid lines are considered areas to which the human eye naturally pays attention first:\nPortraits: Place the subject\u0026rsquo;s eyes on one of the top intersection points. Landscapes: Position the horizon line along either the bottom or top grid line (depending on whether you want to emphasize the sky or the ground). Why does it work? # Unlike centered composition, which often feels static and overly formal, shifting the main subject according to the rule of thirds creates a sense of dynamic flow and space. It encourages the viewer\u0026rsquo;s eye to wander around the frame, exploring the details.\nInteractive Simulator: Practice the Rule of Thirds # Try putting your knowledge into practice! Below is an interactive camera simulator. Pan the viewfinder so that a key subject in the frame (either the lighthouse or the sailboat) aligns with one of the dotted grid intersections, then press the shutter release button to snap a photo!\n📸 Simulator: Rule of Thirds Composition Drag the camera with your mouse (or finger), use the mouse wheel or pinch gesture to zoom. Catch the lighthouse or the sailboat in the grid intersections and click the red shutter button to capture your composition and save it as a photo! M • 1/250 • f/8.0 • RAW 🎯 Frame evaluated! 0%\nRetake Download Grid: ON ","date":"April 25, 2026","externalUrl":null,"permalink":"/en/photography/composition-basics/","section":"Photography","summary":"","title":"Composition Basics: Rule of Thirds","type":"photography"},{"content":"","date":"25 April 2026","externalUrl":null,"permalink":"/tags/%D0%BA%D0%BE%D0%BC%D0%BF%D0%BE%D0%B7%D0%B8%D1%86%D0%B8%D1%8F/","section":"Tags","summary":"","title":"Композиция","type":"tags"},{"content":"","date":"20 April 2026","externalUrl":null,"permalink":"/categories/my-gallery/","section":"Categories","summary":"","title":"My-Gallery","type":"categories"},{"content":"","date":"20 April 2026","externalUrl":null,"permalink":"/tags/%D1%84%D0%BE%D1%82%D0%BE/","section":"Tags","summary":"","title":"Фото","type":"tags"},{"content":" Я большой любитель фотографии, иногда даже удается поймать интересный кадр 📸\nНа этой странице будут появляться фото снятые недавно или в прошлом. Они будут размещены в виде галереи из плиток.\nЕщё у меня сохранились статьи из старой группы в ВК по фотографии. Их тоже планирую выложить в блог в скором времени.\nПриятного просмотра!\nВсе Природа Город Макро Улица ","date":"20 April 2026","externalUrl":null,"permalink":"/my-gallery/","section":"Welcome to my blog! 🎉","summary":"","title":"Фотографии","type":"page"},{"content":" Вступление # Часто в приложениях, которые мы разрабатываем, требуется, чтобы различные части системы обменивались данными: один блок кода что-то измеряет, другой собирает статистику, третий принимает решения.\nХочется, чтобы этот обмен был простым, безопасным и предсказуемым. Для этого в Rust существуют каналы. Они позволяют отделить тех, кто отправляет данные (producers), от того, кто принимает и обрабатывает их (consumer).\nВ этой главе мы снова поднимемся на борт корабля Vectoria. Капитан Нова и робот RUST-Y окажутся в ситуации, где от правильного выбора канала зависит успех спасательной операции.\nВместе с героями мы разберём:\nчем std::sync::mpsc отличается от tokio::sync::mpsc, почему блокирующий recv() может остановить выполнение всей программы, как на практике использовать модель «много отправителей — один получатель». В конце статьи вас ждет мини-квиз по каналам: проверим вопросы по mpsc, буферам, отправителям и корректному завершению работы. Пролог. Сигнал # После прохождения через пространственные врата корабль Vectoria дрейфовал у планеты Лира-Бета.\nНа низкой орбите находился поврежденный медицинский модуль, подающий слабый аварийный сигнал.\nПопытка RUST-Y: блокирующий std::sync::mpsc Запуск ▶ 1// ?hidden:start 2use std::sync::mpsc; 3use std::thread; 4use std::time::Duration; 5// ?hidden:end 6 7fn main() { 8 let (tx, rx) = mpsc::channel(); 9 10 // Запускаем поток, имитирующий долгую работу навигатора 11 thread::spawn(move || { 12 println!(\u0026#34;[НАВИГАЦИЯ] Выполняем расчет траектории...\u0026#34;); 13 thread::sleep(Duration::from_millis(500)); 14 let _ = tx.send(\u0026#34;Траектория рассчитана!\u0026#34;.to_string()); 15 }); 16 17 println!(\u0026#34;[МОСТИК] Ждем данные от навигатора...\u0026#34;); 18 19 // Внимание: recv() БЛОКИРУЕТ текущий поток! 20 // Если этот поток управляет всем интерфейсом или рантаймом, 21 // вся программа зависнет в ожидании. 22 match rx.recv() { 23 Ok(msg) =\u0026gt; println!(\u0026#34;[МОСТИК] Успех: {}\u0026#34;, msg), 24 Err(e) =\u0026gt; println!(\u0026#34;[МОСТИК] Ошибка связи: {:?}\u0026#34;, e), 25 } 26 27 println!(\u0026#34;[МОСТИК] Работа продолжается.\u0026#34;); 28} 29 Синхронный блокирующий канал std::sync::mpsc\nВызов rx.recv() полностью останавливает текущий поток ОС до тех пор, пока не придут данные. В асинхронном окружении (например, Tokio) блокировка потока воркера парализует выполнение всех остальных задач на этом потоке. Результат выполнения: \u0026times; Капитан Нова выделил строку с вызовом recv():\n// Поток зависает здесь, если сообщений нет: let msg = rx.recv().unwrap(); — «Вот здесь всё и останавливается. Метод recv() в синхронном канале std::sync::mpsc блокирует текущий поток операционной системы до тех пор, пока не придет сообщение. В простом многопоточном приложении это нормально — один поток засыпает в ожидании, пока другой работает.»\nОн переключил схему на общий контур управления кораблем: — «Но у нас однопоточный асинхронный контекст. Когда ты вызываешь блокирующий recv() в основном потоке воркера, ты говоришь планировщику: \u0026ldquo;Ничего не делай, пока не ответит навигатор\u0026rdquo;. И пока он рассчитывает траекторию, весь корабль замирает: мы не можем ни щиты скорректировать, ни принять экстренную команду отмены.»\nДоктор Арчи добавил: — «Если внутри такой блокировки зависнет система мониторинга состояния жизнеобеспечения, мы узнаем об аварии слишком поздно. Блокирующий вызов в асинхронном коде превращается в угрозу.»\nЧасть 2. Асинхронные каналы Tokio # — «Решение не в том, чтобы отказаться от каналов», — продолжит Нова. — «Нам просто нужен инструмент, созданный специально для асинхронной среды. Такой, который не блокирует поток ОС, а приостанавливает только текущую задачу, уступая время другим.»\nОн переписал код RUST-Y с использованием tokio::sync::mpsc:\nРешение с использованием асинхронного tokio::sync::mpsc Запуск ▶ 1// ?hidden:start 2use tokio::sync::mpsc; 3use tokio::time::{sleep, Duration}; 4// ?hidden:end 5 6#[tokio::main] 7async fn main() { 8 // Создаем асинхронный mpsc-канал с емкостью буфера на 10 элементов 9 let (tx, mut rx) = mpsc::channel(10); 10 11 // Запускаем асинхронную задачу в Tokio 12 tokio::spawn(async move { 13 println!(\u0026#34;[НАВИГАЦИЯ] Начинаем расчет...\u0026#34;); 14 sleep(Duration::from_millis(300)).await; 15 let _ = tx.send(\u0026#34;Траектория рассчитана асинхронно!\u0026#34;.to_string()).await; 16 }); 17 18 println!(\u0026#34;[МОСТИК] Ожидаем данные (неблокирующий await)...\u0026#34;); 19 20 // recv() возвращает Future. Она приостанавливает выполнение нашей задачи, 21 // но воркер-поток Tokio продолжает выполнять другие задачи в это время! 22 if let Some(msg) = rx.recv().await { 23 println!(\u0026#34;[МОСТИК] Принято: {}\u0026#34;, msg); 24 } 25 26 println!(\u0026#34;[МОСТИК] Сближение завершено.\u0026#34;); 27} 28 Асинхронный канал tokio::sync::mpsc\nСоздается с помощью mpsc::channel(capacity). Емкость буфера обязательна для защиты от переполнения памяти (backpressure). Методы send().await и recv().await не блокируют ОС-поток, а приостанавливают выполнение текущей задачи (task yield). Результат выполнения: \u0026times; Почему это сработало? # // Асинхронное чтение освобождает поток планировщика: let msg = rx.recv().await; 2 Неблокирующий recv().await: В асинхронном канале вызов recv() возвращает Future. Когда мы пишем .await на строке 2 , планировщик Tokio видит, что данных в канале еще нет. Он временно «замораживает» текущую задачу и переключает поток ОС на выполнение других задач. Как только данные приходят, планировщик возобновляет выполнение. Ограниченная емкость (Bounded Channel): Асинхронный канал Tokio создается через mpsc::channel(capacity). Емкость буфера обязательна! Если отправитель шлет сообщения быстрее, чем получатель успевает их читать, буфер переполнится, и вызов send().await приостановит задачу отправителя. Это называется backpressure (обратное давление) и защищает приложение от утечки памяти. Часть 3. Модель «Многие отправители — один получатель» # — «Капитан, а если мне нужно собирать телеметрию сразу с нескольких сенсорных модулей?» — спросил RUST-Y. — «Мне создавать канал для каждого?»\n— «Нет», — ответил Зори. — «Канал называется mpsc — Multi-producer Single-consumer. Это значит, что отправителей может быть много, а получатель — всегда один. Мы можем просто клонировать передатчик и раздать его разным задачам.»\nRUST-Y обновил код, применив клонирование отправителя:\nИспользование клонированных Sender для нескольких источников Запуск ▶ 1// ?hidden:start 2use tokio::sync::mpsc; 3use tokio::time::{sleep, Duration}; 4// ?hidden:end 5 6async fn sensor_task(id: u32, tx: mpsc::Sender\u0026lt;String\u0026gt;) { 7 for i in 1..=3 { 8 sleep(Duration::from_millis(50 * id as u64)).await; 9 let msg = format!(\u0026#34;Датчик {} -\u0026gt; Значение {}\u0026#34;, id, i); 10 let _ = tx.send(msg).await; 11 } 12} 13 14#[tokio::main] 15async fn main() { 16 let (tx, mut rx) = mpsc::channel(10); 17 18 // Запускаем два параллельных датчика, каждый со своей копией Sender 19 tokio::spawn(sensor_task(1, tx.clone())); 20 tokio::spawn(sensor_task(2, tx.clone())); 21 22 // Важно: удаляем исходный Sender, иначе цикл rx.recv() никогда не завершится, 23 // так как канал будет считаться открытым 24 drop(tx); 25 26 println!(\u0026#34;[ЦЕНТР] Ожидаем показания датчиков...\u0026#34;); 27 28 // Канал закроется автоматически, когда все отправители (Sender) будут удалены 29 while let Some(msg) = rx.recv().await { 30 println!(\u0026#34;[ЦЕНТР] Получено: {}\u0026#34;, msg); 31 } 32 33 println!(\u0026#34;[ЦЕНТР] Все датчики отключены.\u0026#34;); 34} 35 Паттерн Multi-producer Single-consumer (MPSC)\nМы можем клонировать Sender с помощью tx.clone() и передавать его в разные асинхронные задачи. Получатель Receiver существует только в одном экземпляре. Чтобы цикл чтения завершился при выходе всех задач, исходный tx на мостике должен быть явно удален через drop(tx). Результат выполнения: \u0026times; Важные детали работы с клонами Sender: # // Освобождаем исходный передатчик: drop(tx); 3 // Получатель завершит цикл, когда все Sender исчезнут: while let Some(msg) = rx.recv().await { 4 println!(\u0026#34;Получено: {:?}\u0026#34;, msg); } Метод tx.clone(): Мы можем создавать сколько угодно копий отправителя и передавать их в асинхронные задачи. Закрытие канала: Канал закрывается автоматически, когда все экземпляры Sender (включая самый первый, оригинальный) удаляются из памяти (drop). Явный drop(tx) в main: Обратите внимание на строку drop(tx) на строке 3 в основной задаче после запуска рабочих потоков. Если мы забудем удалить оригинальный tx в main, то получатель Receiver в цикле while let Some(...) на строке 4 зависнет навсегда. Канал будет считаться открытым, так как одна копия Sender все еще находится на мостике, пусть из нее никто ничего и не отправляет. Эпилог # — «Кажется, теперь все сходится!» — обрадовался RUST-Y. — «Мы настроили асинхронный канал с датчиков сближения на мостик, поток не блокируется, а исходный отправитель аккуратно удаляется после запуска.»\n— «Отлично», — подытожил капитан Нова. — «Связь установлена. Зори, начинаем маневр сближения с модулем доктора Арчи.»\nКорабль Vectoria мягко скользнул вперед, а его асинхронные системы работали слаженно и без блокировок.\nПроверь свои знания! # Пройдите небольшой тест, чтобы закрепить понимание работы асинхронных каналов в Rust и Tokio.\n","date":"13 November 2025","externalUrl":null,"permalink":"/rust/rust-tokio-channels/","section":"Rust","summary":"","title":"Каналы в Rust при поддержке Tokio","type":"rust"},{"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 September 2025","externalUrl":null,"permalink":"/about-all/android-16k/","section":"Посты обо всем","summary":"","title":"Android 15 и 16 KB страница памяти: что изменится, как подготовить Android и Flutter-приложения","type":"about-all"},{"content":"","date":"25 September 2025","externalUrl":null,"permalink":"/tags/flutter/","section":"Tags","summary":"","title":"Flutter","type":"tags"},{"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// ?ignore 2// ?hidden:start 3fn query_sensor() -\u0026gt; u32 { 42 } 4fn process(d: u32) -\u0026gt; u32 { d } 5 6use std::{thread, time::Duration}; 7// ?hidden:end 8fn calibrate() -\u0026gt; u32 { 9 let d = query_sensor(); 10 thread::sleep(Duration::from_secs(5)); 11 process(d) 12} 13 14fn main() { 15 println!(\u0026#34;[МОСТИК] Запуск калибровки...\u0026#34;); 16 let data = calibrate(); 17 println!(\u0026#34;[МОСТИК] Калибровка завершена: {data}\u0026#34;); 18} 19 Проблема:\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// ?ignore 2// ?hidden:start 3use std::time::Duration; 4use std::thread; 5use tokio::task; 6 7fn query_sensor() -\u0026gt; u32 { 42 } 8fn process(d: u32) -\u0026gt; u32 { d } 9 10fn calibrate_blocking() -\u0026gt; u32 { 11 let d = query_sensor(); 12 thread::sleep(Duration::from_secs(5)); // блокируем, но в отдельном потоке 13 process(d) 14} 15 16// ?hidden:end 17 18#[tokio::main] 19async fn main() { 20 println!(\u0026#34;[МОСТИК] Запуск калибровки (spawn_blocking)...\u0026#34;); 21 let data = task::spawn_blocking(|| calibrate_blocking()) 22 .await 23 .expect(\u0026#34;join error\u0026#34;); 24 println!(\u0026#34;[МОСТИК] Калибровка завершена: {data}\u0026#34;); 25} 26 Комментарий\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Приложение: шпаргалка капитана по ключевым приёмам # Шпаргалка капитана по асинхронности: # Чтобы уверенно пройти сквозь врата async-программирования и не завалить финальный тест, держи под рукой этот краткий бортовой журнал:\nНе блокируйте воркеры: Забудьте про thread::sleep в async-функциях - всегда заменяйте его на асинхронный tokio::time::sleep. Тяжёлое CPU / блокирующее I/O: Если код нельзя переписать под async (например, старые библиотеки), оборачивайте его в tokio::task::spawn_blocking. Старт из sync-кода: Чтобы построить мост из обычного синхронного потока ОС в мир Tokio, используйте tokio::runtime::Handle::block_on. Параллельное ожидание: Используйте макрос tokio::join! для фиксированного набора разных задач и futures::future::join_all для динамических коллекций (Vec). Фоновая работа: Для запуска независимых фоновых процессов используйте tokio::spawn (помните, что он требует передачи владения через move и времени жизни 'static). Пачки динамических задач: Если задач много, они создаются в цикле и результаты нужно обрабатывать по мере их готовности — используйте tokio::task::JoinSet в связке с .join_next().await. Кооперативность: Помните, что планировщик Tokio не может прервать задачу насильно. Она должна уступать место сама в точках .await. Если у вас долгий вычислительный цикл без точек ожидания — вставляйте внутрь tokio::task::yield_now().await. Финальный квиз 🚀 # 🛰️\n","date":"18 September 2025","externalUrl":null,"permalink":"/rust/future-async/","section":"Rust","summary":"","title":"Rust async и Tokio: В ожидании будущего","type":"rust"},{"content":" Вступление # Иногда нам нужен список который не зажат в рамки фиксированного размера. В Rust для этого имеется Vec\u0026lt;T\u0026gt; динамический контейнер, который растёт и сжимается по мере необходимости.\nЧтобы сделать знакомство с ним более запоминающимся, мы отправимся в путешествие с увлекательным сюжетом.\nГлавное действующее лицо - капитан космического корабля «Vectoria». Его задача - подготовить корабль к миссии: собрать экипаж, загрузить оборудование, очистить и обслужить системы и наконец отправиться в полёт.\nВместе с капитаном будет робот-помощник RUST-Y или просто Расти. Он умеет писать программы на Rust, но часто совершает типичные ошибки новичка. Капитану придется направлять его и показывать правильные решения.\nА ещё в конце статьи вас ждёт испытание: мини-квиз, который покажет насколько хорошо вы справились с миссией и разобрались с Vec. Готовы? Тогда начнём с самого простого шага: списка экипажа.\nЧасть 1. Знакомство с Vec — первые шаги # Вас вызывают в штаб. Перед стартом новой космической миссии вам поручают задание - предоставить список экипажа. Пока известно только четыре члена команды, ещё двое прибудут позже.\nВы даете задание Расти — составь список экипажа. Ваша задача - научить его вести списки правильно, ведь от этого зависит порядок на борту и успех всей миссии.\nПопытка RUST-Y — ошибка новичка ← Шаг 1/2 → Запуск ▶ 1// ?compile_fail 2fn main() { 3 // Робот записывает имена по отдельности 4 let crew1 = \u0026#34;Капитан Нова\u0026#34;; 5 let crew2 = \u0026#34;Инженер Спаркс\u0026#34;; 6 let crew3 = \u0026#34;Доктор Люмен\u0026#34;; 7 let crew4 = \u0026#34;Биолог Рост\u0026#34;; 8 9 // Он решает: \u0026#34;создам массив\u0026#34; 10 let crew = [crew1, crew2, crew3, crew4]; 11 12 // Но сразу лезет к пятому элементу... 13 let crew5 = crew[4]; 14 15 println!(\u0026#34;Пятый член экипажа: {crew5}\u0026#34;); 16} Результат: программа упадёт с паникой.\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 → Запуск ▶ 1// ?should_panic 2fn main() { 3 let crew = vec![ 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 \u0026#34;Связист Хекс\u0026#34;, 10 ]; 11 12 // Попытка вызвать участника с индексом 99 13 let who = crew[99]; // ❌ паника 14 println!(\u0026#34;На тренировку выходит: {}\u0026#34;, who); 15} Результат: программа упадёт с паникой.\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 → Запуск ▶ 1// ?compile_fail 2fn main() { 3 let mut names = vec![ 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 String::from(\u0026#34;Связист Хекс\u0026#34;), 10 ]; 11 12 // ❌ Ошибочный подход: iter() даёт неизменяемые ссылки (\u0026amp;String) 13 for (i, name) in names.iter().enumerate() { 14 *name = format!(\u0026#34;R-{:03} {}\u0026#34;, i + 1, name); 15 println!(\u0026#34;Подготовка кода для: {name} (#{})\u0026#34;, i + 1); 16 } 17} 18 Почему не компилируется\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 September 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 August 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 June 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 June 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 → Запуск ▶ 1// ?compile_fail 2use std::rc::Rc; 3use std::thread; 4 5fn main() { 6 let data = Rc::new(vec![1, 2, 3]); 7 8 let handle = thread::spawn(move || { 9 println!(\u0026#34;{:?}\u0026#34;, data); 10 }); 11 12 handle.join().unwrap(); 13} 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 June 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// ?compile_fail 2#[derive(Debug)] 3struct City { 4 name: String, 5 history: String, 6} 7 8#[derive(Debug)] 9struct CitySummary { 10 headline: String, 11 description: String, 12} 13 14fn main() { 15 let history = String::from(\u0026#34;Город основан в 1574 году\u0026#34;); 16 17 let city = City { 18 name: String::from(\u0026#34;Уфа\u0026#34;), 19 history: history, 20 }; 21 22 let summary = CitySummary { 23 headline: String::from(\u0026#34;Уфа\u0026#34;), 24 description: history, // ❌ перемещено из-под city 25 }; 26 27 println!(\u0026#34;{:?}, {:?}\u0026#34;, city, summary); 28} 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// ?should_panic 2#[allow(unused_variables)] 3use std::rc::Rc; 4use std::cell::RefCell; 5 6fn main() { 7 let shared = Rc::new(RefCell::new(vec![1, 2, 3])); 8 9 let _first = shared.borrow_mut(); 10 let _second = shared.borrow_mut(); // ❌ паника: уже есть активный borrow_mut 11} 12 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 June 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 June 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 May 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 May 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 May 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() Константы должны быть известны при компиляции - нельзя присвоить им результат вычислений в рантайме Как объявить константу? # Синтаксис объявления констант чрезвычайно прост, но имеет важные отличия от переменных.\nРабота с константами в Rust ← Шаг 1/2 → Запуск ▶ 1const MAX_CONNECTIONS: u32 = 100; 2const SECONDS_IN_HOUR: u64 = 60 * 60; // Компилятор вычислит это выражение заранее! 3 4fn main() { 5 println!(\u0026#34;Max: {}, Seconds: {}\u0026#34;, MAX_CONNECTIONS, SECONDS_IN_HOUR); 6} Объявление констант\nКонстанты const объявляются с явным указанием типа. Значение константы вычисляется на этапе компиляции. Результат выполнения: \u0026times; 1// ?compile_fail 2const FAST_LANG: \u0026amp;str = \u0026#34;Rust\u0026#34;; 3 4fn main() { 5 println!(\u0026#34;The {} language\u0026#34;, FAST_LANG); 6 let FAST_LANG = \u0026#34;Dart\u0026#34;; // ❌ Ошибка компиляции 7} Константы нельзя переопределять/затенять\nЗатенение let запрещено для констант. Компилятор выдаст ошибку, защищая код от нежелательного переопределения глобальных значений. Результат выполнения: \u0026times; 🔥 Важные правила:\nСтарайтесь придерживаться именования SCREAMING_SNAKE_CASE (заглавные буквы и подчёркивание). Нельзя изменять значение константы. Нельзя затенять константу с помощью let. Нужно обязательно указать тип после двоеточия (компилятор не выводит типы констант автоматически). Зачем же так строго? Компилятор 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 May 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Взаимодействие между скоупами # Как я уже упоминал в прошлой статье про затенение, внутренний скоуп может захватывать переменные из внешнего скоупа. Но в обратную сторону это не сработает. Вы не сможете объявить во внутреннем скоупе переменную и использовать её вне его границ.\nОбласти видимости и скоупы в Rust ← Шаг 1/2 → Запуск ▶ 1// ?compile_fail 2fn main() { 3 { 4 let light_sensor = 20; 5 println!(\u0026#34;Внутри блока (light): {}\u0026#34;, light_sensor); 6 } 7 // ❌ Ошибка компиляции: light_sensor больше не существует! 8 println!(\u0026#34;Вне блока (light): {}\u0026#34;, light_sensor); 9} Выход за границы области видимости\nПеременная light_sensor объявлена внутри вложенного блока {}. За границами этого блока переменная уничтожается (освобождается память). Обращение к ней вне блока приводит к ошибке компиляции E0425. Результат выполнения: \u0026times; 1fn main() { 2 let move_sensor = 10; 3 { 4 // Внутренний скоуп видит внешние переменные 5 println!(\u0026#34;Внутри блока (move): {}\u0026#34;, move_sensor); 6 let inner_sensor = 5; 7 println!(\u0026#34;Сумма в блоке: {}\u0026#34;, move_sensor + inner_sensor); 8 } 9 println!(\u0026#34;Вне блока (move): {}\u0026#34;, move_sensor); 10} Доступ к внешнему контексту\nВложенный блок имеет доступ к переменным внешнего скоупа (move_sensor). В обратную сторону (вынести inner_sensor наружу) это правило не работает. Результат выполнения: \u0026times; При попытке использовать light_sensor за пределами фигурных скобок (как показано на Шаге 1) компилятор выдает ошибку cannot find value 'light_sensor' in this scope, потому что время жизни этой переменной закончилось на закрывающей фигурной скобке.\nВывод # Вложенный скоуп можно представить как некий портал в который вы можете что-то принести, но вынести из него ничего нельзя. ✨\nВажно запомнить, что:\nПеременные существуют внутри скоупа, в котором они объявлены. Внутренний скоуп может захватывать переменные из внешнего скоупа. Вы не можете использовать переменную вне ее скоупа. Теперь вы знаете как правильно \u0026ldquo;готовить\u0026rdquo; области видимости в Rust! 😉\n","date":"16 May 2025","externalUrl":null,"permalink":"/rust/rust-scope/","section":"Rust","summary":"","title":"Области видимости в Rust","type":"rust"},{"content":" Затенение (variable shadowing) # Затенение переменных - это концепция Rust, которая позволяет повторно объявить переменную с тем же именем, используя ключевое слово let. При этом оригинальная переменная заменяется (затеняется) новой, что позволяет изменять тип или состояние данных без создания временных переменных (например, temp_str или temp_float).\nПример использования затенения # Один из наиболее распространенных примеров, где это может потребоваться - это последовательная трансформация данных и работа с локальными областями видимости (например, обработка данных с температурных датчиков или сенсоров яркости в умном доме).\nЗатенение переменных в Rust ← Шаг 1/2 → Запуск ▶ 1fn main() { 2 let temperature = \u0026#34;25.5\u0026#34;; 3 let temperature: f64 = temperature.parse().unwrap(); 4 let mut temperature = temperature * 9.0 / 5.0 + 32.0; // переводим в Фаренгейт 5 6 temperature = temperature + 1.0; 7 println!(\u0026#34;Температура: {}\u0026#34;, temperature); 8} Последовательная трансформация данных\nЗатенение позволяет повторно использовать имя переменной temperature. Мы изменили тип данных со строки \u0026amp;str на число f64. В конце мы сделали её изменяемой с помощью let mut. Результат выполнения: \u0026times; 1fn main() { 2 let brightness = \u0026#34;50\u0026#34;; 3 { 4 let brightness: u8 = brightness.parse().unwrap(); 5 println!(\u0026#34;Внутри блока: {}%\u0026#34;, brightness); 6 let brightness = brightness + 20; 7 println!(\u0026#34;Внутри блока (увеличено): {}%\u0026#34;, brightness); 8 } 9 println!(\u0026#34;Вне блока (оригинал): {}\u0026#34;, brightness); 10} Затенение во вложенной области видимости (Scope)\nВнутри блока {} мы затеняем внешнюю переменную brightness новым типом и значением. После выхода из блока временные переменные удаляются из стека. Внешняя переменная восстанавливает своё оригинальное значение \u0026quot;50\u0026quot;. Результат выполнения: \u0026times; Описание работы примера: # Шаг 1. Последовательная трансформация данных:\nСначала мы объявили переменную temperature со строковым значением \u0026quot;25.5\u0026quot;. Затем с помощью ключевого слова let затенили её новой переменной типа f64, распарсив строку. Далее мы перевели значение в Фаренгейты, а затем переопределили её как изменяемую (mut), прибавив единицу. Это позволяет повторно использовать понятное имя переменной без придумывания временных названий. Шаг 2. Затенение в рамках области видимости (scope):\nМы объявили внешнюю переменную brightness со строковым значением \u0026quot;50\u0026quot;. Внутри блока {} мы объявили локальную переменную brightness с типом u8 и числовым значением 50. Внутри блока мы затенили её ещё раз, увеличив на 20. После выхода из блока все временные локальные переменные очищаются из стека, и внешняя переменная восстанавливает своё оригинальное значение \u0026quot;50\u0026quot;. Надеюсь, эта статья помогла вам понять концепцию затенения переменных в Rust и избавит вас от придумывания новых имен для переменных наподобие upd_temperature.\n","date":"14 May 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Для наглядности давайте посмотрим на интерактивный пример этой ошибки и её исправления:\nПример ошибки E0425 (Неразрешенное имя) ← Шаг 1/2 → Запуск ▶ 1// ?compile_fail 2fn main() { 3 let message = \u0026#34;Привет, Rust!\u0026#34;; 4 // Опечатка в имени переменной: messg вместо message 5 println!(\u0026#34;{}\u0026#34;, messg); 6} Ошибка E0425: Использование неизвестного имени\nКомпилятор Rust прерывает сборку, поскольку имя messg не объявлено в данной области видимости. Обратите внимание на помощь компилятора: он автоматически ищет похожие переменные и предлагает исправить опечатку. Результат выполнения: \u0026times; 1fn main() { 2 let message = \u0026#34;Привет, Rust!\u0026#34;; 3 // Исправляем опечатку 4 println!(\u0026#34;{}\u0026#34;, message); 5} Исправление опечатки\nЗаменив messg на message, мы обращаемся к реально существующей и объявленной переменной. Теперь компилятор успешно соберёт программу, и в терминале отобразится приветствие. Результат выполнения: \u0026times; Еще один частый гость: Ошибка E0308 (Несоответствие типов) # Другой популярной ошибкой, с которой часто сталкиваются новички, является несоответствие типов (E0308). Rust — язык со строгой статической типизацией. Он не выполняет неявные преобразования за вас (например, он не превратит число с плавающей точкой в целое число автоматически).\nДавайте посмотрим на интерактивном слайде, как выглядит эта ошибка и как её исправить:\nПример ошибки E0308 (Несоответствие типов) ← Шаг 1/2 → Запуск ▶ 1// ?compile_fail 2fn main() { 3 // В Rust строгая типизация. Мы не можем присвоить число с плавающей точкой 4 // переменной, которая ожидает целое число без явного приведения. 5 let speed: i32 = 12.5; 6 println!(\u0026#34;Скорость: {}\u0026#34;, speed); 7} Ошибка E0308: Несоответствие типов (Mismatched Types)\nМы указали тип i32 (целое число), но присвоили значение 12.5 (с плавающей точкой). Rust не выполняет неявных преобразований типов, чтобы уберечь вас от потери данных или точности. Результат выполнения: \u0026times; 1fn main() { 2 // Способ 1: Изменить тип переменной на соответствующий 3 let speed_float: f64 = 12.5; 4 5 // Способ 2: Явное приведение типов с помощью оператора `as` 6 let speed_int: i32 = 12.5 as i32; 7 8 println!(\u0026#34;Дробная скорость: {}, Целая скорость: {}\u0026#34;, speed_float, speed_int); 9} Решение несоответствия типов\nЛибо меняем тип на f64 или f32 для сохранения точности. Либо явно приводим значение к i32 с помощью as i32 (при этом дробная часть будет отброшена). Результат выполнения: \u0026times; Альтернативные варианты # Хотя поиск ответа через GPT/ИИ может казаться быстрым решением, важно быть осторожным и не полагаться на нейронные сети, которые могут галлюцинировать. Использование rustc --explain в сочетании со справочными материалами — гораздо более надёжный и глубокий способ обучения.\nОнлайн-ресурсы # Если вы перейдете по ссылке Rust Code Errors, то увидите перечень всех кодов возможных ошибок. Кроме того, на сайте вы можете запустить пример кода и увидеть вывод компилятора для конкретной ошибки.\nСоветы для начинающих # Чтобы действительно научиться работать с ошибками, не стоит просто исправлять их и двигаться дальше. Вместо этого, на первых порах, следуйте простому алгоритму:\nИсправьте ошибку. Напишите маленькую программу, которая позволит воспроизвести ту же ошибку компилятора. Подумайте еще раз об этой ошибке и попробуйте объяснить себе, почему она произошла и как принято её решать. Погуглите и поищите помощи на ресурсах и в сообществах, если всё ещё не до конца разобрались. Следуя этому алгоритму, вы не только исправите ошибку, но и получите более глубокое понимание языка.\nВывод # Теперь, когда у вас возникнет ошибка компиляции, вы знаете, где можно найти помощь, не покидая среду разработки. Компилятор Rust — ваш надежный и верный помощник.\nВ последующих статьях нам ещё не раз предстоит столкнуться с ошибками компилятора, но вы уже вооружены отличным инструментом для их расшифровки и исправления.\nПроверь свои знания! # Пройдите короткий интерактивный тест, чтобы закрепить понимание работы с ошибками компилятора в Rust.\n","date":"12 May 2025","externalUrl":null,"permalink":"/rust/rust-err/","section":"Rust","summary":"","title":"Разбираемся с ошибками в Rust","type":"rust"},{"content":" Введение # В Rust все переменные по умолчанию иммутабельные (Immutable), то есть они не могут изменяться. Мутабельные переменные, напротив, имеют способность изменяться (Mutable).\nОбещание себе # Давайте рассмотрим пример. Предположим, вы решили вставать в 6 утра каждый день и ложиться в 22:00. Для этого вы создаете две переменные. Вы хотите вывести значения этих переменных в консоль, а затем изменить их.\nИзменяемые и неизменяемые переменные в Rust ← Шаг 1/2 → Запуск ▶ 1// ?compile_fail 2fn main() { 3 let get_up = 6; 4 let bedtime = 22; 5 println!(\u0026#34;Я планирую вставать в {} часов и ложиться спать в {}\u0026#34;, get_up, bedtime); 6 7 get_up = 7; // ❌ Ошибка компиляции: get_up неизменяема 8 bedtime = 23; 9} Попытка изменить неизменяемую переменную\nВ Rust переменные по умолчанию неизменяемы (immutable). Этот код приведёт к ошибке компиляции E0384. Результат выполнения: \u0026times; 1fn main() { 2 let mut get_up = 6; 3 let mut bedtime = 22; 4 println!(\u0026#34;Я планирую вставать в {} часов и ложиться спать в {}\u0026#34;, get_up, bedtime); 5 6 get_up = 7; // ✅ Корректно: переменные помечены как `mut` 7 bedtime = 23; 8 9 println!(\u0026#34;Я планирую вставать в {} часов и ложиться спать в {}\u0026#34;, get_up, bedtime); 10} Использование ключевого слова mut\nЧтобы сделать переменную изменяемой, добавьте mut после let. Теперь значения переменных можно безопасно переопределять. Результат выполнения: \u0026times; Анализ ошибки # Компилятор сообщает, что вы пытаетесь присвоить новое значение неизменяемой переменной (cannot assign twice to immutable variable). В Rust переменные по умолчанию неизменяемые (иммутабельные).\nИсправление ошибки # Rust предлагает решение: сделать вашу переменную мутабельной, добавив ключевое слово mut после let. Как вы видите на втором шаге интерактивного примера выше, после добавления mut программа успешно выполняется и обновляет значения в соответствии с вашим новым графиком.\nИтоги # Rust — язык со строгими правилами. Он требует от разработчика определяться с тем, будут ли его переменные изменяемыми или нет. По умолчанию все переменные объявленные при помощи ключевого слова let являются иммутабельными (неизменяемыми). Чтобы сделать переменную изменяемой, нужно добавить ключевое слово mut после let. Это помогает писать более безопасный и предсказуемый код (создатели языка Rust нас именно в этом стараются убедить).\n","date":"6 May 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 May 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, или доверить компилятору вывести тип самостоятельно на основе присвоенного значения.\nОбъявление и присвоение переменных в Rust ← Шаг 1/3 → Запуск ▶ 1fn main() { 2 let sensors = 20; 3 println!(\u0026#34;Количество датчиков: {}\u0026#34;, sensors); 4} Объявление переменной\nКлючевое слово let объявляет новую переменную. Компилятор автоматически выводит тип как i32 на основе значения 20. Результат выполнения: \u0026times; 1fn main() { 2 let ultra = 10; 3 let temp = ultra; 4 println!(\u0026#34;Значение temp: {}\u0026#34;, temp); 5} Присвоение значения другой переменной\nМы можем скопировать значение из одной переменной в другую. Теперь temp хранит копию значения 10. Результат выполнения: \u0026times; 1fn main() { 2 let ultra = 10; 3 let temp = 5; 4 let sensors = ultra + temp; 5 println!(\u0026#34;Сумма датчиков (sensors): {}\u0026#34;, sensors); 6} Присвоение результата выражения\nПеременной можно присвоить результат математического выражения. Здесь sensors принимает результат сложения 10 + 5 = 15. Результат выполнения: \u0026times; Интересный момент: Rust не любит, когда вы объявляете переменную, но не используете её. Go кстати тоже не любит. Но в Rust можно перед названием переменной поставить _ и тогда компилятор проигнорирует такую переменную. Пример: let _ignore_me = 42; Изменяемость переменных # По умолчанию переменные в Rust являются неизменяемыми (immutable). Это означает, что после объявления переменной ее значение не может быть изменено. Если вам необходимо изменить значение переменной, вы можете объявить ее как изменяемую (mutable), используя ключевое слово mut:\nlet mut sensors = 20; sensors = 30; // Теперь sensors имеет значение 30 В этом примере мы объявляем sensors как изменяемую переменную и затем изменяем ее значение на 30.\n","date":"3 May 2025","externalUrl":null,"permalink":"/rust/rust-var/","section":"Rust","summary":"","title":"Переменные в Rust: основы и примеры использования","type":"rust"},{"content":"","date":"26 March 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 March 2025","externalUrl":null,"permalink":"/til/kotlin/kotlin2_1_20/","section":"Сегодня я узнал","summary":"","title":"Kotlin 2.1.20: Новые возможности и улучшения для разработчиков","type":"til"},{"content":" Введение # Hash Map — это коллекция пар \u0026ldquo;ключ-значение\u0026rdquo;, которая позволяет быстро находить значения по ключу. Представьте ее как справочник, где вы моментально получаете нужную информацию.\nСоздание HashMap и добавление элементов # Для создания пустого HashMap используется функция HashMap::new(). Чтобы наполнить коллекцию данными, её необходимо сделать изменяемой с помощью ключевого слова mut, после чего использовать метод insert(key, value).\nДавайте посмотрим на интерактивном слайде, как создаётся HashMap, вставляются новые значения и как обновить существующее значение:\nБазовые операции с HashMap: создание, вставка и обновление ← Шаг 1/2 → Запуск ▶ 1use std::collections::HashMap; 2 3fn main() { 4 // Создаем изменяемый HashMap 5 let mut smart_home = HashMap::new(); 6 7 // Добавляем устройства и их состояние 8 smart_home.insert(\u0026#34;гостиная_свет\u0026#34;.to_string(), \u0026#34;включен\u0026#34;.to_string()); 9 smart_home.insert(\u0026#34;спальня_термостат\u0026#34;.to_string(), \u0026#34;22°C\u0026#34;.to_string()); 10 11 println!(\u0026#34;Устройства: {:?}\u0026#34;, smart_home); 12} Создание HashMap и добавление элементов\nДля использования HashMap импортируем его из std::collections. Чтобы наполнить коллекцию данными, она должна быть объявлена изменяемой (mut). Метод insert(key, value) добавляет новую пару ключ-значение в мапу. Результат выполнения: \u0026times; 1use std::collections::HashMap; 2 3fn main() { 4 let mut smart_home = HashMap::new(); 5 smart_home.insert(\u0026#34;гостиная_свет\u0026#34;.to_string(), \u0026#34;включен\u0026#34;.to_string()); 6 smart_home.insert(\u0026#34;спальня_термостат\u0026#34;.to_string(), \u0026#34;22°C\u0026#34;.to_string()); 7 8 // Обновляем значение по существующему ключу 9 smart_home.insert(\u0026#34;гостиная_свет\u0026#34;.to_string(), \u0026#34;выключен\u0026#34;.to_string()); 10 11 println!(\u0026#34;Обновленные устройства: {:?}\u0026#34;, smart_home); 12} Обновление значения по ключу\nЕсли мы вызываем insert с ключом, который уже существует в HashMap, старое значение перезаписывается новым. Результат выполнения: \u0026times; Получение значений и Entry API # При поиске значений в HashMap по ключу основным инструментом является метод .get(). Он возвращает перечисление Option\u0026lt;\u0026amp;V\u0026gt;, что защищает программу от ошибок обращения к отсутствующим элементам. Также Rust предоставляет мощный Entry API для условных операций (добавление или изменение значений только при выполнении определенных условий).\nДавайте подробно разберем, как искать элементы, обрабатывать их отсутствие и использовать Entry API на интерактивном слайде:\nПоиск значений, unwrap_or и Entry API ← Шаг 1/3 → Запуск ▶ 1use std::collections::HashMap; 2 3fn main() { 4 let mut smart_home = HashMap::new(); 5 smart_home.insert(\u0026#34;гостиная_свет\u0026#34;, \u0026#34;включен\u0026#34;); 6 7 // Метод get() возвращает Option\u0026lt;\u0026amp;V\u0026gt; 8 let light_status = smart_home.get(\u0026#34;гостиная_свет\u0026#34;); 9 let lock_status = smart_home.get(\u0026#34;дверь_замок\u0026#34;); 10 11 println!(\u0026#34;Свет в гостиной: {:?}\u0026#34;, light_status); 12 println!(\u0026#34;Замок двери: {:?}\u0026#34;, lock_status); 13} Поиск значений по ключу через .get()\nМетод .get() принимает ссылку на ключ и возвращает Option\u0026lt;\u0026amp;V\u0026gt;. Если ключ найден, возвращается Some(\u0026amp;value). Если ключа нет, возвращается None. Это позволяет безопасно проверять наличие данных без риска паники во время работы. Результат выполнения: \u0026times; 1use std::collections::HashMap; 2 3fn main() { 4 let mut smart_home = HashMap::new(); 5 smart_home.insert(\u0026#34;гостиная_свет\u0026#34;, \u0026#34;включен\u0026#34;); 6 7 // Получаем значение с дефолтом, если ключ отсутствует 8 let default_status = \u0026#34;выключен\u0026#34;; 9 let light = smart_home.get(\u0026#34;гостиная_свет\u0026#34;).unwrap_or(\u0026amp;default_status); 10 let lock = smart_home.get(\u0026#34;дверь_замок\u0026#34;).unwrap_or(\u0026amp;default_status); 11 12 println!(\u0026#34;Свет: {}\u0026#34;, light); 13 println!(\u0026#34;Замок: {}\u0026#34;, lock); 14} Безопасное извлечение с unwrap_or\nМетод .unwrap_or() позволяет задать запасное значение, которое будет использовано, если .get() вернул None. Результат выполнения: \u0026times; 1use std::collections::HashMap; 2 3fn main() { 4 let mut smart_home = HashMap::new(); 5 smart_home.insert(\u0026#34;гостиная_свет\u0026#34;.to_string(), \u0026#34;включен\u0026#34;.to_string()); 6 7 // or_insert добавляет значение, только если ключа нет 8 smart_home.entry(\u0026#34;кухня_чайник\u0026#34;.to_string()).or_insert(\u0026#34;выключен\u0026#34;.to_string()); 9 smart_home.entry(\u0026#34;гостиная_свет\u0026#34;.to_string()).or_insert(\u0026#34;включен\u0026#34;.to_string()); 10 11 // and_modify изменяет значение, только если ключ есть 12 smart_home.entry(\u0026#34;гостиная_свет\u0026#34;.to_string()) 13 .and_modify(|status| *status = \u0026#34;выключен\u0026#34;.to_string()); 14 15 println!(\u0026#34;Итоговое состояние: {:?}\u0026#34;, smart_home); 16} Entry API: or_insert и and_modify\nМетод .entry() возвращает перечисление Entry, представляющее запись (присутствующую или отсутствующую). Метод .or_insert() вставляет значение, только если ключа нет, и возвращает мутабельную ссылку на значение. Метод .and_modify() позволяет выполнить замыкание для изменения существующего значения по ключу. Результат выполнения: \u0026times; Способы обработки результатов поиска # Обработка отсутствующих ключей с помощью сопоставления с образцом (match): Поскольку .get() возвращает Option\u0026lt;\u0026amp;V\u0026gt;, вы можете использовать сопоставление шаблонов для детальной обработки обоих сценариев (Some и None):\nmatch smart_home_devices.get(\u0026#34;гостиная_свет\u0026#34;) { Some(status) =\u0026gt; println!(\u0026#34;Статус света: {}\u0026#34;, status), None =\u0026gt; println!(\u0026#34;Статус света не найден\u0026#34;), } Значения по умолчанию с unwrap_or: Если вместо детальной обработки match вам достаточно подставить стандартное значение в случае отсутствия ключа, используйте метод .unwrap_or():\nlet default_status = \u0026#34;выключен\u0026#34;.to_string(); let status = smart_home_devices.get(\u0026#34;гостиная_свет\u0026#34;).unwrap_or(\u0026amp;default_status); Практический пример # Давайте рассмотрим практический пример, связанный с управлением умным домом. Предположим, у нас есть несколько устройств в доме и нам нужно отслеживать их текущий статус. Например, мы хотим знать, включен ли свет в гостиной, какая температура установлена на термостате в спальне и работает ли кондиционер на кухне. Если статус какого-то устройства неизвестен, мы хотим выводить соответствующее сообщение.\nУ нас есть список всех устройств, которые нас интересуют. Для каждого устройства нужно вывести его название и текущий статус из HashMap, подставив значение по умолчанию для неизвестных приборов. Давайте посмотрим, как работает этот алгоритм на практике с помощью интерактивного слайда:\nПрактический пример: Мониторинг умного дома Запуск ▶ 1use std::collections::HashMap; 2 3fn main() { 4 // Создаем HashMap для хранения статусов устройств 5 let mut home_device_status: HashMap\u0026lt;String, String\u0026gt; = HashMap::new(); 6 7 // Добавляем статусы для некоторых устройств 8 home_device_status.insert(\u0026#34;гостиная_свет\u0026#34;.to_string(), \u0026#34;включен\u0026#34;.to_string()); 9 home_device_status.insert(\u0026#34;спальня_термостат\u0026#34;.to_string(), \u0026#34;22°C\u0026#34;.to_string()); 10 11 // Список интересующих нас приборов 12 let devices = vec![\u0026#34;гостиная_свет\u0026#34;, \u0026#34;спальня_термостат\u0026#34;, \u0026#34;кухня_кондиционер\u0026#34;]; 13 let default_device_status = \u0026#34;статус неизвестен\u0026#34;.to_string(); 14 15 // Перебираем все устройства и выводим их статус 16 for device in devices { 17 let status = home_device_status.get(device).unwrap_or(\u0026amp;default_device_status); 18 println!(\u0026#34;{}: {}\u0026#34;, device, status); 19 } 20} Практический пример: Мониторинг умного дома\nМы обходим вектор интересующих нас приборов и ищем состояние каждого в HashMap. Для прибора кухня_кондиционер, которого нет в мапе, метод .unwrap_or() подставляет запасное значение статус неизвестен. Результат выполнения: \u0026times; Почему HashMap так полезен? # 🚀 Быстрый доступ: поиск, добавление и удаление элементов происходят за константное время $O(1)$ в среднем. 🔄 Entry API: очень лаконичный и безопасный способ условной работы с коллекцией без лишних проверок. 🛡️ Безопасность типизации: компилятор Rust гарантирует, что ключи и значения будут строго соответствовать объявленным типам. Заключение # HashMap предоставляет мощный способ работы с парами \u0026ldquo;ключ-значение\u0026rdquo;. Они идеально подходят для задач, требующих быстрого поиска и эффективного управления данными.\nИспользуйте HashMap, когда:\nВы хотите связать произвольный ключ с произвольным значением. Вам нужен кэш. Вам нужна эффективная ассоциативная коллекция без сохранения порядка вставки. Официальная документация по HashMap\nПроверь свои знания! # Пройдите короткий интерактивный тест, чтобы закрепить понимание работы с HashMap в Rust.\n","date":"5 March 2025","externalUrl":null,"permalink":"/rust/rust-hashmap/","section":"Rust","summary":"","title":"Hash Map в Rust","type":"rust"},{"content":"","date":"14 February 2025","externalUrl":null,"permalink":"/tags/postgresql/","section":"Tags","summary":"","title":"PostgreSQL","type":"tags"},{"content":"","date":"14 February 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 February 2025","externalUrl":null,"permalink":"/til/postgresql/array-len/","section":"Сегодня я узнал","summary":"","title":"Как посчитать количество элементов в массиве в PostgreSQL?","type":"til"},{"content":"","date":"13 February 2025","externalUrl":null,"permalink":"/til/tools/","section":"Сегодня я узнал","summary":"","title":"Tools","type":"til"},{"content":"","date":"13 February 2025","externalUrl":null,"permalink":"/tags/uml/","section":"Tags","summary":"","title":"UML","type":"tags"},{"content":"","date":"13 February 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 February 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 February 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Паттерн Строитель в Rust Запуск ▶ 1#[derive(Debug)] 2struct Burger { 3 bun: String, 4 patty: String, 5 cheese: Option\u0026lt;String\u0026gt;, 6 salad: bool, 7 sauce: Option\u0026lt;String\u0026gt;, 8} 9 10impl Burger { 11 // Создаем новую реализацию с обязательными параметрами 12 fn new(bun: String, patty: String) -\u0026gt; Self { 13 Burger { 14 bun, 15 patty, 16 cheese: None, 17 salad: false, 18 sauce: None, 19 } 20 } 21 22 // Добавляем сыр 23 fn add_cheese(mut self, cheese: String) -\u0026gt; Self { 24 self.cheese = Some(cheese); 25 self 26 } 27 28 // Добавляем салат 29 fn add_salad(mut self) -\u0026gt; Self { 30 self.salad = true; 31 self 32 } 33 34 // Добавляем соус 35 fn add_sauce(mut self, sauce: String) -\u0026gt; Self { 36 self.sauce = Some(sauce); 37 self 38 } 39 40 // Валидация и финализация объекта 41 fn build(self) -\u0026gt; Result\u0026lt;Self, String\u0026gt; { 42 if self.bun.is_empty() || self.patty.is_empty() { 43 return Err(\u0026#34;Булочка и котлета обязательны!\u0026#34;.to_string()); 44 } 45 Ok(self) 46 } 47} 48 49fn main() { 50 let burger = Burger::new(\u0026#34;Булочка с кунжутом\u0026#34;.to_string(), \u0026#34;Котлета из говядины\u0026#34;.to_string()) 51 .add_cheese(\u0026#34;Чеддер\u0026#34;.to_string()) 52 .add_salad() 53 .add_sauce(\u0026#34;Барбекю\u0026#34;.to_string()) 54 .build(); 55 56 match burger { 57 Ok(burger) =\u0026gt; println!(\u0026#34;Ваш бургер: {:?}\u0026#34;, burger), 58 Err(e) =\u0026gt; println!(\u0026#34;Ошибка: {}\u0026#34;, e), 59 } 60} Реализация паттерна \u0026ldquo;Строитель\u0026rdquo; (Builder)\nКонструируем сложный объект Burger пошагово с помощью цепочки вызовов. Метод build() выполняет валидацию и возвращает Result. Результат выполнения: \u0026times; Как это работает? # Методы конфигурации: Каждый метод (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 February 2025","externalUrl":null,"permalink":"/rust/rust-builder/","section":"Rust","summary":"","title":"Паттерн \"Строитель\" в Rust","type":"rust"},{"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 February 2025","externalUrl":null,"permalink":"/about-all/telegram-multiple-files/","section":"Посты обо всем","summary":"","title":"Как отправить несколько файлов одним сообщением через Telegram Bot API","type":"about-all"},{"content":" Введение. Что такое структура # Структура (или struct) в Rust — это пользовательский тип данных, который позволяет группировать связанные данные вместе. Структуры похожи на классы в других языках программирования, но в Rust (как говорит документация) они более гибкие и мощные, так как язык предоставляет различные возможности для работы с ними. Кто бы сомневался 😁 При этом видов структур существует несколько.\nВ этой главе мы подробно рассмотрим как классические C-структуры, так и структуры-кортежи, юнит-структуры, а также способы работы с ними.\nШаг 1. Проектирование структуры # Прежде чем писать код, важно спроектировать и описать, что мы хотим получить. Давайте возьмем нашу любимую тему по системам для управления умным домом. В нашей системе есть хаб (центральное устройство) и несколько сенсоров, которые подключены к этому хабу.\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: name: String — имя хаба. id: String — уникальный идентификатор хаба. location: String — местоположение хаба. sensors: Vec\u0026lt;Sensor\u0026gt; — список сенсоров, подключенных к хабу. Sensor: name: String — имя сенсора. id: String — уникальный идентификатор сенсора. sensor_type: String — тип сенсора (например, \u0026ldquo;температура\u0026rdquo;, \u0026ldquo;влажность\u0026rdquo; и т.д.). value: f32 — текущее значение, измеренное сенсором. unit: String — единица измерения (например, \u0026ldquo;°C\u0026rdquo;, \u0026ldquo;%\u0026rdquo; и т.д.). is_active: bool — статус сенсора (активен/неактивен). Шаг 2. Разновидности структур и синтаксис инициализации # Rust поддерживает три основных вида структур:\nСтруктуры с именованными полями (классические C-структуры). Структуры-кортежи (Tuple Structs). Юнит-структуры (Unit-like Structs). Давайте изучим все виды структур, сокращенную инициализацию, синтаксис перехода (..) и правила видимости полей на интерактивных слайдах ниже:\nСтруктуры в Rust ← Шаг 1/6 → Запуск ▶ 1#[allow(dead_code)] 2#[derive(Debug)] 3struct Hub { 4 name: String, 5 id: String, 6 location: String, 7 sensors: Vec\u0026lt;Sensor\u0026gt;, 8} 9 10#[allow(dead_code)] 11#[derive(Debug)] 12struct Sensor { 13 name: String, 14 id: String, 15 sensor_type: String, 16 value: f32, 17 unit: String, 18 is_active: bool, 19} 20 21fn main() { 22 let hub = Hub { 23 name: \u0026#34;Main Hub\u0026#34;.to_string(), 24 id: String::from(\u0026#34;HUB001\u0026#34;), 25 location: \u0026#34;Room 101\u0026#34;.to_string(), 26 sensors: Vec::new(), // пустой список сенсоров 27 }; 28 29 println!(\u0026#34;{:#?}\u0026#34;, hub); 30} Объявление и базовая инициализация структур\nСтруктуры определяются с помощью ключевого слова struct. Атрибут #[derive(Debug)] позволяет выводить структуру в отладочном формате с помощью {:#?}. Результат выполнения: \u0026times; 1#[allow(dead_code)] 2#[derive(Debug)] 3struct Hub { 4 name: String, 5 id: String, 6 location: String, 7 sensors: Vec\u0026lt;Sensor\u0026gt;, 8} 9 10#[allow(dead_code)] 11#[derive(Debug)] 12struct Sensor { 13 name: String, 14 id: String, 15 sensor_type: String, 16 value: f32, 17 unit: String, 18 is_active: bool, 19} 20 21fn main() { 22 let sensor1 = Sensor { 23 name: \u0026#34;Temperature Sensor\u0026#34;.to_string(), 24 id: \u0026#34;TEMP001\u0026#34;.to_string(), 25 sensor_type: \u0026#34;Temperature\u0026#34;.to_string(), 26 value: 25.3, 27 unit: \u0026#34;°C\u0026#34;.to_string(), 28 is_active: true, 29 }; 30 31 let sensor2 = Sensor { 32 name: \u0026#34;Humidity Sensor\u0026#34;.to_string(), 33 id: \u0026#34;HUM001\u0026#34;.to_string(), 34 sensor_type: \u0026#34;Humidity\u0026#34;.to_string(), 35 value: 60.5, 36 unit: \u0026#34;%\u0026#34;.to_string(), 37 is_active: true, 38 }; 39 40 let hub = Hub { 41 name: \u0026#34;Main Hub\u0026#34;.to_string(), 42 id: String::from(\u0026#34;HUB001\u0026#34;), 43 location: \u0026#34;Room 101\u0026#34;.to_string(), 44 sensors: vec![sensor1, sensor2], 45 }; 46 47 println!(\u0026#34;{:#?}\u0026#34;, hub); 48} Структуры со вложенными коллекциями\nМы создали экземпляры Sensor и передали их в поле sensors структуры Hub. Макрос vec! используется для удобной инициализации вектора Vec\u0026lt;Sensor\u0026gt;. Результат выполнения: \u0026times; 1#[derive(Debug, Default)] 2struct Foo { 3 a: u32, 4 b: u8, 5} 6 7fn main() { 8 let a = 42; 9 let b = 13; 10 11 // Сокращенный синтаксис инициализации (field init shorthand) 12 // Имена переменных совпадают с именами полей 13 let x = Foo { a, b }; 14 15 // Синтаксис перехода (struct update syntax ..) 16 // Копирование (перенос) значений оставшихся полей из другой структуры 17 let y = Foo { a: 50, ..x }; 18 19 // Инициализация структуры с дефолтными значениями, кроме поля a 20 let z = Foo { a: 100, ..Default::default() }; 21 22 println!(\u0026#34;x: {:?}\u0026#34;, x); 23 println!(\u0026#34;y: {:?}\u0026#34;, y); 24 println!(\u0026#34;z: {:?}\u0026#34;, z); 25} Сокращенный синтаксис и синтаксис перехода\nЕсли имена переменных совпадают с именами полей, можно писать a вместо a: a (field init shorthand). Синтаксис .. позволяет скопировать остальные поля из другого экземпляра той же структуры. Типаж Default позволяет инициализировать поля по умолчанию с помощью ..Default::default(). Результат выполнения: \u0026times; 1// Объявление структуры-кортежа 2#[derive(Debug)] 3struct Bar(u32, bool); 4 5fn main() { 6 // Создание экземпляра 7 let x = Bar(42, true); 8 9 // Доступ к полям по порядковому индексу 10 let number = x.0; 11 let flag = x.1; 12 13 println!(\u0026#34;x: {:?}\u0026#34;, x); 14 println!(\u0026#34;Поля: number = {}, flag = {}\u0026#34;, number, flag); 15 16 // Неявное представление конструктора структуры-кортежа как функции 17 let constructor: fn(u32, bool) -\u0026gt; Bar = Bar; 18 let y = constructor(100, false); 19 println!(\u0026#34;y: {:?}\u0026#34;, y); 20} Структуры-кортежи (Tuple Structs)\nПоля в структурах-кортежах не имеют названий, а определяются только своими типами. Доступ к полям осуществляется по порядковому индексу через точку: x.0, x.1. Имя структуры-кортежа выступает в роли функции-конструктора (например, Bar имеет тип fn(u32, bool) -\u0026gt; Bar). Результат выполнения: \u0026times; 1// Объявление юнит-структуры 2#[derive(Debug)] 3struct Baz; 4 5fn main() { 6 // Корректное создание (без использования скобок) 7 let x = Baz; 8 9 println!(\u0026#34;x: {:?}\u0026#34;, x); 10} Юнит-структуры (Unit-like Structs)\nОбъявляются вообще без использования скобок. Создаются также без использования скобок: let x = Baz;. Обычно используются как маркеры или для реализации типажей на них без хранения данных. Результат выполнения: \u0026times; 1// Структура с полями разного уровня видимости 2#[allow(dead_code)] 3pub struct VisibleStruct { 4 pub a: u32, // Публичное поле (доступно везде) 5 pub(crate) b: u8, // Доступно только внутри текущего крейта 6 c: u64, // Приватное поле (доступно только внутри текущего модуля) 7} 8 9fn main() { 10 let x = VisibleStruct { a: 1, b: 2, c: 3 }; 11 println!(\u0026#34;a = {}, b = {}\u0026#34;, x.a, x.b); 12 // Доступ к x.c разрешен, так как main находится в том же модуле 13 println!(\u0026#34;c = {}\u0026#34;, x.c); 14} Управление видимостью полей\nПоля структур приватны по умолчанию. Модификатор pub делает поле видимым извне текущего модуля и крейта. Модификатор pub(crate) делает поле видимым только внутри текущего крейта. Результат выполнения: \u0026times; Детали реализации и шаги: # Шаг 1. Определение и базовая инициализация:\nМы определили классические структуры с именованными полями Hub и Sensor. Инициализировали экземпляр структуры Hub с пустым вектором sensors (Vec::new()). Шаг 2. Структуры со вложенными коллекциями:\nМы создали два сенсора и проинициализировали структуру Hub, передав сенсоры во вложенный массив с помощью макроса vec!. Шаг 3. Сокращенный синтаксис и синтаксис перехода:\nField Init Shorthand: Если имена переменных совпадают с именами полей структуры, мы можем просто указать имя переменной (например, let x = Foo { a, b };). Struct Update Syntax: С помощью .. мы можем скопировать или перенести значения оставшихся полей из другого экземпляра структуры (например, let y = Foo { a: 50, ..x };). Шаг 4. Структуры-кортежи (Tuple Structs):\nИменованный тип, поля которого не имеют названий, а определяются только типами в круглых скобках: struct Bar(u32, bool);. Доступ к полям осуществляется по порядковому индексу: x.0, x.1. Тип структуры-кортежа является функцией-конструктором: let constructor: fn(u32, bool) -\u0026gt; Bar = Bar;. Часто используется для паттерна Newtype (создание безопасной обертки вокруг одного типа). Шаг 5. Юнит-структуры (Unit-like Structs):\nОбъявляются с помощью ключевого слова struct вообще без использования скобок: struct Baz;. Создаются аналогично без скобок: let x = Baz;. Шаг 6. Видимость и управление доступом к полям:\nПо умолчанию все поля приватны (доступны только внутри текущего модуля). Модификаторы видимости управляют доступом: pub — поле публично везде. pub(crate) — поле видимо только внутри текущего крейта. pub(super) — поле видимо только родительскому модулю. Примечание по видимости: Даже если поле структуры помечено как pub, сама структура должна быть доступна (публична) внешнему коду, чтобы поля можно было прочитать.\nПроверь свои знания! # Пройдите короткий интерактивный тест, чтобы закрепить понимание структур в Rust.\n","date":"4 February 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Реализация в разных стилях # Давайте посмотрим на оба подхода в действии. Ниже представлен интерактивный слайдер, где на первом шаге показан императивный стиль, а на втором — функциональный.\nСравнение императивного и функционального стилей в Rust ← Шаг 1/2 → Запуск ▶ 1#[derive(Debug)] 2struct Sensor { 3 id: String, 4 temperature: f32, 5} 6 7fn main() { 8 let sensors = vec![ 9 \u0026#34;sensor1 22.5\u0026#34;, 10 \u0026#34;sensor2 35.7\u0026#34;, 11 \u0026#34;sensor3 29.8\u0026#34;, 12 \u0026#34;sensor4 41.3\u0026#34;, 13 \u0026#34;sensor5 30.0\u0026#34; 14 ]; 15 16 let critical_threshold = 30.0; 17 let mut critical_sensors = vec![]; 18 19 // Проходимся циклом по датчикам 20 for s in sensors { 21 let mut s = s.split(\u0026#39; \u0026#39;); 22 let id = s.next(); 23 let temperature = s.next(); 24 25 if id.is_some() \u0026amp;\u0026amp; temperature.is_some() { 26 let id = id.unwrap().to_owned(); 27 let temperature = temperature.unwrap().parse::\u0026lt;f32\u0026gt;(); 28 29 if temperature.is_ok() { 30 let temperature = temperature.unwrap(); 31 if temperature \u0026gt; critical_threshold { 32 critical_sensors.push(Sensor { id, temperature }); 33 } 34 } 35 } 36 } 37 38 for s in critical_sensors { 39 println!(\u0026#34;{:?}\u0026#34;, s); 40 } 41} Императивный стиль\nМы вручную разбираем строки, выполняем проверки и наполняем изменяемый вектор critical_sensors. Код требует явного управления состоянием и частых вызовов unwrap(). Результат выполнения: \u0026times; 1#[derive(Debug)] 2struct Sensor { 3 id: String, 4 temperature: f32, 5} 6 7fn main() { 8 let sensors = vec![ 9 \u0026#34;sensor1 22.5\u0026#34;, 10 \u0026#34;sensor2 35.7\u0026#34;, 11 \u0026#34;sensor3 29.8\u0026#34;, 12 \u0026#34;sensor4 41.3\u0026#34;, 13 \u0026#34;sensor5 30.0\u0026#34; 14 ]; 15 16 let critical_threshold = 30.0; 17 18 // Функциональный стиль с цепочкой итераторов 19 let critical_sensors: Vec\u0026lt;Sensor\u0026gt; = sensors.iter() 20 .map(|s| { 21 let mut s = s.split(\u0026#39; \u0026#39;); 22 let id = s.next()?.to_owned(); 23 let temperature = s.next()?.parse::\u0026lt;f32\u0026gt;().ok()?; 24 Some(Sensor { id, temperature }) 25 }) 26 .flatten() 27 .filter(|s| s.temperature \u0026gt; critical_threshold) 28 .collect(); 29 30 for s in critical_sensors { 31 println!(\u0026#34;{:?}\u0026#34;, s); 32 } 33} Функциональный стиль\nИспользование цепочки методов (map, flatten, filter) избавляет от необходимости мутировать вектор вручную. Метод collect() собирает элементы итератора обратно в результирующий вектор Vec\u0026lt;Sensor\u0026gt;. Результат выполнения: \u0026times; Сравнение подходов: # Императивный стиль:\nОсобенности: большое количество строчек кода, ручной перебор элементов в цикле for, ручная проверка Option и Result. Минусы: код перегружен деталями управления памятью и состоянием, менее читаем, требует аллокации временных коллекций. Функциональный стиль:\nОсобенности: использование цепочки адаптеров итераторов (map, flatten, filter). Плюсы: высокая читаемость, отсутствие необходимости изменять состояние вручную, лаконичность. 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 July 2024","externalUrl":null,"permalink":"/rust/rust-collect/","section":"Rust","summary":"","title":"Rust императивный и функциональный, а также о методе collect","type":"rust"},{"content":"","date":"11 June 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 June 2024","externalUrl":null,"permalink":"/about-all/night-code/","section":"Посты обо всем","summary":"","title":"Выпуск альбома Код Ночи","type":"about-all"},{"content":"","date":"11 May 2024","externalUrl":null,"permalink":"/series/about-dart/","section":"Series","summary":"","title":"About-Dart","type":"series"},{"content":"","date":"11 May 2024","externalUrl":null,"permalink":"/tags/dart/","section":"Tags","summary":"","title":"Dart","type":"tags"},{"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 May 2024","externalUrl":null,"permalink":"/dart/streams-groups/","section":"Dart","summary":"","title":"Эффективная обработка данных из нескольких источников на Dart","type":"dart"},{"content":"","date":"23 April 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 April 2024","externalUrl":null,"permalink":"/languages/french-lang/","section":"Иностранные языки","summary":"","title":"Французский язык 🇫🇷","type":"languages"},{"content":"","date":"23 April 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 April 2024","externalUrl":null,"permalink":"/languages/chinese-lang/","section":"Иностранные языки","summary":"","title":"Китайский язык 🇨🇳","type":"languages"},{"content":" One of my hobbies is learning various foreign languages.\nMy proficiency is still far from writing full-scale articles, so I will be posting links to useful groups, materials, and websites.\nEnjoy viewing!\n","date":"April 20, 2024","externalUrl":null,"permalink":"/en/languages/","section":"Foreign Languages","summary":"","title":"Foreign Languages","type":"languages"},{"content":"","date":"April 20, 2024","externalUrl":null,"permalink":"/en/categories/languages/","section":"Categories","summary":"","title":"Languages","type":"categories"},{"content":"","date":"20 April 2024","externalUrl":null,"permalink":"/tags/start/","section":"Tags","summary":"","title":"Start","type":"tags"},{"content":"Сегодня запускаю новый раздел | категорию - Фотографии.\nПодробности на странице раздела ⬇\nПерейти на страницу фотографии\n","date":"20 April 2024","externalUrl":null,"permalink":"/about-all/new-photos/","section":"Посты обо всем","summary":"","title":"Запуск нового раздела c фотографиями 🚀","type":"about-all"},{"content":"Сегодня запускаю новый раздел | категорию - Иностранные языки.\nПодробности на странице раздела ⬇\nПерейти на страницу языки\n","date":"20 April 2024","externalUrl":null,"permalink":"/about-all/new-languages/","section":"Посты обо всем","summary":"","title":"Запуск нового раздела иностранные языки 🚀","type":"about-all"},{"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 April 2024","externalUrl":null,"permalink":"/til/tools/gramps/","section":"Сегодня я узнал","summary":"","title":"Gramps","type":"til"},{"content":"","date":"20 April 2024","externalUrl":null,"permalink":"/tags/opensource/","section":"Tags","summary":"","title":"OpenSource","type":"tags"},{"content":"","date":"20 April 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 March 2024","externalUrl":null,"permalink":"/tags/cad/","section":"Tags","summary":"","title":"Cad","type":"tags"},{"content":"","date":"30 March 2024","externalUrl":null,"permalink":"/til/cad/","section":"Сегодня я узнал","summary":"","title":"CAD","type":"til"},{"content":"Открыл для себя замечательную свободную программу для построения параметрических моделей, которые потом можно распечатать на 3d принтере.\nДля создания моделей используется собственный скриптовый язык. Скрипты писать довольно легко, главное изучить синтаксис и запомнить базовые примитивы для построения. В этом поможет туториал\nПодсверка синтаксиса, консоль с ошибками, возможность изменять параметры модели через удобную панель - все присутствует.\nhttps://openscad.org/index.html\nВот такая машина получилась 😀 ","date":"30 March 2024","externalUrl":null,"permalink":"/til/cad/openscad/","section":"Сегодня я узнал","summary":"","title":"OpenSCAD","type":"til"},{"content":"","date":"March 29, 2024","externalUrl":null,"permalink":"/en/categories/about-all/","section":"Categories","summary":"","title":"About All","type":"categories"},{"content":"","date":"March 29, 2024","externalUrl":null,"permalink":"/en/about-all/","section":"Posts about everything","summary":"","title":"Posts about everything","type":"about-all"},{"content":"","date":"29 March 2024","externalUrl":null,"permalink":"/series/smart-home/","section":"Series","summary":"","title":"Smart Home","type":"series"},{"content":"","date":"29 March 2024","externalUrl":null,"permalink":"/series/smart_home/","section":"Series","summary":"","title":"Smart_home","type":"series"},{"content":"","date":"29 March 2024","externalUrl":null,"permalink":"/tags/technology/","section":"Tags","summary":"","title":"Technology","type":"tags"},{"content":"","date":"March 29, 2024","externalUrl":null,"permalink":"/en/categories/til/","section":"Categories","summary":"","title":"Til","type":"categories"},{"content":" TIL stands for \u0026ldquo;Today I Learned\u0026rdquo;. Once, while browsing Reddit, I came across the abbreviation TIL. Millions of notes on various topics, from programming to astronomy, were gathered under this tag.\nI did some googling and found out that this is a large movement, and many people keep notes writing down what they learned today. So, here is my TIL.\n","date":"March 29, 2024","externalUrl":null,"permalink":"/en/til/","section":"Today I Learned","summary":"","title":"Today I Learned","type":"til"},{"content":" Вступление # \u0026ldquo;Вот умный дом, который построил Джек\u0026rdquo; - название, которое тут же заставляет вспомнить всем нам знакомое английское стихотворение. Но в нашем случае, Джеком становятся все разработчики умных домов в России, а дом - это образ всей отрасли, которую они строят вместе и часто отдельно друг от друга. Рассмотрим, какие проблемы и сложности возникают на этом пути.\nПроблемы развития умных домов в России # Умный дом - это система, которая обеспечивает управление и автоматизацию домашнего хозяйства. Она предполагает объединение домашних устройств для удобства и комфорта жильцов. Однако, несмотря на то, что технология умных домов находится на волне популярности, ее развитие в России сталкивается с серьезными трудностями.\nНедостаток квалифицированных кадров # Само понятие \u0026ldquo;умный дом\u0026rdquo; подразумевает применение передовых технологий. Однако, нет секрета, что в России наблюдается дефицит кадров, способных разрабатывать и внедрять подобные решения. Это приводит к тому, что многие проекты остаются нереализованными, а рынок не может оценить весь потенциал этой технологии.\nРазрозненность стандартов # Еще одна проблема заключается в отсутствии единых стандартов разработки устройств для умных домов. Каждая компания-производитель стремится создать свою уникальную систему, которая не совместима с продукцией других производителей. Это затрудняет процесс внедрения технологии, поскольку потребителю приходится выбирать между различными \u0026ldquo;экосистемами\u0026rdquo; умных домов.\nЗаключение # Пока что умный дом, который строит Джек, выглядит скорее как сложный пазл, чем готовое к проживанию здание. Отсутствие квалифицированных кадров и разрозненность стандартов замедляют развитие технологии в России. Но, несмотря на все сложности, интерес к умным домам не угасает. Надееюсь, что в ближайшем будущем ситуация изменится, и каждый сможет построить свой умный дом.\nДжек продолжает свою работу. И нам остается верить, что он сумеет справиться со всеми сложностями, ведь именно от него зависит, насколько умным будет наш дом завтра.\nПродолжение следует.\nПрисоединяйтесь, чтобы узнать больше об умном доме.\n","date":"29 March 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 March 2024","externalUrl":null,"permalink":"/about-all/smart-home/","section":"Посты обо всем","summary":"","title":"Вот умный дом, который построил Джек: проблемы развития технологии в России","type":"about-all"},{"content":"Сегодня запускаю новый раздел | категорию - TIL.\nПодробности на странице раздела ⬇\nПерейти в TIL\n","date":"29 March 2024","externalUrl":null,"permalink":"/about-all/new-til/","section":"Посты обо всем","summary":"","title":"Запуск нового раздела TIL 🚀","type":"about-all"},{"content":" Вступление # Умный дом не просто собрание устройств под одной крышей, это сложная экосистема, работающая как единое целое. И для создания такой системы требуются высококвалифицированные специалисты.\nВ России наблюдается дефицит кадров, способных разрабатывать и внедрять подобные решения. Это приводит к тому, что многие проекты остаются нереализованными, а рынок не может оценить весь потенциал этой технологии.\nМой опыт работы # По своему опыту могу сказать, что сегодня в области умных домов работают специалисты самых разных профессий. У нас в команде были специалисты из нефтяной отрасли, программисты, работавшие с промышленными контроллерами. Именно тогда я узнал о таких вещах как: программируемые логические контроллеры (ПЛК) и отраслевые стандарты на это оборудование.\nВажность обучения и развития специалистов # Важность обучения и развития специалистов в области умных домов трудно переоценить. Это сложная и многогранная область, которая включает в себя не только разработку программного обеспечения, но и знание принципов работы различных домашних устройств, сценариев использования, а также способность интегрировать эти устройства в единую систему.\nОбразование и умные дома в России # Сегодня, к сожалению, высшее образование в России не предлагает специализированных программ, которые бы готовили специалистов по умным домам. Большинство специалистов приходят в эту сферу из других областей IT и учатся на ходу. Эта область не раскрыта, не получила широкого распространения. Вендоры сами вынуждены приходить в институты и колледжи и предлагать провести занятия, объяснить студентам перспективы умных домов и глубже погрузить в предмет. Конечно вендоры делают акцент на своих устройствах и собственной инфраструктуре.\nТакже на своем опыте убедился, что в технических колледжах добавляют в предметы робототехнику и электронику, закупают распбери и ардуины, но квалификации преподавательского состава не хватает чтобы все это нормально настроить и собрать интересные проекты. Хочется верить, что просто мне не повезло и в стране есть другие примеры.\nВозможности для обучения и развития # Недостаток квалифицированных кадров – это возможность для тех, кто готов учиться и развиваться. Ведь спрос на эти услуги будет только расти, а значит и специалисты в этой области будут востребованы.\nАнонс следующей заметки # В следующей заметке хочу рассмотреть еще одну важную проблему в развитии умных домов в России - разрозненность стандартов. Присоединяйтесь, чтобы узнать больше об умном доме.\n","date":"29 March 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 March 2024","externalUrl":null,"permalink":"/til/swift/develop_in_swift_tutorials/","section":"Сегодня я узнал","summary":"","title":"Develop in swift tutorials","type":"til"},{"content":" ","date":"29 March 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 March 2024","externalUrl":null,"permalink":"/til/flutter/1711660895366-flutter/","section":"Сегодня я узнал","summary":"","title":"Assets-for-api-docs","type":"til"},{"content":" ","date":"28 March 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 March 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 March 2024","externalUrl":null,"permalink":"/rust/start-rust/","section":"Rust","summary":"","title":"Basic rust","type":"rust"},{"content":" Введение # В языке программирования Rust, match является мощной конструкцией, которая позволяет сравнивать значение с серией шаблонов и выполнять код в зависимости от совпадения. Рассмотрим различные виды и применения match.\nБазовый синтаксис, деструктуризация и условия # Конструкция match умеет значительно больше, чем простое сравнение числа с литералами. Она умеет деструктурировать сложные типы данных и уточнять шаблоны с помощью дополнительных условий — Match Guards.\nДавайте разберём основные приёмы на интерактивном слайде:\nОсновы match: синтаксис, деструктуризация, Match Guards ← Шаг 1/3 → Запуск ▶ 1fn main() { 2 let season = \u0026#34;лето\u0026#34;; 3 4 match season { 5 \u0026#34;весна\u0026#34; =\u0026gt; println!(\u0026#34;Весна — всё цветёт\u0026#34;), 6 \u0026#34;лето\u0026#34; =\u0026gt; println!(\u0026#34;Лето — самое тёплое время\u0026#34;), 7 \u0026#34;осень\u0026#34; =\u0026gt; println!(\u0026#34;Осень — время листопада\u0026#34;), 8 \u0026#34;зима\u0026#34; =\u0026gt; println!(\u0026#34;Зима — время снега\u0026#34;), 9 _ =\u0026gt; println!(\u0026#34;Неизвестное время года\u0026#34;), 10 } 11} Базовый синтаксис match\nКонструкция match сравнивает значение с каждой «веткой» по порядку и выполняет код первого совпадения. Паттерн _ — специальный «заглушитель», который срабатывает, если ни один другой шаблон не совпал. Rust требует, чтобы все возможные значения были обработаны. Результат выполнения: \u0026times; 1fn main() { 2 let pair = (0, -2); 3 4 match pair { 5 (first, second) if first == 0 =\u0026gt; println!(\u0026#34;Первый — 0, второй: {}\u0026#34;, second), 6 (first, second) if second == 0 =\u0026gt; println!(\u0026#34;Первый: {}, второй — 0\u0026#34;, first), 7 _ =\u0026gt; println!(\u0026#34;Другая комбинация\u0026#34;), 8 } 9} Match с деструктуризацией кортежей\nmatch умеет разбирать (деструктурировать) сложные типы данных, например кортежи. Здесь (first, second) if first == 0 совпадает с любым кортежем, где первый элемент равен 0, и связывает оба элемента с переменными. Результат выполнения: \u0026times; 1fn main() { 2 let pair = (4, 7); 3 4 match pair { 5 (x, y) if x == y =\u0026gt; println!(\u0026#34;Числа одинаковые: {} и {}\u0026#34;, x, y), 6 (x, y) if x + y == 0 =\u0026gt; println!(\u0026#34;{} и {} — противоположны (сумма = 0)\u0026#34;, x, y), 7 (x, _) if x % 2 == 0 =\u0026gt; println!(\u0026#34;Первое число {} — чётное\u0026#34;, x), 8 _ =\u0026gt; println!(\u0026#34;Нет особой связи\u0026#34;), 9 } 10} Match с условиями (Match Guards)\nПосле шаблона можно дополнительно указать условие с помощью ключевого слова if. Ветка срабатывает только если шаблон совпал и условие выполняется. Это позволяет точно и лаконично фильтровать конкретные сценарии. Результат выполнения: \u0026times; Match с перечислениями (Enums) # match особенно мощен в работе с перечислениями. Варианты enum могут не только быть простыми маркерами, но и хранить данные — и match позволяет их безопасно извлечь прямо в ветке шаблона.\nMatch с перечислениями: Color и Exercise ← Шаг 1/2 → Запуск ▶ 1// ?hidden:start 2#[allow(dead_code)] 3// ?hidden:end 4enum Color { 5 Red, 6 Blue, 7 Green, 8 RGB(u32, u32, u32), 9} 10 11fn main() { 12 let color = Color::RGB(122, 17, 40); 13 14 match color { 15 Color::Red =\u0026gt; println!(\u0026#34;Красный\u0026#34;), 16 Color::Blue =\u0026gt; println!(\u0026#34;Синий\u0026#34;), 17 Color::Green =\u0026gt; println!(\u0026#34;Зелёный\u0026#34;), 18 Color::RGB(r, g, b) =\u0026gt; println!(\u0026#34;RGB: {}, {}, {}\u0026#34;, r, g, b), 19 } 20} Match с перечислениями (Enums)\nmatch — основной инструмент для работы с перечислениями (enum) в Rust. Каждая ветка соответствует одному из вариантов enum. Компилятор проверяет, что все варианты обработаны. Вариант Color::RGB(r, g, b) извлекает (деструктурирует) числа из данных, хранящихся внутри этого варианта enum. Результат выполнения: \u0026times; 1// ?hidden:start 2#[allow(dead_code)] 3// ?hidden:end 4enum Exercise { 5 Run { distance: f64 }, 6 Swim { duration: u32 }, 7 Yoga, 8} 9 10fn process_exercise(exercise: Exercise) { 11 match exercise { 12 Exercise::Run { distance } =\u0026gt; { 13 println!(\u0026#34;Ты пробежал {} км. Отлично!\u0026#34;, distance); 14 } 15 Exercise::Swim { duration } =\u0026gt; { 16 println!(\u0026#34;Ты плавал {} минут. Продолжай в том же духе!\u0026#34;, duration); 17 } 18 Exercise::Yoga =\u0026gt; { 19 println!(\u0026#34;Отличная работа — норма йоги на сегодня выполнена!\u0026#34;); 20 } 21 } 22} 23 24fn main() { 25 process_exercise(Exercise::Run { distance: 10.5 }); 26 process_exercise(Exercise::Swim { duration: 45 }); 27 process_exercise(Exercise::Yoga); 28} Match с богатыми данными внутри Enum\nВарианты enum могут хранить именованные поля (как в структуре). В шаблоне Exercise::Run { distance } мы одновременно проверяем вариант и извлекаем поле distance. Это позволяет безопасно и элегантно обработать разные «формы» одного типа. Результат выполнения: \u0026times; Шаблоны match позволяют обрабатывать различные варианты и извлекать связанные данные — безопасно и эффективно. Это даёт нам возможность легко обрабатывать различные случаи и извлекать данные из сложных типов, сохраняя при этом чистоту и ясность кода.\nКогда мы используем match, мы не только упрощаем наш код, но и делаем его более надёжным. Компилятор гарантирует, что мы обработали все возможные варианты — иначе код просто не скомпилируется.\nЗаключение # match в Rust — это мощный инструмент, который делает код более ясным и безопасным. Он позволяет обрабатывать различные случаи и извлекать данные из сложных типов в удобной и безопасной манере.\nТакже приятно отметить, что механизм паттерн-матчинга появился начиная с версии 3 в Dart (там используется ключевое слово switch) и активно применяется в языке Elixir.\nПроверь свои знания! # Пройдите короткий интерактивный тест, чтобы закрепить понимание Pattern Matching в Rust.\n","date":"16 March 2024","externalUrl":null,"permalink":"/rust/match/","section":"Rust","summary":"","title":"Pattern matching","type":"rust"},{"content":" Привет и добро пожаловать на мой блог!\nРаньше я его вел на платформе Ghost, но домен был для меня недоступен и лишь недавно удалось его восстановить. Вместо того, чтобы поднимать всю систему снова, я решил что в этот раз будет интереснее попробовать что-то новое. И остановился на статическом генераторе сайтов Hugo.\nСпросите, а хорошие старые статьи из прошлого блога пропали? Вовсе нет, они ни куда не делись. Постепенно я начну переносить их сюда.\nЧто же я буду здесь публиковать? Помимо статей на тему технологий таких как Flutter/Dart, Java/Kotlin, Golang, Elixir, Linux, Docker, Rust и прочих, также буду рассказывать о своих хобби. Подкину пару полезных штук, лайфхаков, и просто поделюсь своими размышлениями о жизни, технологиях и окружающем мире.\nСпасибо за твое внимание и за то, что заглянул на эту страничку.\nПриятного чтения! :waving_hand:\n","date":"13 March 2024","externalUrl":null,"permalink":"/about-all/start/","section":"Посты обо всем","summary":"","title":"Мой первый пост после перезапуска 🚀","type":"about-all"},{"content":" Недавние / Обновленные Видео и подкасты недели: мобильная разработка 17 июля 2026\u0026middot;530 слов\u0026middot;3 минуты\u0026middot; loading \u0026middot; loading Ios Android Swift Kotlin Ai Mobile Замыкания в Rust: функции с собственной памятью 1282 слова\u0026middot;7 минут\u0026middot; loading \u0026middot; loading \u0026middot;Rust-middle Rust Dev Closures Advanced Обработка ошибок в Rust: паника, Option, Result и как с ними дружить 1360 слов\u0026middot;7 минут\u0026middot; loading \u0026middot; loading \u0026middot;Rust-middle Rust Dev Errors Система типов в Rust: почему компилятор — ваш лучший друг 955 слов\u0026middot;5 минут\u0026middot; loading \u0026middot; loading \u0026middot;Rust-basic Rust Dev Theory Кортежи (Tuples) в Rust 600 слов\u0026middot;3 минуты\u0026middot; loading \u0026middot; loading \u0026middot;Rust-basic Rust Dev Низкоуровневое представление типов в памяти в Rust (Type Layout) 728 слов\u0026middot;4 минуты\u0026middot; loading \u0026middot; loading \u0026middot;Rust-middle Rust Dev Операторы let-else и пропагации ошибок в Rust 821 слово\u0026middot;4 минуты\u0026middot; loading \u0026middot; loading \u0026middot;Rust-middle Rust Dev Перечисления (Enums) в Rust 639 слов\u0026middot;3 минуты\u0026middot; loading \u0026middot; loading \u0026middot;Rust-basic Rust Dev Условные конструкции и ветвление в Rust 892 слова\u0026middot;5 минут\u0026middot; loading \u0026middot; loading \u0026middot;Rust-basic Rust Dev Циклы и диапазоны в Rust 1120 слов\u0026middot;6 минут\u0026middot; loading \u0026middot; loading \u0026middot;Rust-basic Rust Dev Rust-Basic Tokio: как отправить и принять множество событий 1847 слов\u0026middot;9 минут\u0026middot; loading \u0026middot; loading \u0026middot;Rust-middle Rust Dev Как использовать монопод правильно: Секреты стабильного кадра 28 апреля 2026\u0026middot;463 слова\u0026middot;3 минуты\u0026middot; loading \u0026middot; loading Photography Штатив Монопод Советы Техника Опыт важнее мегапикселей: Почему стоит прочитать мануал к камере 27 апреля 2026\u0026middot;361 слово\u0026middot;2 минуты\u0026middot; loading \u0026middot; loading Photography Опыт Техника Советы Магия кривых: Как убрать паразитные оттенки и сделать красивый тон 26 апреля 2026\u0026middot;347 слов\u0026middot;2 минуты\u0026middot; loading \u0026middot; loading Photography Обработка Коррекция Советы Основы композиции: Правило третей 25 апреля 2026\u0026middot;306 слов\u0026middot;2 минуты\u0026middot; loading \u0026middot; loading Photography Композиция Советы Фотографии 20 апреля 2026\u0026middot; loading \u0026middot; loading My-Gallery Фото Каналы в Rust при поддержке Tokio Обновлено Обновлено: 15 июля 2026\u0026middot;1217 слов\u0026middot;6 минут\u0026middot; loading \u0026middot; loading \u0026middot;Rust-middle Rust Dev Android 15 и 16 KB страница памяти: что изменится, как подготовить Android и Flutter-приложения 25 сентября 2025\u0026middot;964 слова\u0026middot;5 минут\u0026middot; loading \u0026middot; loading Android Flutter Dev Rust async и Tokio: В ожидании будущего Обновлено Обновлено: 15 июля 2026\u0026middot;1899 слов\u0026middot;9 минут\u0026middot; loading \u0026middot; loading \u0026middot;Rust-middle Rust Dev Как работает Vec в Rust: устройство, методы, оптимизация 2274 слова\u0026middot;11 минут\u0026middot; loading \u0026middot; loading \u0026middot;Rust-middle Rust Dev BufReader в Rust: ускоряем ввод-вывод 1861 слово\u0026middot;9 минут\u0026middot; loading \u0026middot; loading \u0026middot;Rust-middle Rust Dev Поиск данных в файле - Cli хелпер для мастера с помощью Rust 1812 слов\u0026middot;9 минут\u0026middot; loading \u0026middot; loading \u0026middot;Rust-basic Rust Dev Ввод данных от пользователя в Rust: как использовать stdin 344 слова\u0026middot;2 минуты\u0026middot; loading \u0026middot; loading \u0026middot;Rust-basic Rust Dev Arc в Rust: потокобезопасное разделённое владение 576 слов\u0026middot;3 минуты\u0026middot; loading \u0026middot; loading \u0026middot;Rust-middle Rust Dev Rc в Rust: Руководство по разделённому владению и RefCell 775 слов\u0026middot;4 минуты\u0026middot; loading \u0026middot; loading \u0026middot;Rust-middle Rust Dev Заимствование (Borrowing) в Rust Обновлено Обновлено: 15 июля 2026\u0026middot;796 слов\u0026middot;4 минуты\u0026middot; loading \u0026middot; loading \u0026middot;Rust-middle Rust Dev Владение (Ownership) в Rust Обновлено Обновлено: 15 июля 2026\u0026middot;772 слова\u0026middot;4 минуты\u0026middot; loading \u0026middot; loading \u0026middot;Rust-basic Rust Dev Директивы компилятора в Rust 312 слов\u0026middot;2 минуты\u0026middot; loading \u0026middot; loading \u0026middot;Rust-basic Rust Dev Псевдонимы типов в Rust или как сделать код понятнее 414 слов\u0026middot;2 минуты\u0026middot; loading \u0026middot; loading \u0026middot;Rust-basic Rust Dev Создание и работа с константами в Rust Обновлено Обновлено: 15 июля 2026\u0026middot;564 слова\u0026middot;3 минуты\u0026middot; loading \u0026middot; loading \u0026middot;Rust-basic Rust Dev Области видимости в Rust Обновлено Обновлено: 15 июля 2026\u0026middot;480 слов\u0026middot;3 минуты\u0026middot; loading \u0026middot; loading \u0026middot;Rust-basic Rust Dev Затенение переменных в Rust Обновлено Обновлено: 15 июля 2026\u0026middot;387 слов\u0026middot;2 минуты\u0026middot; loading \u0026middot; loading \u0026middot;Rust-basic Rust Dev Разбираемся с ошибками в Rust Обновлено Обновлено: 14 июля 2026\u0026middot;768 слов\u0026middot;4 минуты\u0026middot; loading \u0026middot; loading \u0026middot;Rust-basic Rust Dev Изменяемые и неизменяемые переменные в Rust Обновлено Обновлено: 15 июля 2026\u0026middot;349 слов\u0026middot;2 минуты\u0026middot; loading \u0026middot; loading \u0026middot;Rust-basic Rust Dev Работа с println! и интерполяцией строк в Rust 567 слов\u0026middot;3 минуты\u0026middot; loading \u0026middot; loading \u0026middot;Rust-basic Rust Dev Переменные в Rust: основы и примеры использования Обновлено Обновлено: 15 июля 2026\u0026middot;329 слов\u0026middot;2 минуты\u0026middot; loading \u0026middot; loading \u0026middot;Rust-basic Rust Dev Kotlin 2.1.20: Новые возможности и улучшения для разработчиков 26 марта 2025\u0026middot;712 слов\u0026middot;4 минуты\u0026middot; loading \u0026middot; loading Kotlin Hash Map в Rust Обновлено Обновлено: 15 июля 2026\u0026middot;962 слова\u0026middot;5 минут\u0026middot; loading \u0026middot; loading \u0026middot;Rust-basic 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 Обновлено Обновлено: 15 июля 2026\u0026middot;723 слова\u0026middot;4 минуты\u0026middot; loading \u0026middot; loading \u0026middot;Rust-middle Rust Dev Как отправить несколько файлов одним сообщением через Telegram Bot API 8 февраля 2025\u0026middot;343 слова\u0026middot;2 минуты\u0026middot; loading \u0026middot; loading About All Создание и использование структур в Rust: Практическое руководство Обновлено Обновлено: 15 июля 2026\u0026middot;1293 слова\u0026middot;7 минут\u0026middot; loading \u0026middot; loading \u0026middot;Rust-basic Rust Dev Rust императивный и функциональный, а также о методе collect Обновлено Обновлено: 15 июля 2026\u0026middot;980 слов\u0026middot;5 минут\u0026middot; loading \u0026middot; loading \u0026middot;Rust-middle 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 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 \u0026middot;Rust-basic Rust Dev Pattern matching Обновлено Обновлено: 15 июля 2026\u0026middot;810 слов\u0026middot;4 минуты\u0026middot; loading \u0026middot; loading \u0026middot;Rust-basic Rust Dev Мой первый пост после перезапуска 🚀 13 марта 2024\u0026middot;132 слова\u0026middot;1 минута\u0026middot; loading \u0026middot; loading About All Start Моя статистика Rust async и Tokio: В ожидании будущего Обновления: Обновлен текст статьи 1899 слов\u0026middot;9 минут\u0026middot; loading \u0026middot; loading \u0026middot;Rust-middle Rust Dev Заимствование (Borrowing) в Rust Обновления: Обновлен текст статьи 796 слов\u0026middot;4 минуты\u0026middot; loading \u0026middot; loading \u0026middot;Rust-middle Rust Dev Владение (Ownership) в Rust Обновления: Обновлен текст статьи 772 слова\u0026middot;4 минуты\u0026middot; loading \u0026middot; loading \u0026middot;Rust-basic Rust Dev Переменные в Rust: основы и примеры использования Обновления: Обновлен текст статьи Добавлены примеры кода 329 слов\u0026middot;2 минуты\u0026middot; loading \u0026middot; loading \u0026middot;Rust-basic Rust Dev Создание и использование структур в Rust: Практическое руководство Обновления: Обновлен текст статьи Обновлены примеры кода Добавлены примеры кода 1293 слова\u0026middot;7 минут\u0026middot; loading \u0026middot; loading \u0026middot;Rust-basic Rust Dev Каналы в Rust при поддержке Tokio Обновления: Добавлены примеры кода 1217 слов\u0026middot;6 минут\u0026middot; loading \u0026middot; loading \u0026middot;Rust-middle Rust Dev Создание и работа с константами в Rust Обновления: Добавлены примеры кода 564 слова\u0026middot;3 минуты\u0026middot; loading \u0026middot; loading \u0026middot;Rust-basic Rust Dev Области видимости в Rust Обновления: Добавлены примеры кода 480 слов\u0026middot;3 минуты\u0026middot; loading \u0026middot; loading \u0026middot;Rust-basic Rust Dev Затенение переменных в Rust Обновления: Добавлены примеры кода 387 слов\u0026middot;2 минуты\u0026middot; loading \u0026middot; loading \u0026middot;Rust-basic Rust Dev Изменяемые и неизменяемые переменные в Rust Обновления: Добавлены примеры кода 349 слов\u0026middot;2 минуты\u0026middot; loading \u0026middot; loading \u0026middot;Rust-basic Rust Dev Hash Map в Rust Обновления: Добавлены примеры кода 962 слова\u0026middot;5 минут\u0026middot; loading \u0026middot; loading \u0026middot;Rust-basic Rust Dev Паттерн \"Строитель\" в Rust Обновления: Добавлены примеры кода 723 слова\u0026middot;4 минуты\u0026middot; loading \u0026middot; loading \u0026middot;Rust-middle Rust Dev Rust императивный и функциональный, а также о методе collect Обновления: Добавлены примеры кода Обновлен текст статьи 980 слов\u0026middot;5 минут\u0026middot; loading \u0026middot; loading \u0026middot;Rust-middle Rust Dev Pattern matching Обновления: Добавлены примеры кода 810 слов\u0026middot;4 минуты\u0026middot; loading \u0026middot; loading \u0026middot;Rust-basic Rust Dev Разбираемся с ошибками в Rust Обновления: Добавлены примеры кода 768 слов\u0026middot;4 минуты\u0026middot; loading \u0026middot; loading \u0026middot;Rust-basic Rust Dev ","date":"20 February 2024","externalUrl":null,"permalink":"/posts/","section":"Welcome to my blog! 🎉","summary":"","title":"Все посты","type":"page"},{"content":"","externalUrl":null,"permalink":"/en/authors/","section":"Authors","summary":"","title":"Authors","type":"authors"},{"content":"","externalUrl":null,"permalink":"/en/categories/dart/","section":"Categories","summary":"","title":"Dart","type":"categories"},{"content":" My main programming language currently is Dart. A lot can be said about its convenience, beauty, and elegance, but one shouldn\u0026rsquo;t forget its downsides either. Instead of long discussions, let\u0026rsquo;s see in practice how convenient using Dart can be. Note: Instead of a thousand words - lines of Dart code. Flutter is waiting for a separate section\n","externalUrl":null,"permalink":"/en/dart/","section":"Dart","summary":"","title":"Dart","type":"dart"},{"content":"","externalUrl":null,"permalink":"/en/stats/","section":"Welcome to my blog! 🎉","summary":"","title":"My Statistics","type":"page"}]