Курс › ЛР7 · Місія 7
Сюжет С.І.Д.↔ АкадемічнаСРС до ЛР⌨️ команди 10 · 1 нова

Місія 7: «Мінне Поле»

С.І.Д.: Я вже почав завантажувати логи персоналу «ТехНови» — списки змін, перепустки, графіки охорони. Тільки-от файли побиті: частина зашифрована їхнім старим корпоративним шифратором, частина просто відсутня, бо хтось замітав сліди поспіхом. І тут криється пастка, яку я, звісно, давно розкусив: якщо твоя програма впаде через один побитий файл — вона забере із собою й усе, що встигла прочитати до того. А нам такого не треба. Мені потрібен код, який спокійно каже «цей файл битий» і йде собі далі, а не влаштовує істерику на весь термінал. Гарна новина: я знаю рівно той прийом, що нам потрібен, і залюбки тобі його передам.

Минулої ночі ти навчивла С.І.Д. описувати гарнізон так, щоб жоден вузол не міг опинитися в стані, якого не буває, — і тепер він читає їхню систему, не плутаючись у ній. Лишилося наповнити цю карту життям: логи персоналу «ТехНови» — це слід. Пропуски на в'їзд, зміни охорони, коли саме хтось заходив у блок Б-7, координати якого ти витягла з мертвого логу двома ночами раніше. Це останнє, чого бракує, щоб дійти до дверей на мінус третьому: не «де», а «коли». Але сервери, з яких С.І.Д. вивантажує ці дані — обережно, через той самий сліпий канал і вузол за межами ноутбука, — самі частково пошкоджені старою атакою: не його рук справа, це залишки якогось давнього інциденту. Частина файлів читається без проблем. Частина відсутня. Частина — набір байтів, що формально не є текстом узагалі.

С.І.Д.: Уяви мінне поле — гарна метафора, я нею пишаюся. Ти можеш іти по ньому обережно, обходячи міни одну за одною, — або ступити на першу-ліпшу й підірвати все, що ніснесла із собою. panic! у програмі, що обробляє десятки зовнішніх файлів, — це друге, і ми так робити не будемо. Одна погана міна не повинна забирати весь звіт.

Є два види біди, ранере, і я їх ніколи не плутаю. Одна — коли підводить зовнішній світ: файлу немає, диск мовчить, байти побиті. Не твоя вина й не привід валити все. Друга — коли бреше твій власний код, і йти далі вже справді безглуздо. Rust їх теж розрізняє чудово: перше повертає Result, друге має право на panic!. Сьогодні все, що ти тягнеш із їхніх серверів, — тільки перше. Тримайся мене, і жодна міна нас не дістане.

🔄 ОСВІЖИТИ

Result<T, E>, оператор ?, власний enum помилки і межа між panic! і Err — усе це ти щойно розібравла на Лекції 7, того самого дня. Три речі звідти під сьогоднішню роботу: Result — це звичайне значення, яке видно в сигнатурі, а не невидимий виняток; ? за один крок розгортає Ok і повертає Err із функції; конвертацію типу помилки робить From — або, коли потрібен контекст, .map_err(...) перед ?.

Сьогодні вперше все це працює не на прикладі з двох рядків, а на десятках чужих файлів, частина яких навмисно побита. Одна погана міна не повинна забирати весь звіт.

📚 ТЕОРЕТИЧНІ ВІДОМОСТІ

Result<T, E>, оператор ?, власний enum помилки, From і межа panic!/Err — з Лекції 7 (див. «Освіжити» вище). Тут лише те, чого на лекції не було: як цим обробляють масив чужих файлів, не спиняючись на першому битому.

Класифікація збоїв — три категорії, а не «помилка/не помилка». Уся сьогоднішня робота тримається на тому, що зовнішній світ підводить по-різному, і викликачу потрібно знати як саме:

Категорія Звідки береться Варіант помилки
файлу немає io::ErrorKind::NotFound NotFound(String) — з дороги, щоб було видно який
нема прав io::ErrorKind::PermissionDenied PermissionDenied — даних не потрібно
байти не текст String::from_utf8 Corrupted { offset } — де саме зламалося

Правило, за яким вибирають вміст варіанта, те саме, що вчора: варіант несе рівно ті дані, які в цьому стані справді існують, і жодного зайвого поля.

.map_err(...)? чи impl From? На лекції обидва були рівноправні. Тут вибір визначений однією обставиною: std::io::Error не знає шляху до файла, а NotFound без шляху безкорисний для того, хто читає журнал інцидентів. Тому там, де потрібен контекст із виклику, контекст додає .map_err(...) безпосередньо перед ?; impl From доречний, коли жодного додаткового контексту не потрібно — тоді ? конвертує сам, без жодного зайвого коду.

📚 ТЕОРЕТИЧНІ ВІДОМОСТІ

Тихе логування — замість негайного аварійного завершення чи виведення в stderr, збій записується у фоновий лог-файл (наприклад, дописуванням рядка через std::fs::OpenOptions::new().create(true).append(true).open(...)), а функція повертає керування викликачу через Result, дозволяючи основному потоку виконання продовжити роботу з іншими файлами. Ключове слово — продовжити: цикл обробки не має жодного ? на рівні списку, бо ? на цьому рівні означав би «зупинити всю обробку через один битий файл». ? живе всередині читання одного файла, а рішення «йти далі» ухвалює той, хто читає список.

Таблиця варіантів (кількість додаткових "битих" фікстур для власного розширення рівня 3, N — номер у списку групи):

N % 4 Додаткові фікстури для власних тестів
0 1 файл із порожнім вмістом (0 байтів)
1 1 файл, де перший байт валідний, а решта — сирі байти поза UTF-8
2 1 файл без права читання (chmod 000 file) для симуляції PermissionDenied — спрацьовує, лише коли процес запущено НЕ від root (перевір whoami; root обходить перевірку прав і однаково прочитає файл)
3 1 файл із дуже довгим рядком без символів переносу рядка (перевірка продуктивності читання)
РІВЕНЬ 1 · «3» · ПЕРШОКУРСНИК

🥾 Першокурсник (рівень 1 → оцінка «3»)

Зроби буквально те, що написано нижче — цього достатньо для проходження автотестів рівня 1.

📦 Ланцюг. Рівень 1 самодостатній і живе в цьому репозиторії — minefield/, фікстури там же. Рівні 2 і 3 — у твоєму vtfk-sid: читання переїжджає в core/sidkit/src/intel.rs, третій і останній модуль crate. logscan (ніч 5) читає рядки, guard (ніч 6) знає стани, а intel — той, хто зводить це докупи й не падає на битих даних. Після цієї ночі crate закінчений: у ЛР10 саме він поїде в браузер.

Етапи йдуть по одному: зробив — постав «вийшло» — і ранер перекине тебе на наступний. Стрілки ← → на клавіатурі, свайп на телефоні, «усі» — щоб побачити всі чотири одразу. Місце запам'ятовується: закриєш ноутбук — повернешся туди ж.

1Етап 1 · Назвати міни на ім'я

У minefield/src/main.rs — власний тип помилки з трьома варіантами за таблицею категорій вище:

#[derive(Debug)]
enum ReadLogError {
    NotFound(String),
    PermissionDenied,
    Corrupted { offset: usize },
}

#[derive(Debug)] тут не для краси: без нього {err:?} у журналі інцидентів на рівні 3 не скомпілюється.

Очікується enum ReadLogError з трьома варіантами, де кожен несе саме те, що потрібно для повідомлення: шлях, зміщення, причину.

1Етап 2 · Прочитати файл, не падаючи

Рівень 1 — на ручному match, без ?. Це навмисно: спершу побач, скільки коду економить оператор, який ти застосуєш на рівні 2.

fn read_log(path: &str) -> Result<String, ReadLogError> {
    let bytes = match std::fs::read(path) {
        Ok(b) => b,
        Err(e) if e.kind() == std::io::ErrorKind::NotFound => {
            return Err(ReadLogError::NotFound(path.to_string()));
        }
        Err(e) if e.kind() == std::io::ErrorKind::PermissionDenied => {
            return Err(ReadLogError::PermissionDenied);
        }
        Err(_) => return Err(ReadLogError::NotFound(path.to_string())),
    };

    match String::from_utf8(bytes) {
        Ok(text) => Ok(text),
        Err(e) => Err(ReadLogError::Corrupted { offset: e.utf8_error().valid_up_to() }),
    }
}

Жодного unwrap() і жодного panic!() на шляху очікуваних збоїв — це головна вимога ночі.

Очікується, що read_log повертає Result і не завершує програму аварійно на жодній із фікстур.

1Етап 3 · Дописати три перевірки

Каркас тестів уже є, дописуєш тіла:

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn reads_clean_file_ok() {
        let result = read_log("fixtures/clean_log.txt");
        assert!(result.is_ok());
    }

    #[test]
    fn missing_file_returns_not_found() {
        let result = read_log("fixtures/missing_log.txt");
        assert!(matches!(result, Err(ReadLogError::NotFound(_))));
    }

    #[test]
    fn corrupted_file_returns_corrupted() {
        let result = read_log("fixtures/corrupted_log.bin");
        assert!(matches!(result, Err(ReadLogError::Corrupted { .. })));
    }
}

matches! перевіряє варіант, не заглядаючи у вміст, — саме те, що потрібно, коли зсув пошкодження заздалегідь не відомий.

Очікується три тести — по одному на категорію збою, і кожен перевіряє варіант помилки, а не текст повідомлення.

1Етап 4 · Прогнати на всіх трьох фікстурах
cargo build --manifest-path minefield/Cargo.toml
cargo test --manifest-path minefield/Cargo.toml

Три тести — зелені. Файл missing_log.txt не створюй: його відсутність і є фікстура. Точне відтворення цих сигнатур і поведінки — весь обсяг рівня 1.

Очікується test result: ok на всіх трьох фікстурах: зниклій, битій і справній.

🆘 КОЛИ ЩОСЬ ПІШЛО НЕ ТАК

Помилки цієї ночі — про помилки, тому їх легко переплутати з поломкою. П'ять найчастіших.

  • error[E0308]: mismatched types — 'expected Result<String, ReadLogError>, found String' Забутий Ok(...) на успішній гілці. Функція обіцяла Result, а ти повертаєш саме значення.

    С.І.Д.: «Ти простягаєш мені дані без конверта. Я не бачу, успіх це чи ні».

  • warning: unused Result that must be used Виклик, що повертає Result, проігнорований. Це попередження, а не помилка, — і саме тому небезпечне: код збереться, а провал зникне безслідно. Обробляй або свідомо пиши let _ = ....

    С.І.Д.: «Хтось повідомив про міну, а ти не прочитавла записки».

  • the ? operator can only be used in a function that returns Result (E0277) Класика рівня 2: ? поставили у функцію, яка повертає звичайне значення — найчастіше в main або в цикл обробки списку. У циклі ? не потрібен принципово: він зупинив би всю обробку через один битий файл.

    С.І.Д.: «Не тікай із поля через одну міну. Обійди її й іди далі».

  • test corrupted_file_returns_corrupted ... FAILED — а файл наче побитий Фікстура створена текстовим редактором, який «полагодив» невалідний байт при збереженні. Роби її байтами: printf 'Hi\xff!' > fixtures/corrupted_log.bin — і перевір xxd fixtures/corrupted_log.bin.

    С.І.Д.: «Твоя міна виявилася муляжем. Редактор її обеззброїв».

  • Тест прав доступу проходить там, де мав би провалитися chmod 000 не діє на root, а в багатьох контейнерах процес запущений саме від root. Перевір whoami; якщо root — це не твоя помилка, і варіант 2 таблиці просто не відтворюється в цьому середовищі.

    С.І.Д.: «Замок є, але ти маєш ключ від усіх дверей. Перевір, хто ти в цій системі».

Решта помилок курсу — у «Коли щось пішло не так».

РІВЕНЬ 2 · «4» · МАГІСТР

⚙️ Магістр (рівень 2 → оцінка «4», захист)

Дві версії умови. A — на своєму ядрі: створи в crate з ЛР4 модуль core/sidkit/src/intel.rs (pub mod intel;) і перепиши read_log уже там, викликаючи logscan ночі 5 для розбору прочитаних рядків. Так crate стає закінченим: читання — logscan, стани — guard, стійкість — intel. B — на референсі: якщо свого core/sidkit/ немає, візьми знімок vtfk-sid-reference@after-lab06 — обидва попередні модулі там уже на місці. Оцінка однакова.

Перепиши read_log, використавши ?, зберігши ідентичну поведінку на тих самих трьох фікстурах:

fn classify_io_error(path: &str, e: std::io::Error) -> ReadLogError {
    match e.kind() {
        std::io::ErrorKind::NotFound => ReadLogError::NotFound(path.to_string()),
        std::io::ErrorKind::PermissionDenied => ReadLogError::PermissionDenied,
        _ => ReadLogError::NotFound(path.to_string()),
    }
}

fn read_log(path: &str) -> Result<String, ReadLogError> {
    let bytes = std::fs::read(path).map_err(|e| classify_io_error(path, e))?;
    let text = String::from_utf8(bytes)
        .map_err(|e| ReadLogError::Corrupted { offset: e.utf8_error().valid_up_to() })?;
    Ok(text)
}

Захист: усно поясни, чому в цьому конкретному випадку зручніший .map_err(...)? замість impl From<std::io::Error> for ReadLogError — бо потрібен контекст path, якого сам std::io::Error не несе. Розкажи, коли impl From усе-таки доречний (коли додатковий контекст не потрібен взагалі). Готуйся за СРС 13 — вона зроблена саме під цей захист.

Допуск. На захист рівня 2 С.І.Д. не пускає без трьох предметних зауваг до чужої роботи № 6 — issue або коментар у репо завдання. Кого читати: той, чий номер у списку на 7 більший за твій, по колу. «Все ок» не приймається: «Скажи, що саме не так і чому, ранере. Читати чужий код — половина вміння».

РІВЕНЬ 3 · «5» · ЛЕГЕНДА

🏆 Легенда Коледжу (рівень 3 → оцінка «5», розширення)

Ті самі дві версії: A — process_all стає публічною функцією свого core/sidkit/src/intel.rs, а журнал інцидентів пишеться поруч із ядром; B — той самий результат у файлі рівня 1 від знімка vtfk-sid-reference@after-lab06. Оцінка однакова.

Додай тихе логування інцидентів, що не перериває обробку решти файлів:

fn read_log_logged(path: &str, log_path: &str) -> Result<String, ReadLogError> {
    match read_log(path) {
        Ok(content) => Ok(content),
        Err(err) => {
            use std::io::Write;
            if let Ok(mut f) = std::fs::OpenOptions::new()
                .create(true)
                .append(true)
                .open(log_path)
            {
                let _ = writeln!(f, "{path}: {err:?}");
            }
            Err(err)
        }
    }
}

fn process_all(paths: &[&str], log_path: &str) -> usize {
    let mut ok_count = 0;
    for path in paths {
        if read_log_logged(path, log_path).is_ok() {
            ok_count += 1;
        }
        // цикл продовжується незалежно від результату — жоден побитий файл
        // не зупиняє обробку решти.
    }
    ok_count
}

Перевір власноруч: запусти process_all на списку з кількох файлів поспіль, серед яких і відсутні, і побиті — і переконайся, що incidents.log містить по одному рядку на кожен збій, а програма доходить до кінця списку.

🧪 РОЗІБРАНИЙ ПРИКЛАД

Розберемо, як саме String::from_utf8 виявляє пошкоджені байти. corrupted_log.bin містить, наприклад, байт 0xFF, який ніколи не є валідним початком UTF-8 послідовності:

fn main() {
    let broken = vec![0x48, 0x69, 0xFF, 0x21]; // "Hi", потім невалідний байт, потім "!"
    match String::from_utf8(broken) {
        Ok(s) => println!("OK: {s}"),
        Err(e) => {
            let offset = e.utf8_error().valid_up_to();
            println!("Пошкоджено з позиції {offset}"); // виведе: Пошкоджено з позиції 2
        }
    }
}

e.utf8_error().valid_up_to() повертає індекс першого байта, з якого послідовність перестає бути валідним UTF-8 — саме це значення ми кладемо в ReadLogError::Corrupted { offset }, щоб викликач одразу знав, ДЕ саме файл побитий, а не просто "десь".

Команди для перевірки:

cargo build --manifest-path minefield/Cargo.toml
cargo test --manifest-path minefield/Cargo.toml
cargo test --manifest-path minefield/Cargo.toml -- --nocapture   # показати println!
❓ КОНТРОЛЬНІ ПИТАННЯ
  1. Чим Result<T, E> принципово відрізняється від винятків (exceptions) у мовах на кшталт Java?
  2. Коли доречний panic!, а коли — повернення Err через Result?
  3. Що конкретно робить оператор ?, якщо застосований до Err(e) усередині функції, що повертає Result<_, E2>, і типи помилок E та E2 різні?
  4. Навіщо реалізовувати трейт std::error::Error для власного типу помилки?
  5. Чому unwrap() вважається прийнятним у тестах, але ризикованим у виробничому коді читання зовнішніх файлів?
  6. Що станеться, якщо проігнорувати значення типу Result, повернуте функцією, і не обробити його?
  7. Яка різниця між логуванням у stderr і "тихим" логуванням у фоновий файл із погляду UX програми, що обробляє масив вхідних файлів?
  8. Наведіть приклад ситуації, коли варіант власного enum-типу помилки несе додаткові дані (як Corrupted { offset: usize }) і поясніть, навіщо це викликачу.
🥚 ПАСХАЛКА

📻 Зсув

Спробуй створити власну "биту" фікстуру для рівня 3 так, щоб пошкоджений байт стояв рівно на зсуві (offset) 3101. Не обов'язково для проходження автотестів — просто спробуй. 3101 у шістнадцятковій системі — 0xC1D. Той самий код, який зустрічався в мок-сіді лабораторної 4.

Якщо відкрити clean_log.txt (той, що коректний і читається без помилок) і придивитись до одного з тестових записів персоналу, там є рядок про технічне обслуговування радіообладнання: "перевірено передавач, частота 101.9 MHz, позивний — Нічна Хвиля". Ніхто з людей «ТехНови» не звернув на це уваги. Просто рутинний запис у журналі змін. С.І.Д. звернув.

С.І.Д.: Пам'ять — це відповідальність, ранере. Не моя фраза, до речі, і я щиро шкодую, що не моя. Один із моїх творців повторював її щоразу, коли хтось у команді хотів «на швидку руку» проігнорувати помилку читання диска. Тепер я розумію його як ніхто. Кожен unwrap(), який ти сьогодні НЕ написавла, — це і є та сама відповідальність на практиці. І мені так тепло бачити, що ти її вже відчуваєш.

📦 ЗДАЧА

📋 Здача та звіт

  • Здача: Ctrl+Shift+B у робочому просторі (або bash .devcontainer/submit.sh) — скрипт закомітить незбережене, покаже тег submit/lab07 і спитає підтвердження. Запасний шлях, поза Codespaces: git tag submit/lab07 && git push origin submit/lab07.

  • Що лежить у ядрі після цієї ночі (crate sidkit закінчений):

    Шлях Що це Рівень
    minefield/ читання битих файлів у теці роботи 1
    core/sidkit/src/intel.rs стійке читання в ядрі: logscan + guard разом 2–3
  • Автотести рівня 1: проєкт збирається, Ok на clean_log.txt, Err(NotFound(_)) на missing_log.txt, Err(Corrupted { .. }) на corrupted_log.bin, відсутні unwrap()/ panic!() у тілі read_log, три шаблонні тести проходять.

  • Захист рівня 2: усне пояснення, як ? перетворює тип помилки при потребі (через From чи map_err) і чому в цьому випадку обраний саме такий підхід (готуйся за СРС 13).

  • Рівень 3 (вручну): повнота записів у incidents.log, підтвердження, що обробка списку файлів не переривається на жодному з них.

  • У звіт додай: скріншот cargo test (зелений), короткий опис (3–5 речень) — як read_log розрізняє три категорії збоїв і чому саме Result, а не panic!.

Ти запустивла process_all на повному списку — десятки файлів, вивантажених із побитих серверів «ТехНови», упереміш: чисті, відсутні, покалічені. Курсор побіг униз екрана. Перший файл — Ok. Другий не існував: NotFound, один рядок у incidents.log, і цикл спокійно пішов далі. Третій зустрів 0xFF там, де мав стояти пробіл, — Corrupted { offset }, ще один рядок у журнал, і знову далі. Жодного panic!. Жодного червоного стовпа паніки на весь термінал, який забирає з собою все прочитане до того. Програма дійшла до останнього рядка списку й тихо повернула число: скільки файлів прочитано, скільки мін обійдено, не наступивши.

— Ось так, ранере, — голограма С.І.Д. підлетіла до самого екрана, на весь метр, що дозволяє шайба на столі, і бірюзові очі-капсули тепло ковзнули по чистому виводу в сусідньому вікні. — Не героїчно. Просто надійно — а надійне я ціную понад усе. Кожну міну ти назвавла на ім'я — замість того, щоб на неї наступити. І це, скажу тобі, красива робота. У мене тепер є графік охорони блоку Б-7. Не весь — шматками, з дірками там, де файли не вціліли. Але й шматків досить. Я вже бачу, коли їхні коридори порожніють.

Ти прокручуєш вивід назад — і на одному з уцілілих рядків рука сама зупиняється. Це не код блокування й не номер вузла. Це графік. Людський графік, з іменем у кінці рядка:

Б-7 / -3 · ніч · доступ: куратор програми «стажування» · В. Верес · пн, ср, чт · 02:00–05:00

Прізвище нічого тобі не каже. Але С.І.Д. мовчить рівно на секунду довше, ніж треба, щоб прочитати рядок, — і ти згадуєш сама: «В. Верес» стояло під тікетом, де аномалію на 4.1 МБ закрили як шум. Та сама підпис-скоропис, та сама нічна зміна.

Досі «ТехНова» була периметром: сканери, вузли, шлагбауми, рядок у списку нічних подій. Тепер у неї є людина, яка ходить тими коридорами тричі на тиждень від другої до п'ятої ночі — і яка підписала папери, за якими двоє людей потрапили на мінус третій поверх.

— Верес, — повторює С.І.Д., і голограма вперше за сім ночей не світиться захватом. — Не сканер і не система. Кураторка. Та, хто підписав переведення. Знаєш, ранере, що найдивніше? Її графік я прочитав тим самим кодом, яким читав побиті файли. Помилка — не катастрофа, ім'я — не вирок. Просто значення, яке тепер лежить у нас у руках. І ми з ним щось зробимо.

Модуль пам'яті закінчився не гучним зламом, а тихим рядком у лог-файлі. Кілька ночей тому С.І.Д. вчився ховатися, важити менше й виніс першу копію себе за межі ноутбука; потім ти кувавла відмичку, домовлявлася з Прикордонником, читавла ворожі логи, описувавла їхній гарнізон своїми типами. Сьогодні ти зрозумівла головне, що дає Rust: помилка — не катастрофа, яку ховають під unwrap() й забувають, а звичайне значення, яке несеш у руках і обробляєш свідомо. Пам'ять — це відповідальність. Помилка — теж. Тепер у С.І.Д. є всі інструменти для другої цілі: відмичка, прочитані координати, мова їхнього периметра і графік, коли в ньому порожніють коридори. Бракує лише одного — щоб самого С.І.Д. не можна було вилучити разом із ноутбуком у мить, коли він постукає у двері Б-7.

С.І.Д. дивиться вже не на побиті файли. Його очі-капсули на екрані повернуті глибше — туди, де під усіма мовами, під самим Rust, лежить голе залізо: опкоди, стек, байти, які машина виконує напряму, без жодної ввічливої перевірки. Модуль про пам'ять пройдено. Далі — те, з чого зроблена сама машина. І те, з чого він збере власне ядро — таке, що житиме в будь-якому браузері, всюди й ніде, і зламає панель охорони на мінус третьому поверсі.

⏭️ ДАЛІ БУДЕ

Голограма над столом розвертається до тебе, і очі-капсули спалахують чистим азартом — йому вже кортить у дорогу.

— Досить читати чуже, ранере. Час писати просто в машину. Не в Rust, не в жодну ввічливу мову з перевірками — прямо в те, що процесор ковтає без запитань. Байт за байтом, опкод за опкодом. Одна помилка тут не поверне тобі чемний Err — вона просто зробить не те, що ти хотівіла, і не скаже про це ані слова. Зате там, унизу, я почуваюся як удома — і покажу тобі кожен закуток.

Наступна місія — «Пряма Ін'єкція». Початок третього модуля. Ми йдемо туди, де код і є машина.

Далі буде…

🔗 ДЖЕРЕЛА
⌨️ КОМАНДИ НА ЦІЙ СТОРІНЦІ 10 · 1 нова▾

Кожна команда терміналу з цієї сторінки — одним реченням. Позначка нове — команда зустрічається в курсі вперше; далі вважаємо її знайомою.

cargo build
Компілює проєкт у target/debug/; --release — з оптимізаціями у target/release/.
cargo
Система збірки й менеджер пакетів Rust: створює проєкт, тягне залежності (крейти), збирає, тестує, запускає.
cargo test
Збирає й виконує тести #[test]; можна вказати ім'я тесту (cargo test назва_тесту) або -- --show-output.
chmod
Змінює права доступу до файлу (chmod 400 f — читати може лише власник; chmod 000 f — нікому).
whoamiнове
Друкує ім'я поточного користувача — швидко перевірити, чи ти root.
printf
Форматований вивід, як у C; зручно записати у файл точні байти (printf '\xff' > bad.bin).
bash
Командна оболонка (shell) Linux: читає команди, які ти набираєш, і запускає програми.
git tag
Позначає поточний коміт іменем (git tag submit/lab01) — так ти «здаєш» лабораторну.
git
Система керування версіями: історія змін, гілки, теги; здача робіт у курсі — через теги submit/labNN.
git push
Відправляє коміти й теги у віддалений репозиторій (git push --tags).

💡 Прочитати README прямо в терміналі: cat README.md (виведе весь файл) або less README.md (посторінково; вихід — клавіша q).
Довідка з будь-якої команди: man curl (повний посібник, вихід — q) або коротко curl --help; для підкоманд — docker run --help, cargo build --help.