Краща історія паралельності є однією з головних цілей проекту «Іржа», тому слід очікувати вдосконалення за умови довіри проекту до досягнення його цілей. Повна відмова від відповідальності: Я високо оцінюю Расту і я в неї інвестую. У відповідності з проханням, я буду намагатися оціночними судженнями уникати і описати відмінності , а не (ИМХО) поліпшень .
Безпечна і небезпечна іржа
"Іржа" складається з двох мов: тієї, яка дуже намагається ізолювати вас від небезпеки системного програмування, і більш потужної без будь-яких подібних прагнень.
Небезпечна іржа - це бридка, британська мова, яка дуже відчуває себе як C ++. Це дозволяє робити довільно небезпечні речі, розмовляти з технікою, (неправильно) керувати пам’яттю вручну, стріляти собі в ногу і т. Д. Це дуже схоже на C і C ++ в тому, що правильність програми в кінцевому рахунку у ваших руках і руки всіх інших програмістів, які беруть участь у цьому. Ви ввімкнули цю мову за допомогою ключового слова unsafe
, і, як і в C та C ++, одна помилка в одному місці може призвести до того, що весь проект вийде з ладу.
Безпечна іржа - це "за замовчуванням", переважна більшість коду Rust є безпечною, і якщо ви ніколи не згадуєте ключове слово unsafe
у своєму коді, ви ніколи не залишаєте безпечну мову. Інша частина публікації стосуватиметься переважно цієї мови, оскільки unsafe
код може порушити будь-які гарантії того, що безпечний Rust так важко дає вам. З іншого боку, unsafe
код не є злим і не сприймається громадою як такий (він, однак, сильно не рекомендується, коли це не потрібно).
Це так, але це також важливо, оскільки дозволяє будувати абстракції, які використовує безпечний код. Хороший небезпечний код використовує систему типу, щоб запобігти зловживанням ним інших людей, а тому наявність небезпечного коду в програмі Rust не повинна порушувати безпечний код. Усі наступні відмінності існують через те, що в системах типу Руста є інструменти, яких у C ++ немає, і через те, що небезпечний код, який реалізує абстракцію паралельності, ефективно використовує ці інструменти.
Без різниці: спільна / змінна пам'ять
Хоча Руст приділяє більше уваги передачі повідомлень і дуже суворо керує спільною пам'яттю, вона не виключає одночасності спільної пам'яті і явно підтримує загальні абстракції (блокування, атомні операції, змінні стану, одночасні колекції).
Більше того, як C ++ і на відміну від функціональних мов, Руст дуже любить традиційні імперативні структури даних. У стандартній бібліотеці немає стійкого / незмінного пов'язаного списку. Є, std::collections::LinkedList
але це як std::list
у C ++ і відстороняється з тих же причин, що і std::list
(неправильне використання кешу).
Однак, посилаючись на заголовок цього розділу ("спільна / змінна пам'ять"), Rust має одну різницю на C ++: Це настійно рекомендує пам'яті бути "спільним XOR-змінним", тобто, що пам'ять ніколи не поділяється і не змінюється одночасно. час. Вимкніть пам'ять, як вам подобається, "у конфіденційності власної нитки", так би мовити. Порівнюйте це зі C ++, де спільна мутаційна пам'ять є типовим варіантом і широко використовується.
Незважаючи на те, що парадигма розділяється на xor-мутацій є дуже важливою для наведених нижче відмінностей, це також зовсім інша парадигма програмування, яка потребує певного часу, щоб звикнути, і це ставить значні обмеження. Інколи доводиться відмовлятися від цієї парадигми, наприклад, з атомними типами ( AtomicUsize
це суть спільної змінної пам'яті). Зауважте, що блоки також підкоряються правилу спільного xor-mutable, оскільки воно виключає одночасне зчитування та записування (поки пише один потік, жоден інший потік не може читати чи записувати).
Без різниці: перегони даних - це не визначена поведінка (UB)
Якщо ви запускаєте перегони даних у коді Rust, це закінчується грою, як і в C ++. Усі ставки відключені, і компілятор може робити все, що заманеться.
Однак, це гарантія, що безпечний код Rust не має перегонів даних (або будь-якого UB з цього питання). Це поширюється як на основну мову, так і на стандартну бібліотеку. Якщо ви можете написати програму Rust, яка не використовує unsafe
(в тому числі в сторонніх бібліотеках, але виключає стандартну бібліотеку), яка запускає UB, то це вважається помилкою і буде виправлено (це вже траплялося кілька разів). Це, звичайно, на відміну від C ++, де писати програми з UB тривіально.
Різниця: Сувора дисципліна блокування
В відміну від C ++, замок в Руста ( std::sync::Mutex
, std::sync::RwLock
, і т.д.) має дані , які він захищають. Замість того, щоб брати замок і потім маніпулювати деякою спільною пам'яттю, яка асоціюється з блокуванням лише в документації, спільні дані недоступні, поки ви не тримаєте замок. Охоронець RAII зберігає блокування і одночасно надає доступ до заблокованих даних (це багато що може бути реалізовано C ++, але не за допомогою std::
блокування). Система експлуатації гарантує, що ви не можете продовжувати отримувати доступ до даних після звільнення блокування (скиньте захист RAII).
Звичайно, ви можете мати замок, який не містить корисних даних ( Mutex<()>
), і просто поділитися деякою пам’яттю, не пов'язуючи його з цим замком. Однак наявність потенційно несинхронізованої спільної пам'яті вимагає unsafe
.
Різниця: Запобігання випадковому обміну
Хоча ви можете мати спільну пам'ять, ви ділитесь лише тоді, коли явно про це просите. Наприклад, коли ви використовуєте повідомлення, що передаються (наприклад, канали від std::sync
), система експлуатації гарантує, що ви не збережете посилання на дані після того, як ви надіслали їх в інший потік. Щоб ділитися даними за блоком, ви чітко будуєте замок і надаєте їх іншому потоку. Щоб поділитися з unsafe
вами несинхронізованою пам’яттю , доведеться використовувати unsafe
.
Це пов'язано з наступним моментом:
Різниця: відстеження безпеки нитки
Система типу Руста відстежує деяке поняття безпеки ниток. Зокрема, Sync
ознака позначає типи, які можуть бути спільними для декількох потоків без ризику перебігу даних, тоді як Send
позначає ті, які можна переміщувати з одного потоку в інший. Це застосовується компілятором у всій програмі, і таким чином дизайнери бібліотек наважуються робити оптимізацію, яка була б нерозумно небезпечною без цих статичних перевірок. Наприклад, C ++, std::shared_ptr
який завжди використовує атомні операції, щоб маніпулювати своїм контрольним числом, щоб уникнути UB, якщо shared_ptr
трапляється використовувати декілька потоків. Іржа має Rc
і Arc
відрізняється лише тим, що Rc
використовує неатомні операції перезавантаження і не є безпечною для потоків (тобто не реалізує Sync
або Send
), хоча Arc
дуже схожаshared_ptr
(і реалізує обидві ознаки).
Зауважте, що якщо тип не використовується unsafe
вручну для синхронізації, наявність чи відсутність ознак визначається правильно.
Різниця: Дуже суворі правила
Якщо компілятор не може бути абсолютно впевнений, що якийсь код не містить перегонів даних та інших UB, він не буде компілюватися, періодично . Вищезазначені правила та інші інструменти можуть доставити вас далеко, але рано чи пізно ви захочете зробити щось правильне, але з тонких причин, що уникають повідомлення компілятора. Це може бути хитрою структурою даних без блокування, але вона може бути настільки ж повсюдною, як "Я записую у випадкові місця у спільному масиві, але індекси обчислюються таким чином, що кожне місце записується лише одним потоком".
У цей момент ви можете або вкусити кулю, і додати трохи непотрібної синхронізації, або переробити код таким чином, щоб компілятор міг бачити його правильність (часто це можливо, іноді досить важко, іноді неможливо), або ви кинете в unsafe
код. І все-таки це додаткові ментальні витрати, і Руст не дає жодних гарантій на правильність unsafe
коду.
Різниця: менше інструментів
Через вищезазначені відмінності, в Rust набагато рідше пишеться код, який може мати перегони даних (або використання після безкоштовного, або подвійне вільне, або ...). Незважаючи на те, що це приємно, але прикро, що екосистема для відстеження таких помилок є ще більш розвиненою, ніж можна було б очікувати, враховуючи молодь та невеликий розмір громади.
Хоча такі інструменти, як valgrind та дезінфікуючий потік LLVM, в принципі можуть бути застосовані до коду Rust, чи це насправді все ще працює від інструменту до інструменту (і навіть ті, які працюють, можливо важко налаштувати, тим більше, що ви не можете знайти будь-які оновлення -надавати ресурси про те, як це зробити). Це не дуже допомагає, що Русту в даний час не вистачає реальної специфікації, зокрема формальної моделі пам'яті.
Коротше кажучи, unsafe
правильно писати Rust-код важче, ніж правильно писати код C ++, незважаючи на те, що обидві мови приблизно порівнянні за можливостями та ризиками. Звичайно, це слід зважити на той факт, що типова програма Rust буде містити лише відносно невелику частину unsafe
коду, тоді як програма C ++ - це повністю C ++.