From 17f37947f37d5860a15c755d71c5cf7970a07b38 Mon Sep 17 00:00:00 2001 From: Rorik Star Platinum Date: Thu, 27 Nov 2025 23:37:40 +0300 Subject: [PATCH] hardware --- .../00-utils/sops/base workflow with sops.md | 24 +++ 10-linux/00-utils/ssh/Untitled.md | 2 + 10-linux/10-nixos/deploy.md | 16 -- .../Кэш, регистры, кэш-линия.md | 88 ++++++++++ 20-dev/00-rust/rustlings/Untitled.md | 85 ++++++++++ 20-dev/00-rust/smart pointers/Rc.md | 159 ++++++++++++++++++ 20-dev/00-rust/smart pointers/deref.md | 67 ++++++++ 20-dev/00-rust/smart pointers/drop.md | 60 +++++++ 8 files changed, 485 insertions(+), 16 deletions(-) create mode 100644 10-linux/00-utils/sops/base workflow with sops.md create mode 100644 10-linux/00-utils/ssh/Untitled.md create mode 100644 10-linux/30-computer-arch/Кэш, регистры, кэш-линия.md create mode 100644 20-dev/00-rust/rustlings/Untitled.md create mode 100644 20-dev/00-rust/smart pointers/Rc.md create mode 100644 20-dev/00-rust/smart pointers/deref.md create mode 100644 20-dev/00-rust/smart pointers/drop.md diff --git a/10-linux/00-utils/sops/base workflow with sops.md b/10-linux/00-utils/sops/base workflow with sops.md new file mode 100644 index 0000000..61cbce9 --- /dev/null +++ b/10-linux/00-utils/sops/base workflow with sops.md @@ -0,0 +1,24 @@ + ``` + age-keygen -y ~/.config/sops/age/keys.txt + ``` +Вот это выведет публичный ключ для вставки в .sops + +пример содержимого `.sops` + +``` +# .sops.yaml +creation_rules: + - path_regex: secrets.yaml + key_groups: + - age: + - age12345...6789 + +``` + +затем + +`sops secrets.yaml` + +и вуаля - там уже образец примера файла будет. + + diff --git a/10-linux/00-utils/ssh/Untitled.md b/10-linux/00-utils/ssh/Untitled.md new file mode 100644 index 0000000..d89f572 --- /dev/null +++ b/10-linux/00-utils/ssh/Untitled.md @@ -0,0 +1,2 @@ +скопировать что нибудь +`rsync -avz ./themes/ user@remote_host:~/.config/helix/themes/` \ No newline at end of file diff --git a/10-linux/10-nixos/deploy.md b/10-linux/10-nixos/deploy.md index eb24d97..e69de29 100644 --- a/10-linux/10-nixos/deploy.md +++ b/10-linux/10-nixos/deploy.md @@ -1,16 +0,0 @@ -# 🚀 ФИНАЛЬНЫЙ DEPLOYMENT СКРИПТ - -bash - -`#!/bin/bash # ~/deploy-both.sh # Deployment script for German VPN server + Russian VDS set -e echo "╔═════════════════════════════════════════════════════════╗" echo "║ 🚀 DUAL SERVER NIXOS DEPLOYMENT (nixos-anywhere) ║" echo "╚═════════════════════════════════════════════════════════╝" echo "" # ============================================================ # CONFIG # ============================================================ GERMAN_REPO="$HOME/nix-server" RUSSIAN_REPO="$HOME/vds-ru" GERMAN_SERVER="root@64.188.70.209" RUSSIAN_SERVER="root@176.108.250.130" # ============================================================ # FUNCTION: Deploy one server # ============================================================ deploy_server() { local REPO="$1" local SERVER="$2" local SERVER_NAME="$3" local GENERATE_SECRETS="$4" # "yes" or "no" echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" echo "🎯 DEPLOYING: $SERVER_NAME" echo " Repo: $REPO" echo " Server: $SERVER" echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" echo "" cd "$REPO" # Generate secrets if needed if [ "$GENERATE_SECRETS" = "yes" ]; then echo "📝 Generating secrets..." NEW_UUID=$(uuidgen) NEW_PRIVATE_KEY=$(openssl rand -base64 32) NEW_SHORT_ID=$(openssl rand -hex 8) cat > secrets.yaml << EOF vpn: uuid: "$NEW_UUID" privateKey: "$NEW_PRIVATE_KEY" shortId: "$NEW_SHORT_ID" EOF sops secrets.yaml echo "✅ Secrets encrypted" echo "" fi # Backup echo "💾 Creating backup..." mkdir -p ~/backups ssh "$SERVER" 'tar czf /tmp/backup.tar.gz /etc/nixos/ /var/lib/sing-box/ 2>/dev/null || true' 2>/dev/null || true BACKUP_NAME="$(echo $SERVER | cut -d'@' -f2)-backup-$(date +%Y-%m-%d-%H%M%S).tar.gz" scp "$SERVER":/tmp/backup.tar.gz ~/backups/"$BACKUP_NAME" 2>/dev/null || echo "⚠️ Backup skipped" echo "✅ Backup: $BACKUP_NAME" echo "" # Deploy echo "🚀 Running nixos-anywhere..." echo " (this will take 5-10 minutes)" echo "" nixos-anywhere --flake .#server "$SERVER" echo "" echo "⏳ Waiting for system to boot (120 seconds)..." sleep 120 echo "✅ Boot complete" echo "" # Verify echo "🔍 Verifying deployment..." echo "" # Try to find the non-root user (nxoska or similar) NON_ROOT_USER=$(ssh "$SERVER" 'getent passwd | grep -E ":(1000|1001):" | cut -d: -f1 | head -1') if [ -z "$NON_ROOT_USER" ]; then NON_ROOT_USER="root" fi VERIFY_SERVER="${NON_ROOT_USER}@$(echo $SERVER | cut -d'@' -f2)" ssh "$VERIFY_SERVER" << 'VERIFY_SCRIPT' echo "=== Uptime ===" uptime echo "" echo "=== System Status ===" systemctl is-system-running echo "" echo "=== Time Synchronization ===" timedatectl status | head -4 echo "" echo "=== sing-box Service (if applicable) ===" sudo systemctl status sing-box --no-pager 2>/dev/null | head -8 || echo "N/A (not a VPN server)" echo "" echo "=== Port 443 (if applicable) ===" sudo ss -tulpn 2>/dev/null | grep 443 || echo "N/A (not listening)" echo "" echo "=== NixOS Generation ===" sudo nixos-rebuild list-generations | head -3 VERIFY_SCRIPT echo "" echo "✅ $SERVER_NAME deployment complete!" echo "" echo "" } # ============================================================ # MAIN: Ask user which servers to deploy # ============================================================ echo "Which servers do you want to deploy?" echo "" echo "1) German server only (nix-server VPN)" echo "2) Russian server only (vds-ru)" echo "3) Both servers" echo "" read -p "Choose [1-3]: " CHOICE case $CHOICE in 1) deploy_server "$GERMAN_REPO" "$GERMAN_SERVER" "German Server (VPN)" "yes" ;; 2) deploy_server "$RUSSIAN_REPO" "$RUSSIAN_SERVER" "Russian Server (VDS)" "no" ;; 3) deploy_server "$GERMAN_REPO" "$GERMAN_SERVER" "German Server (VPN)" "yes" deploy_server "$RUSSIAN_REPO" "$RUSSIAN_SERVER" "Russian Server (VDS)" "no" ;; *) echo "❌ Invalid choice!" exit 1 ;; esac # ============================================================ # FINAL STATUS # ============================================================ echo "╔═════════════════════════════════════════════════════════╗" echo "║ ✅ DEPLOYMENT COMPLETED SUCCESSFULLY! ║" echo "╚═════════════════════════════════════════════════════════╝" echo "" echo "📝 Next steps:" echo " 1. Test your services" echo " 2. Commit changes to git:" echo " cd ~/nix-server && git add . && git commit -m '🚀 Deploy with nixos-anywhere'" echo " git push" echo "" echo "📚 Documentation:" echo " See: server-deployment.md" echo ""` - ---- - -## КАК ИСПОЛЬЗОВАТЬ 🎬 - -bash - -`# 1. Создай скрипт cat > ~/deploy-both.sh << 'PASTE_ENTIRE_SCRIPT_ABOVE' # 2. Дай права chmod +x ~/deploy-both.sh # 3. Запусти ~/deploy-both.sh # 4. Выбери вариант: # 1 = German only # 2 = Russian only # 3 = Both` - ---- - diff --git a/10-linux/30-computer-arch/Кэш, регистры, кэш-линия.md b/10-linux/30-computer-arch/Кэш, регистры, кэш-линия.md new file mode 100644 index 0000000..11347ec --- /dev/null +++ b/10-linux/30-computer-arch/Кэш, регистры, кэш-линия.md @@ -0,0 +1,88 @@ +Давай разберем эту иерархию от самого быстрого и "близкого" к мозгам процессора до более объемного. Это можно представить как пирамиду: чем ближе к вершине, тем меньше памяти, но доступ к ней мгновенный. + +### 1. Регистры (Registers) +Это самая быстрая память, которая находится прямо в ядре процессора. +* **Аналогия:** Это твои **руки**. Данные здесь — это то, что ты держишь прямо сейчас. +* **Объем:** Крошечный. Регистров всего несколько десятков штук (например, 16 регистров общего назначения в x86-64). +* **Размер:** В архитектуре x86-64 (64-битной) один регистр вмещает ровно **64 бита** (8 байт). +* **Скорость:** **0 тактов** (мгновенно). Арифметические операции (сложение, умножение) процессор может делать *только* над значениями, которые уже лежат в регистрах. +* **SIMD-регистры:** Это особые широкие регистры (128, 256 или 512 бит), в которые можно положить сразу пачку чисел (например, четыре `float32`) и обработать их одной инструкцией. Именно их использует Rust-компилятор для векторизации. + +### 2. Кэш процессора (CPU Cache) +Это сверхоперативная память типа **SRAM** (Static RAM), расположенная на кристалле процессора. Она нужна, чтобы процессор не простаивал, ожидая данные из медленной оперативной памяти (RAM). +* **Аналогия:** Это **верстак** или рабочий стол. Данные здесь лежат "под рукой". +* **Уровни (L1, L2, L3):** + * **L1 (Level 1):** Самый маленький (например, 32 КБ для команд + 32 КБ для данных на ядро), но самый быстрый. Доступ занимает ~3-4 такта. + * **L2:** Побольше (256 КБ - 1 МБ на ядро), чуть медленнее (~10-12 тактов). + * **L3:** Общий для всех ядер (десятки МБ), еще медленнее (~40-50 тактов), но все равно намного быстрее RAM. + +### 3. Кэш-линия (Cache Line) +Это **минимальная единица обмена** данными между RAM и кэшем. +* **Размер:** Стандарт индустрии — **64 байта**. +* **Суть:** Процессор не умеет читать из памяти "один байт". Если твой код просит переменную типа `u8` (1 байт), процессор всё равно загрузит из RAM целую линию (64 байта), в которой лежит этот байт, и поместит её в кэш. +* **Почему это важно для структур данных:** + * Если у тебя **массив** (или `Vec`), данные лежат плотно. Загрузив одну кэш-линию, процессор получает сразу 16 чисел `i32` (так как 16 * 4 байта = 64). Следующие 15 обращений к массиву будут мгновенными (взяты из L1 кэша). + * Если у тебя **связный список**, каждый элемент может лежать в памяти далеко друг от друга. Чтобы прочитать 16 элементов, процессору придется 16 раз сходить в медленную RAM, загружая 16 разных кэш-линий, из которых полезными будут только по 4-8 байт. Это называется **Cache Miss** (промах кэша), и это убивает производительность. + +### Итог +1. Процессор загружает данные из RAM **кэш-линиями** (по 64 байта) в **кэш**. +2. Из кэша данные попадают в **регистры** (по 8 байт). +3. АЛУ (арифметико-логическое устройство) выполняет операции над регистрами. + +В B-Tree мы храним ключи массивом, поэтому одна загрузка кэш-линии дает нам сразу много ключей для сравнения, и мы максимально эффективно используем эту механику. + +[1](https://ru.wikipedia.org/wiki/%D0%9A%D1%8D%D1%88_%D0%BF%D1%80%D0%BE%D1%86%D0%B5%D1%81%D1%81%D0%BE%D1%80%D0%B0) +[2](https://market.marvel.ru/blog/komplektuyushchie-i-optsii/kesh-pamyat-protsessora/) +[3](https://club.dns-shop.ru/blog/t-100-protsessoryi/37338-chto-takoe-kesh-v-protsessore-i-zachem-on-nujen/) +[4](https://man-made.ru/articles/chto-takoe-kesh-pamyat-protsessora/) +[5](https://otus.ru/journal/kesh-processora-opisanie-urovni-osobennosti/) +[6](https://skyeng.ru/magazine/wiki/it-industriya/chto-takoe-kesh-pamiat/) +[7](https://overclockers.ru/blog/Hardware_inc/show/240934/Kesh-processora-chto-eto-takoe-i-kak-on-zastavlyaet-vash-komp-juter-rabotat-bystree-Chast-1) +[8](https://compress.ru/article.aspx?id=23541) +[9](https://habr.com/ru/companies/vdsina/articles/515660/) +[10](https://www.youtube.com/watch?v=7n_8cOBpQrg) + + +Помимо иерархии памяти (Регистры → Кэш → RAM), для написания высокопроизводительного кода (особенно на Rust/C++) критически важно понимать еще три концепции. Они объясняют, почему "наивный" код часто работает медленнее, чем мог бы. + +### 1. Конвейер (Pipeline) и Предсказание ветвлений (Branch Prediction) + +Современный процессор — это фабрика. Он не делает одну инструкцию за раз; он выполняет их параллельно на разных стадиях (чтение, декодирование, выполнение, запись). +* **Проблема:** Когда процессор видит условный переход `if` (ветвление), он не знает, куда пойдет код дальше, пока условие не вычислится. +* **Решение:** Процессор **угадывает** (Branch Prediction). Он начинает выполнять ветку `true` заранее (спекулятивное выполнение). +* **Цена ошибки:** Если процессор не угадал (Branch Misprediction), он должен выбросить все вычисления и начать заново с правильной ветки. Это огромная потеря времени (10-20 тактов). +* **Для разработчика:** + * Отсортированные массивы обрабатываются быстрее (паттерн ветвлений предсказуем: TTTTFFFF). + * В B-Tree поиск внутри узла часто делают линейным (без ветвлений, через SIMD) или оптимизированным бинарным, чтобы не сбивать предсказатель. + +### 2. Виртуальная память и TLB (Translation Lookaside Buffer) + +Адреса, которые видит твоя программа (например, указатель `0x7ff...`), — ненастоящие. Это **виртуальные адреса**. Процессор должен каждый раз переводить их в **физические адреса** RAM. +* **TLB:** Это специальный маленький "кэш для адресов". Он помнит последние переводы страниц памяти. +* **TLB Miss:** Если ты прыгаешь по памяти слишком хаотично (например, в огромном графе или HashMap), TLB не находит перевод, и процессору приходится лезть в "таблицы страниц" (Page Walk), что очень дорого. +* **Почему это важно:** Локальность данных (B-Tree, массивы) спасает не только кэш данных (L1/L2), но и TLB. Меньше прыжков по страницам — быстрее работа. + +### 3. Суперскалярность и Зависимость по данным (Data Dependency) + +Ядро процессора имеет несколько исполнительных блоков (ALU). Оно может выполнить, например, 4 сложения за один такт, если они независимы. +* **Плохо:** `a = b + 1; c = a + 2;` (Второе действие ждет первого). +* **Хорошо:** `a = b + 1; c = d + 2;` (Процессор сделает это одновременно). +* **Для разработчика:** Иногда развертывание циклов (loop unrolling) или обработка данных независимыми блоками дает ускорение именно за счет загрузки всех ALU ядра. + +*** + +### ИТОГ: Ключевые элементы процессора для программиста + +Чтобы писать эффективный код (особенно структуры данных), нужно держать в голове эту "карту железа": + +| Элемент | Что это | Почему важно понимать | +| :--- | :--- | :--- | +| **Регистры** | Рабочая зона ядра (мгновенно) | Данные должны быть здесь для вычислений. Компилятор пытается держать "горячие" переменные тут. | +| **Кэш-линия** | 64 байта данных (транспорт) | Читаем память блоками. **Массивы (Vec) — короли**, связные списки — зло. B-Tree выигрывает за счет плотности данных. | +| **Кэш (L1/L2/L3)** | Быстрая память (на кристалле) | **Cache Miss** — главный враг производительности. Чем компактнее твои данные, тем больше их влезет в кэш. | +| **Branch Predictor** | Предсказатель `if`'ов | Непредсказуемые условия (рандомные `if`) сбрасывают конвейер. Иногда лучше вычислить лишнее, чем ветвиться. | +| **SIMD** | Векторные инструкции | Обработка 4-8 чисел одной командой. Rust делает это сам, если ты используешь итераторы и простые циклы. | +| **TLB** | Кэш адресов памяти | Хаотичные прыжки по памяти (Pointer Chasing) забивают не только кэш данных, но и TLB. | + +**Главный вывод для Rust-разработчика:** +Думай о памяти как о **ленте**, которую нужно читать подряд. Любой указатель (`Box`, ссылка, узел графа) — это разрыв ленты, который стоит дорого. Структуры данных, которые минимизируют эти разрывы (как B-Tree или `Vec`), всегда будут побеждать на современном железе. \ No newline at end of file diff --git a/20-dev/00-rust/rustlings/Untitled.md b/20-dev/00-rust/rustlings/Untitled.md new file mode 100644 index 0000000..abc02f7 --- /dev/null +++ b/20-dev/00-rust/rustlings/Untitled.md @@ -0,0 +1,85 @@ +В функциональном стиле, когда тебе нужно свести последовательность значений к одному числу (произведению в данном случае), используют **свертку** (fold) или, в более простых случаях, готовые методы агрегации вроде `product`. + +В Rust у итераторов есть метод `product()`, который делает ровно то, что нужно для факториала. + +### Решение 1: Самое идиоматичное (Iterators + product) + +Мы создаем диапазон от `1` до `num` (включительно) и перемножаем все числа. + +```rust +fn factorial(num: u64) -> u64 { + (1..=num).product() +} +``` + +**Почему это работает:** +1. `(1..=num)` создает итератор, генерирующий числа `1, 2, 3, ..., num`. + * Если `num` равно `0`, диапазон `1..=0` пуст. +2. `.product()` проходит по итератору, умножая аккумулятор на каждый элемент. + * Для пустого итератора (факториал 0) `product()` возвращает мультипликативную единицу (`1`), что математически верно: $0! = 1$. + +*** + +### Решение 2: Более явное (Fold) + +Если бы метода `product()` не существовало, мы бы использовали `fold`. Это классическая "reduce" операция из функционального программирования. + +```rust +fn factorial(num: u64) -> u64 { + (1..=num).fold(1, |acc, x| acc * x) +} +``` + +**Разбор:** +* `1` (первый аргумент) — начальное значение аккумулятора. +* `|acc, x| acc * x` — замыкание, которое берет текущий аккумулятор (`acc`) и следующий элемент (`x`), и возвращает новое значение аккумулятора. +* Для `num = 4`: + 1. `acc = 1` (начальное), `x = 1` -> `1 * 1 = 1` + 2. `acc = 1`, `x = 2` -> `1 * 2 = 2` + 3. `acc = 2`, `x = 3` -> `2 * 3 = 6` + 4. `acc = 6`, `x = 4` -> `6 * 4 = 24` + +**Итог:** +Используй `(1..=num).product()` — это самый "ржавый" (Rustacean) и читаемый способ. Он выполняет все твои условия: +* Нет явного `return` (это выражение). +* Нет циклов `for/while` (спрятаны внутри итератора). +* Нет мутабельных переменных. +* Нет рекурсии. + + + ### 1. "Джентльменский набор" итераторов +Помимо уже знакомых тебе `map`, `filter`, `fold` и `flat_map`, вот что ты будешь использовать в 90% случаев: + +* **`collect()`** — ты уже знаешь, это "материализатор". Превращает поток обратно в коллекцию (`Vec`, `HashMap`, `String`...). +* **`find(|x| ...)`** — ищет **первый** элемент, удовлетворяющий условию. Возвращает `Option`. +* **`any(|x| ...)` / `all(|x| ...)`** — возвращают `bool`. `any` проверяет, есть ли *хотя бы один*, `all` — что *все* соответствуют. +* **`enumerate()`** — добавляет индекс к элементу. Вместо `x` получаешь `(index, x)`. + ```rust + // Вывести строки с номерами + lines.iter().enumerate().for_each(|(i, line)| println!("{}: {}", i, line)); + ``` +* **`zip(other_iter)`** — "сшивает" два итератора в один поток пар `(a, b)`. +* **`take(n)` / `skip(n)`** — берет первые `n` или пропускает первые `n` элементов. +* **`chain(other_iter)`** — склеивает два итератора: "сначала всё из первого, потом всё из второго". +* **`inspect(|x| ...)`** — бесценно для отладки! Позволяет "подсмотреть" значение в середине цепочки, не меняя его (обычно там делают `println!`). + +### 2. Вся ли функциональщина в Rust — это итераторы? +И да, и нет. +* **Да**, итераторы — это главный локомотив функционального стиля в Rust. Это самый мощный и оптимизированный инструмент для обработки данных. +* **Нет**, ФП в Rust шире. Оно включает: + * **Enum + Pattern Matching** (алгебраические типы данных) — это основа логики, замена классам и полиморфизму. + * **Immutability** (неизменяемость по умолчанию). + * **Closures** (замыкания) — функции высшего порядка, которые можно передавать и возвращать. + +Но именно цепочки итераторов делают код "визуально функциональным". + +### 3. Каждую ли задачу можно решить функционально? +**Теоретически — да.** Любой цикл можно переписать через рекурсию или `fold`. +**Практически — не стоит.** + +Rust — прагматичный язык. Иногда **императивный код лучше**: +1. **Сложная мутация состояния**: Если тебе нужно обновлять 5 разных переменных в зависимости от сложной логики на каждом шаге, `fold` превратится в ад с кортежем из 5 элементов. Обычный `for` будет чище. +2. **Ранний выход из вложенных циклов**: Выйти из тройного вложенного цикла через `break 'label` проще, чем писать цепочку итераторов, которая умеет прерываться. +3. **Графы и произвольный доступ**: Итераторы хороши для *последовательностей*. Если ты прыгаешь по индексам массива туда-сюда (`i`, `i+5`, `i/2`), функциональный стиль будет выглядеть как натягивание совы на глобус. + +**Совет:** Используй итераторы для *трансформации потоков данных* (фильтрация, маппинг, поиск, агрегация). Используй циклы для *сложного управления потоком выполнения* или когда состояние слишком запутанное. В Rust нормально сочетать оба подхода. \ No newline at end of file diff --git a/20-dev/00-rust/smart pointers/Rc.md b/20-dev/00-rust/smart pointers/Rc.md new file mode 100644 index 0000000..102470d --- /dev/null +++ b/20-dev/00-rust/smart pointers/Rc.md @@ -0,0 +1,159 @@ +`Rc` (Reference Counting) используется для ситуаций **разделяемого владения** (shared ownership), когда данные должны жить до тех пор, пока жив хотя бы один их "владелец". + +### Зачем это нужно? +Основная причина: **неизвестность времени жизни в compile-time**. +В обычной модели владения Rust (`Box`) у данных может быть только *один* владелец. Но в реальных структурах данных (графы, деревья с обратными ссылками, UI-компоненты) часто бывает, что "родителей" много, и мы не знаем заранее, кто из них удалится последним. + +**Пример:** +Узел графа. На него ссылаются 5 других узлов. Если владелец только один, то удаление этого "главного" узла сломает все остальные 4 ссылки (сделает их невалидными). `Rc` решает это: узел жив, пока на него есть хотя бы одна ссылка. + +### Основные характеристики +1. **Shared Ownership:** Позволяет нескольким частям программы владеть одними данными. +2. **Immutable:** `Rc` позволяет получить только **иммутабельную** (неизменяемую) ссылку `&T` на данные.[2] + * *Почему?* Если бы `Rc` давал `&mut T`, это нарушило бы правила заимствования (множество мутабельных ссылок на одни данные = data race). + * *Как изменять?* Для изменяемости внутри `Rc` используется паттерн **Interior Mutability** (обычно в связке `Rc>`). +3. **Single-threaded:** `Rc` не потокобезопасен. Счетчик ссылок обновляется обычными арифметическими операциями (быстро), а не атомарными. Для многопоточности есть `Arc` (Atomic Reference Counting).[6][2] + +### Как это работает (под капотом) +`Rc::new(v)` аллоцирует в куче структуру, содержащую: +* Само значение `v`. +* `strong_count`: счетчик "сильных" ссылок (владельцев). +* `weak_count`: счетчик "слабых" ссылок (для предотвращения циклов). + +Каждый `Rc::clone(&rc)` не копирует данные, а просто инкрементирует счетчик `strong_count`. +Когда `Rc` выходит из области видимости (`drop`), счетчик декрементируется. +Когда `strong_count == 0`, данные удаляются из памяти (`free`). + +### Итог +Используйте `Rc`, когда данные нужны в нескольких местах, и вы не можете построить иерархию, где один владелец живет дольше всех остальных. Но помните, что `Rc` дает только чтение. Для записи нужна обертка `RefCell`. + +[1](https://labex.io/ru/tutorials/rc-t-the-reference-counted-smart-pointer-100434) +[2](https://doc.rust-lang.org/book/ch15-04-rc.html) +[3](https://www.reddit.com/r/rust/comments/1n65om2/is_stdrcrc_identical_to_references_without/) +[4](https://habr.com/ru/companies/bitrix/articles/878912/comments/) +[5](https://rust-book.cs.brown.edu/ch15-04-rc.html) +[6](https://doc.rust-lang.org/std/rc/struct.Rc.html) +[7](https://my-js.org/docs/guide/rust) +[8](https://notes.kodekloud.com/docs/Rust-Programming/Advanced-Rust-Concepts/Rc-Reference-Counting-and-Shared-Ownership) +[9](https://labex.io/ru/tutorials/exploring-rust-s-reference-counting-mechanism-99263) + + + +Реализация B-дерева на `Rc` — это классический пример того, где приходится использовать паттерн **Interior Mutability** (внутренняя изменяемость). + +Почему? Потому что `Rc` дает нам **неизменяемую** ссылку на данные. А в B-дереве нам нужно постоянно менять содержимое узлов (добавлять ключи, перекидывать детей). Чтобы "обойти" неизменяемость `Rc`, мы кладем данные внутрь `RefCell`. + +Получается "бутерброд": `Rc>`. + +Вот упрощенная реализация структуры и поиска (полная реализация со сплитами и балансировкой заняла бы сотни строк, поэтому я покажу суть связывания через `Rc`): + +```rust +use std::rc::Rc; +use std::cell::RefCell; +use std::fmt::Debug; + +// 1. Опишем узел. +// B-дерево состоит из ключей и детей. +#[derive(Debug)] +struct Node { + keys: Vec, + // Дети хранятся через Rc, чтобы мы могли иметь несколько ссылок на узел + // (например, одна ссылка у родителя, другая у курсора-итератора). + // RefCell нужен, чтобы мы могли менять children/keys внутри Rc. + children: Vec>>>, +} + +impl Node { + fn new(keys: Vec) -> Self { + Node { + keys, + children: Vec::new(), + } + } +} + +// 2. Создадим удобный алиас типа для "умной ссылки на узел" +type NodeLink = Rc>>; + +// Вспомогательная функция для создания завернутого узла +fn make_node(keys: Vec) -> NodeLink { + Rc::new(RefCell::new(Node::new(keys))) +} + +fn main() { + // --- СТРОИМ ДЕРЕВО (Ручная сборка для примера) --- + + // Создаем листья + let leaf1 = make_node(vec![1, 2]); + let leaf2 = make_node(vec![4, 5]); + let leaf3 = make_node(vec![7, 8]); + + // Создаем корень + let root = make_node(vec![3, 6]); + + // Связываем корень с детьми. + // Нам нужно "залезть" внутрь Rc через borrow_mut(), чтобы изменить вектор детей. + { + let mut root_inner = root.borrow_mut(); + + // Rc::clone здесь просто увеличивает счетчик ссылок. + // leaf1 теперь принадлежит и переменной leaf1, и вектору внутри root. + root_inner.children.push(Rc::clone(&leaf1)); + root_inner.children.push(Rc::clone(&leaf2)); + root_inner.children.push(Rc::clone(&leaf3)); + } + // Тут borrow_mut заканчивается, блокировка снимается. + + // --- ИСПОЛЬЗОВАНИЕ --- + + println!("Root strong count: {}", Rc::strong_count(&root)); // 1 + println!("Leaf1 strong count: {}", Rc::strong_count(&leaf1)); // 2 (одна у нас в main, одна внутри root) + + // Поиск значения 5 + if search(&root, 5) { + println!("Found 5!"); + } else { + println!("Not found."); + } +} + +// Пример рекурсивного поиска +fn search(node_link: &NodeLink, target: i32) -> bool { + // Берем ссылку на чтение через borrow() + let node = node_link.borrow(); + + // Проверяем ключи в текущем узле + for (i, &key) in node.keys.iter().enumerate() { + if key == target { + return true; // Нашли + } + if key > target { + // Если ключ больше искомого, нужно спускаться в ребенка (если он есть) + if node.children.len() > i { + return search(&node.children[i], target); + } else { + return false; + } + } + } + + // Если дошли до конца ключей, проверяем последнего ребенка + if !node.children.is_empty() { + return search(node.children.last().unwrap(), target); + } + + false +} +``` + +### Почему именно такая структура? + +1. **`Rc` вместо `Box`**: В строгом дереве обычно хватает `Box` (у каждого узла строго 1 родитель). Однако `Rc` часто используют, если: + * Мы хотим реализовать курсоры, которые указывают на узлы дерева независимо от корня. + * Мы хотим реализовать персистентную структуру данных (где новые версии дерева переиспользуют неизмененные поддеревья старых версий). + +2. **`RefCell`**: Без него мы не смогли бы написать `root.children.push(...)`, так как `Rc` запрещает изменение содержимого. `RefCell` переносит проверку правил заимствования с этапа компиляции на этап выполнения (runtime). + +### В чем подвох `Rc` в деревьях? +Если вы добавите ссылку "назад" (от ребенка к родителю) с помощью обычного `Rc`, вы создадите **Reference Cycle** (циклическую ссылку). Счетчики ссылок никогда не станут равны нулю, и память потечет (никогда не очистится). +Для ссылок "назад" к родителю нужно использовать `Weak` (слабые ссылки), которые не увеличивают *strong_count*. \ No newline at end of file diff --git a/20-dev/00-rust/smart pointers/deref.md b/20-dev/00-rust/smart pointers/deref.md new file mode 100644 index 0000000..442f58c --- /dev/null +++ b/20-dev/00-rust/smart pointers/deref.md @@ -0,0 +1,67 @@ +## 1. Суть трейта `Deref` + +Трейт `std::ops::Deref` позволяет кастомизировать поведение оператора разыменования `*`. +Главная цель — сделать **Smart Pointers** (умные указатели) взаимозаменяемыми с обычными ссылками в клиентском коде. + +**Механизм работы:** +Когда вы пишете `*y` для типа, реализующего `Deref`, Rust неявно преобразует это в: + +rust + +`*(y.deref())` + +1. Вызывается метод `deref()`, который возвращает **ссылку** на внутреннее значение (`&Target`). + +2. Затем происходит обычное разыменование `*` этой полученной ссылки. + + +> **Важно:** Метод `deref` возвращает именно ссылку, а не значение, чтобы не перемещать (move) владение данными из умного указателя. + +## 2. Реализация + +Для реализации собственного умного указателя (как в примере `MyBox`) нужно: + +- Указать ассоциированный тип `Target` (тип данных внутри обертки). + +- Реализовать метод `deref`. + + +rust + +`use std::ops::Deref; impl Deref for MyBox { type Target = T; fn deref(&self) -> &Self::Target { &self.0 // Возвращаем ссылку на поле кортежной структуры } }` + +## 3. Deref Coercion (Неявное приведение) + +Это механизм эргономики Rust, который автоматически преобразует ссылки при передаче аргументов в функции или методы. + +- **Как работает:** Если тип `T` реализует `Deref`, то `&T` может быть неявно приведен к `&U`. + +- **Цепочки:** Coercion работает рекурсивно. + + - _Пример:_ `&MyBox` → `deref` → `&String` → `deref` → `&str`. + + - Это позволяет передавать `&MyBox` в функцию, ожидающую `&str`. + +- **Цена:** Работает на этапе компиляции (compile-time resolution), **runtime оверхеда нет**. + + +Без этого механизма код превратился бы в нагромождение символов: `&(*m)[..]` вместо простого `&m`. + +## 4. Взаимодействие с изменяемостью (Mutability) + +Для изменяемых ссылок существует зеркальный трейт `DerefMut`. + +**Три правила приведения (Coercion rules):** +Rust применяет приведение типов в следующих случаях: + +1. **`&T` → `&U`**: Если реализован `Deref` (Immutable to Immutable). + +2. **`&mut T` → `&mut U`**: Если реализован `DerefMut` (Mutable to Mutable). + +3. **`&mut T` → `&U`**: Если реализован `Deref` (Mutable to Immutable). + + +> **Критическое ограничение:** Приведение **Immutable (`&T`) → Mutable (`&mut U`)** невозможно. +> Это нарушило бы правила заимствования (Borrowing Rules), так как нельзя гарантировать уникальность создаваемой изменяемой ссылки, если исходная ссылка была неизменяемой (а их может быть много). + +4. [https://doc.rust-lang.org/book/ch15-02-deref.html](https://doc.rust-lang.org/book/ch15-02-deref.html) \ No newline at end of file diff --git a/20-dev/00-rust/smart pointers/drop.md b/20-dev/00-rust/smart pointers/drop.md new file mode 100644 index 0000000..3298cb5 --- /dev/null +++ b/20-dev/00-rust/smart pointers/drop.md @@ -0,0 +1,60 @@ +Вот краткая выжимка о трейте `Drop` на основе предоставленного материала. + +### 1. Суть трейта `Drop` +Трейт `Drop` используется для настройки кода, который **автоматически** выполняется, когда значение выходит из области видимости (scope). +* **Основная цель:** Очистка ресурсов (освобождение памяти в куче, закрытие файлов, сетевых соединений, снятие блокировок). +* Это аналог **деструктора** в ООП языках. +* В Rust почти все умные указатели (например, `Box`) используют `Drop` для корректного освобождения памяти. + +### 2. Как реализовать +Нужно реализовать единственный метод `drop`, который принимает мутабельную ссылку на `self`. + +```rust +struct CustomSmartPointer { + data: String, +} + +impl Drop for CustomSmartPointer { + fn drop(&mut self) { + // Ваш код очистки. + // В примере: просто выводим сообщение. + println!("Dropping CustomSmartPointer with data `{}`!", self.data); + } +} +``` + +* **Автоматический вызов:** Вам **не нужно** вызывать этот метод вручную. Rust сам вставит вызов в нужном месте (обычно в конце блока `{}`). +* **Порядок:** Переменные удаляются в порядке, **обратном** их созданию (LIFO - Last In, First Out). + +### 3. Ручная очистка (Early Drop) +Иногда нужно освободить ресурс *раньше*, чем закончится область видимости (например, чтобы раньше снять блокировку `Mutex` или закрыть файл). + +* **Проблема:** Rust **запрещает** вызывать метод `.drop()` вручную (ошибка компиляции `explicit destructor calls not allowed`), чтобы избежать двойной очистки (double free error). +* **Решение:** Используйте функцию `std::mem::drop(value)`. Она принимает значение по значению (забирает владение) и тут же выбрасывает его, вызывая деструктор. + +```rust +fn main() { + let c = CustomSmartPointer { data: String::from("some data") }; + println!("Created."); + + // c.drop(); // ОШИБКА! Нельзя вызывать метод трейта напрямую. + + drop(c); // Правильно. Явный вызов функции из std::mem. + // Деструктор отработает здесь. + + println!("End of main."); +} +``` + +### Итог одной строкой +`Drop` позволяет написать код, который выполнится автоматически при уничтожении объекта, а если нужно уничтожить объект досрочно — используйте функцию `std::mem::drop`. + +[1](https://doc.rust-lang.ru/book/ch15-03-drop.html) +[2](https://doc.rust-lang.ru/stable/rust-by-example/trait/drop.html) +[3](https://labex.io/ru/tutorials/cleanup-with-rust-s-drop-trait-100433) +[4](https://habr.com/ru/articles/960608/) +[5](https://doc.rust-lang.ru/stable/rust-by-example/generics/gen_trait.html) +[6](https://www.reddit.com/r/rust/comments/uhz9mr/implementing_drop_manually_to_show_progress/) +[7](https://habr.com/ru/articles/277461/) +[8](https://www.youtube.com/watch?v=7ec7hpndex4) +[9](https://doc.rust-lang.org/std/ops/trait.Drop.html) \ No newline at end of file