Підручник з Rust: Безпека пам'яті та паралельність

Останнє оновлення: 04/12/2025
Автор: Ісаак
  • Rust забезпечує безпеку пам'яті під час компіляції через володіння, запозичення та час життя, без використання збирання сміття.
  • Система типів та правила аліасингу дозволяють паралельну роботу без перегонів даних, використовуючи м'ютекси, канали та розумні вказівники.
  • Cargo, crates.io та активна екосистема спрощують управління залежностями, компіляцію, тестування та розгортання.
  • Розуміння структур, перерахувань, опцій та результатів є ключем до обробки помилок та моделювання безпечних даних у паралельних застосунках.

Мова програмування C проти Rust: переваги та недоліки

Rust стала однією з тих мов, які Кожен розробник систем чує це знову і знову.Він такий же швидкий, як C та C++, але з майже нав'язливим акцентом на безпеку пам'яті та добре виконаний паралельний процес. Це не просто порожній маркетинг: його дизайн обертається навколо того, що компілятор виявляє помилки під час компіляції — помилки, які в інших мовах ви бачите лише тоді, коли система вже працює... або коли вона аварійно завершує роботу.

Якщо ви зацікавлені в розумінні Як Rust досягає безпечної пам'яті без збирання сміття та паралельності без страху виконання данихЦей посібник для вас. Ми розглянемо все: від основ мови та її екосистеми до ключових концепцій, таких як власність, запозичення, складені типи, інструменти, такі як Cargo, і навіть розглянемо атомарні типи та блокування з більш доступної точки зору для новачків у паралельному роботі, з акцентом на безпеці та продуктивності.

Підручник з Rust: Продуктивність, безпека пам'яті та паралельність

Rust — це мова програмування Програмування загального призначення та мультипарадигмальний, розроблений для як для низькорівневого системного програмування, так і для високорівневих проектівз тих пір Операційна системаВід ігрових двигунів та браузерів до високопродуктивних веб-сервісів, він виник у Mozilla з метою покращення безпеки програмного забезпечення, особливо в чутливих компонентах, таких як двигун браузера.

Його визначальною рисою є те, що гарантує безпеку пам'яті під час компіляції без використання збирача сміття. Натомість Rust використовує систему володіння та перевірку запозичень, яка відстежує час життя кожного значення та його посилань. Це дозволяє уникнути класичних проблем, таких як завислі вказівники, переповнення буфера або витоки пам'яті, без необхідності автоматичного підрахунку посилань або збирання сміття.

Крім того, Rust розроблений для того, щоб спростити безпечне відвідуванняЙого модель типів та володіння запобігає перегонам даних між потоками, принаймні залишаючись безпечним у коді Rust. Це означає, що багато небезпечних ситуацій виявляються під час компіляції, ще до виконання хоча б одного рядка.

З усіх цих причин великим компаніям подобається Dropbox, Microsoft, Amazon або Google Вони впровадили Rust у критично важливих частинах своєї інфраструктури. І не випадково він роками очолював опитування Stack Overflow як одна з «найулюбленіших» мов розробників: він поєднує продуктивність у стилі C++ із сучасним набором інструментів (Cargo, crates.io) та дуже активною спільнотою, так званими Rustaceans.

Базові поняття: мова програмування, типи та пам'ять

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

Мова програмування, зрештою, це набір правил і структур, що дозволяє описувати алгоритми та перетворювати їх на виконувані програми. Rust компілюється в власний машинний код за допомогою свого компілятора rustcТаким чином, продуктивність, яку ви отримуєте, зазвичай на рівні C та C++.

Керування пам'яттю – це процес, за допомогою якого програма резервує та звільняє блоки пам'яті під час роботиПомилки в цій області часто є фатальними: витоки пам'яті (неможливість звільнити невикористану пам'ять), пошкодження даних через запис за межі дозволеного простору або використання пам'яті після її звільнення. Rust вирішує цю проблему за допомогою дуже сильної системи типів та формальних правил володіння, запозичення та терміну служби.

Rust також містить такі терміни, як розумні типи та вказівникиТип описує, які дані зберігає змінна (цілі числа, числа з плаваючою комою, рядки, структури тощо) та як ними можна маніпулювати. Розумні вказівники (наприклад, Box, Rc y Arc) – це структури, які інкапсулюють адреси пам'яті та додають додаткову логіку для безпечного керування ресурсами, таку як підрахунок спільних посилань або переміщення значень до купи.

У сфері конкуренції такі поняття, як умови гонки, м'ютекси та канали Вони стають незамінними: умова гонки виникає, коли кілька потоків одночасно отримують доступ до спільного ресурсу та змінюють його без належної координації; м'ютекс (взаємне виключення) гарантує, що лише один потік входить до критичної секції одночасно; а канали дозволяють надсилати повідомлення між потоками без безпосереднього спільного використання пам'яті.

Чому варто вивчати Rust: Безпека пам'яті та безстрашний паралельний процес

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

Щодо продуктивності, Rust компілюється безпосередньо у рідні бінарні файли без потреби у віртуальній машині чи інтерпретаторі. Модель абстракцій з нульовою вартістю має на меті забезпечити, щоб високорівневі абстракції не додавали накладних витрат під час виконання. Тому вона ідеально підходить для розробки систем. гра, компоненти браузера або мікросервіси з низькою затримкою.

Безпека пам'яті базується на її система власності та позикиНемає збирача сміття, але компілятор точно знає, кому належить кожен ресурс, коли він більше не потрібен і коли його можна звільнити. Це запобігає витокам, завислим вказівникам та багатьом помилкам, які традиційно робили програмування на C та C++ таким небезпечним.

У сфері конкуренції Rust переслідує те, що зазвичай називають «Паралелізм без страху»Сама система типів запобігає існуванню кореневих елементів даних у захищеному коді. Якщо ви хочете обмінюватися змінними даними між потоками, вам потрібно буде використовувати відповідні примітиви, такі як Mutex, RwLock o Arc, а компілятор забезпечить дотримання правил псевдонімів та змінюваності.

  Як покроково дезінфікувати Windows за допомогою Malwarebytes

Досвід розробки покращено за допомогою сучасних інструментів, таких як ВантажВін має інтегрований менеджер пакетів та інфраструктуру збірок, а також широку екосистему бібліотек (крейтів), що охоплюють усе: від асинхронних мереж (Tokyo) до веб-фреймворків (Actix, Rocket, Axum). Все це підтримується відкритою, плідною та досить терплячою спільнотою, особливо для початківців.

Монтаж та необхідні інструменти: rustup, rustc та Cargo

Щоб написати та запустити свої перші програми на Rust, зазвичай потрібно встановити офіційний набір інструментів за допомогою руступ (див. Повне введення в Rust), простий інсталятор та менеджер версій, який працює на всіх основних операційних системах.

з руступ Ви можете встановлювати, оновлювати та перемикатися між різними версіями Rust (стабільною, бета-версією, нічною) без жодних проблем. Просто перейдіть на офіційну сторінку інструментів Rust та виконайте кроки для вашої системи. Після встановлення компілятор буде доступний. rustc, керівник проекту cargo і свій власний rustup у вашому термінал.

компілятор rustc Це те, що перетворює ваш вихідний код на виконувані бінарні файли або бібліотеки. Хоча ви можете викликати його безпосередньо за допомогою Команди як rustc main.rsНа практиці ви майже завжди працюватимете через Cargo, який обробляє виклики до rustc з правильними опціями.

Центральним інструментом робочого процесу є ВантажЗа допомогою лише кількох команд ви можете створювати нові проекти, керувати залежностями, компілювати, запускати, тестувати та публікувати пакети на crates.io. Деякі поширені основні команди: cargo new, cargo build, cargo run, cargo test y cargo check, який перевіряє код без створення кінцевого виконуваного файлу, що ідеально підходить для швидкого виявлення помилок.

Якщо ви хочете повозитися, нічого не встановлюючи, Іржа дитячий майданчик (офіційний онлайн-виконавець) та платформи, такі як Replit, дозволяють писати та запускати невеликі фрагменти коду з браузера, що ідеально підходить для експериментів з пам'яттю та прикладами паралельного використання без необхідності налаштування всього середовища.

Ваша перша програма: Привіт, Rust та базовий потік

Класичний спосіб розпочати розмову будь-якою мовою – це відоме «Привіт, світе». У Rust файл main.rs принаймні може містити щось таке просте, як функція main який друкує рядок на екрані.

Ключове слово fn вказує на те, що ми визначаємо функцію, і main Це точка входу в програму. Блок коду функції знаходиться у фігурних дужках. Щоб записати в консоль, використовуйте макрос println!, який приймає рядковий літерал (або шаблон із закладками) та надсилає його на стандартний вивід, що закінчується символом нового рядка.

Якщо ви компілюєте безпосередньо з rustc main.rs, ви отримаєте виконуваний бінарний файл (наприклад, main o main.exe (залежно від системи). Коли ви запустите його, ви побачите повідомлення в терміналі. Але ідіоматичний спосіб роботи з Rust — дозволити Cargo взяти на себе ініціативу над проектом.

з cargo new nombre_proyecto Структура папок створюється автоматично з src/main.rs вже підготовлений з "Привіт, світ" та файлом Cargo.toml який містить метадані та майбутні залежності. Звідти, cargo run скомпілювати та запустити бінарний файлі він перекомпілюється лише тоді, коли виявляє зміни.

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

// Оголошуємо головну функцію: точка входу в програму fn main() { // Ми використовуємо макрос println! для виводу тексту в консоль println!("Привіт, світе!"); }

Змінні, мінливість та основні типи даних

У Rust змінні оголошуються за допомогою ключового слова let, і за замовчуванням незмінніІншими словами, після того, як ви присвоюєте їм значення, ви не можете його змінити, якщо явно не оголосите його змінним за допомогою mut.

Незмінність за замовчуванням допомагає уникнути ледь помітних логічних помилок, особливо в паралельних програмах, де кілька потоків можуть захотіти змінити одне й те саме значення. Якщо вам потрібно його змінити, ви пишете щось на кшталт let mut contador = 0;Звідти ви можете перепризначити нові значення contador.

Rust також дозволяє т.зв. затіненняВи можете оголосити нову змінну з тим самим ім'ям у тій самій області видимості, приховуючи попередню. Це не те саме, що мутація, оскільки ви створюєте нове значення (яке може бути навіть іншого типу). Наприклад, ви можете перетворити рядок на ціле число, використовуючи те саме ім'я, якщо це нове оголошення з let.

Система типів Rust є статичною, що означає, що Тип кожної змінної відомий під час компіляціїОднак, виведення типів є досить потужним: якщо ви пишете let x = 5;Компілятор припускає, що це i32 Якщо ви не вкажете інше. Ви можете додавати такі нотатки, як let x: i64 = 5; коли ви хочете бути відвертим.

Серед доступних скалярних типів є знакові та беззнакові цілі числа (i8, u8, i32тощо), плаваючі (f32, f64), булеві функції (bool) та символи Юнікоду (char). Ці прості типи зазвичай дешево копіювати, і багато хто реалізує цю рису Copyце означає, що коли ви призначаєте їх або передаєте функції, вони копіюються, а не переміщуються.

Рядки в Rust: &str та String

Обробка тексту в Rust може спочатку бути трохи заплутаною, оскільки вона чітко розрізняє «фрагменти» ланцюга та власні ланцюгиДві ключові частини - це &str y String.

Un &str є фрагмент незмінного ланцюгаВигляд послідовності байтів UTF-8, що зберігається десь. Типові приклади включають літерали, такі як "Hola"які належать до типу &'static str (Вони існують протягом усього життя програми та вбудовані в бінарний файл.) Зрізи не володіють даними; вони лише вказують на них.

  Як ретельно налаштувати панель завдань у Windows 11: Повний посібник та розширені поради

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

У багатьох сценаріях ви будете перемикатися між одним та іншим: наприклад, ви створите String::from("hola") з шматочкаабо ви позичите &str по String шляхом передачі посилань на функції, які потребують лише читання.

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

Функції, потік керування та коментарі

Функції в Rust визначаються за допомогою fn і дозволяють організувати програму в логічні одиниці повторного використання. Кожна функція визначає тип його параметрів та тип повернення слідуючи за стрілкою ->Якщо не повертається нічого значущого, передбачається унітарний тип. ().

Важливою деталлю є те, що останній вираз у функції (або будь-якому блоці) без крапки з комою вважається неявним поверненим значенням. Ви можете використовувати return для дострокового повернення коштівАле в ідіоматичному коді ви часто просто залишаєте кінцевий вираз без нього. ;.

Потік керування обробляється класичними методами if/elseпетлі loop, while y forУ Русті, if Це вираз, який повертає значеннятож ви можете використовувати його безпосередньо в letза умови, що гілки повертають однаковий тип. Цикли for Зазвичай вони перебирають діапазони або ітератори колекцій і є рекомендованим варіантом замість ручних індексів.

Щоб задокументувати код і полегшити життя тим, хто прийде після нього (включно з вами через місяць), ви можете використовувати рядкові коментарі з // або блокувати за допомогою /* ... */Крім того, Rust пропонує коментарі до документації з /// які стають згенерованими документами, хоча це більше підходить для великих проектів.

Володіння, позика та тривалість життя: основа безпеки пам'яті

Тут ми підходимо до суті моделі пам'яті Rust: системи власність, запозичення та тривалість життяЦі правила гарантують, що посилання завжди коректні, а пам'ять безпечно звільняється без накопиченого сміття.

Основні правила власності легко сформулювати, хоча спочатку їх може бути важко засвоїти: Кожне значення має одного власника.Одночасно може бути лише один власник; і коли власник залишає свою область видимості, значення знищується, а його пам'ять звільняється. Це стосується, наприклад, String: після завершення блоку, де він був оголошений, він автоматично викликається drop що звільняє купу пам'яті.

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

Щоб дозволити кільком частинам програми отримувати доступ до одного й того ж значення без зміни власника, Rust вводить посилання та запозичення. Під час запозичення ви створюєте посилання. &T (незмінний) або &mut T (змінне) на значення без передачі права власності. Позика обмежена правилами верифікатора позики., який перевіряє, чи посилання не переживають дані, на які вони вказують, і чи змінні та спільні доступи не змішуються небезпечно.

Правила позики можна коротко описати наступним чином: у будь-який момент часу ви можете мати кілька незмінних посилань до значення, або одне змінне посиланняАле не обидва одночасно. Це усуває умови гонки в спільній пам'яті: або є багато читачів, або є ізольований записувач; ніколи одночасні читачі та записувачі не працюють з одними й тими ж даними в один і той же момент.

Складені типи: структури, перелічення та розумні вказівники

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

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

L переліки Вони є ще одним важливим стовпом: вони дозволяють визначити тип, який може бути одним із кількох можливих варіантів, кожен з власними пов'язаними даними або без них. Класичним прикладом є перерахування IP-адрес з одним варіантом V4 який зберігає чотири октети та ще один V6 який зберігає рядок з нотацією IPv6.

Стандартна бібліотека Rust містить два дуже важливі перелічення: Option<T> y Result<T, E>Перший представляє наявність або відсутність значення (щось або нічого) і використовується для уникнення нульових вказівників; другий моделює операції, які можуть повернути правильний результат або помилку, що вимагає, щоб обробка помилок була явною та безпечною.

Для керування динамічною пам'яттю та обміну даними Rust має розумні вказівники як Box<T>, який переміщує значення до купи та зберігає унікальне право власності; Rc<T>, спільний лічильник посилань для однопотокових середовищ; та Arc<T>, подібний до Rc але безпечні для кількох потоків. Їх правильне використання є критично важливим при поєднанні динамічної пам'яті з паралельністю.

Вантаж та екосистема ящиків

Вантаж — це клей, який об'єднує екосистему Rust: керує компіляцією, залежностями та життєвим циклом проектуКожен проект має файл Cargo.toml який діє як маніфест, де оголошуючи назву, версію, мовну редакцію та зовнішні залежності.

  Виправлено: помилка внутрішнього драйвера шини PCI Windows 10

Розділ Цей файл дозволяє вам переглянути список сторонніх крейтів з їхніми версіями. Під час запуску cargo build o cargo runCargo автоматично завантажує ці крейти з crates.io, компілює їх та прив'язує до вашого проєкту. Так само легко додати, наприклад, генератори випадкових чисел, веб-фреймворки або криптографічні бібліотеки.

Серед найпоширеніших команд є cargo new розпочати бінарні проекти o cargo new --lib для бібліотек; cargo build компілювати в режимі налагодження; cargo build --release отримати оптимізовану, орієнтовану на виробництво версію; та cargo test провести низку тестів.

cargo check Він заслуговує на особливу згадку: компілюється до проміжної точки без генерації бінарного файлу, що робить його бути дуже швидким у виявленні помилок компіляціїЦе ідеально підходить для швидкого виконання ітерацій, тоді як перевірка запозичень вказує на проблеми з властивостями, посиланнями та часом життя.

Завдяки цій екосистемі, поширеною є структура проєктів як невеликих, чітко визначених контейнерів (крейтів), спільний доступ до коду між ними та повторне використання рішень, створених спільнотою. Наприклад, для розширеного паралельного програмування у вас будуть контейнери, такі як Tokio для асинхронного програмування або Crossbeam для високопродуктивних паралельних структур даних.

Паралелізм у Rust: потоки, м'ютекси, канали та атоміки

Паралелізм — одна з причин, чому Rust викликає такий великий інтерес: він дозволяє скористатися перевагами багатоядерних процесорів. без впадіння в типові помилки потоків та спільної пам'ятіЯкщо ви вперше звертаєтеся до цих тем, корисно розрізняти кілька понять.

Паралелізм передбачає виконання кількох завдань, які перетинаються в часі, на одному або кількох ядрах. У Rust ви можете створювати системні потоки для паралельного виконання роботи, а мова програмування допомагає вам забезпечити безпечний обмін даними між ними. Класичною помилкою є умова гонки, коли два потоки одночасно отримують доступ до даних та змінюють їх, а результат залежить від порядку виконання — це дуже важко налагодити.

Для координації доступу до спільних даних Rust спирається на такі примітиви, як мутексщо гарантує взаємне виключення: лише один потік може входити до критичної секції одночасно. У поєднанні з Arc<T> Для розподілу власності між потоками можна створювати спільні структури даних, які відповідають правилам власності та запозичення.

Ще однією поширеною формою міжпотокового зв'язку, яка дуже заохочується в Rust, є передача повідомлень за допомогою каналиКанал має сторону відправника та сторону приймача; потоки передають повідомлення (значення) через нього, що зменшує використання змінної спільної пам'яті та спрощує міркування про стан системи.

Якщо глибше заглибитися в низькорівневу паралельність, то можна побачити наступне: атомні типиДоступ до атомарних змінних здійснюється через операції, які є неподільними з точки зору потоку. Це дозволяє реалізувати спільні лічильники, прапорці станів, черги без блокування тощо. Оволодіння атомарними змінними вимагає розуміння моделей пам'яті та команд доступу, тому багато розробників вважають за краще починати з м'ютексів та каналів, перш ніж заглиблюватися в ці деталі.

Перші кроки та ресурси для вивчення паралельності та атомарності

Якщо ви виходите на арену без попереднього досвіду, найрозумнішим варіантом дій буде створити міцну основу загальних понять перш ніж братися за просунуті інструменти, такі як атомарні типи Rust. Книги на кшталт «Програмування на Rust» пропонують поступовий вступ, але цілком нормально, що роботи, зосереджені на атомарних типах та блокуваннях, спочатку здаються складними.

Для більшої зручності рекомендується спочатку ознайомитися з Традиційні теми, взаємне виключення та обмін повідомленнями в Rust. Поекспериментуйте з прикладами std::thread, std::sync::Mutex, std::sync::Arc і канали std::sync::mpsc Це допомагає зрозуміти, як компілятор керує вами та яких помилок він уникає.

Паралельно з цим наполегливо рекомендується переглянути вступні ресурси з паралельності загалом, навіть якщо вони не зосереджені на Rust: розуміння того, що таке умови гонки, що означає блокування, що означає спільна пам'ять порівняно з передачею повідомлень, і як використовуються блокування. Як тільки ці концепції стануть для вас природними, атомна фізика перестане бути «чорною магією». і вони стають просто ще одним інструментом, тільки дуже делікатним.

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

Зрештою, Rust пропонує як високорівневі примітиви, так і дуже низькорівневі інструменти, і головне завжди вибирати найбезпечніший рівень абстракції, який вирішує вашу проблему, вдаючись до атомарного коду. unsafe лише тоді, коли це справді додає цінності, і ви повністю розумієте його наслідки.

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

Вступ до мови Rust з прикладами-0
Пов'язана стаття:
Повний вступ до Rust: Практичний посібник для початківців із прикладами