stejně jako mnoho vývojářů se o Rust zajímám už nějakou dobu. Nejen proto, že se objevuje v tolika titulcích hackerských zpráv, nebo kvůli novému přístupu, který jazyk zaujímá k bezpečnosti a výkonu, ale také proto, že se zdá, že o tom lidé mluví se zvláštním smyslem pro lásku a obdiv. Navíc, Rust je pro mě obzvláště zajímavý, protože sdílí některé stejné cíle a rysy mého oblíbeného jazyka go – to: rychlý. Vzhledem k tomu, že jsem si nedávno udělal čas vyzkoušet Rust v některých malých osobních projektech, chtěl jsem si trochu času zdokumentovat své dojmy z jazyka, zejména v tom, jak se srovnává s Swift.

The Big Picture

Rust a Swift mají mnoho společného: oba jsou kompilované jazyky s výkonnými, moderními typovými systémy a zaměřením na bezpečnost. Funkce, jako jsou algebraické typy a prvotřídní zpracování volitelných hodnot, pomáhají přesunout mnoho tříd chyb z běhu do doby kompilace v obou těchto jazycích.

jak se tedy tyto jazyky liší? Nejlepší způsob, jak mohu charakterizovat rozdíl je:

Swift usnadňuje psaní bezpečného kódu.
Rust ztěžuje psaní nebezpečného kódu.

tyto dva výroky mohou znít rovnocenně, ale existuje důležitý rozdíl. Oba jazyky mají nástroje k dosažení bezpečnosti, ale k dosažení tohoto cíle dělají různé kompromisy: Swift upřednostňuje ergonomii na úkor výkonu, zatímco Rust upřednostňuje výkon na úkor ergonomie.

kompromis: Výkon vs ergonomie

největší způsob, jak je tento rozdíl v prioritě demonstrován, je v přístupu, který tyto jazyky mají ke správě paměti. Začnu Rustem, protože přístup jazyka ke správě paměti je jedním z jeho jedinečných prodejních míst.

v Rustu je paměť primárně spravována staticky (ano, existují i jiné způsoby správy paměti, jako je počítání referencí, ale prozatím je ignorujeme). To znamená, že kompilátor Rust analyzuje váš program a podle sady pravidel rozhodne, kdy má být paměť přidělena a uvolněna.

aby byla zajištěna bezpečnost, Rust používá novou strategii nazvanou borrow checking. V praxi to funguje tak, že jako programátor pokaždé, když projdete kolem proměnné (tj. odkaz na umístění paměti), musíte určit, zda je odkaz proměnlivý nebo neměnný. Kompilátor pak používá sadu pravidel, aby zajistil, že nemůžete zmutovat jediný kus paměti na dvou místech najednou, čímž je prokazatelné, že váš program nemá datové závody.

tento přístup má některé velmi prospěšné vlastnosti s ohledem na využití paměti a výkon. Kontrola výpůjček může být velmi náročná na paměť, protože se obecně vyhýbá kopírování hodnot. Rovněž se vyhýbá režii výkonu řešení, jako je sběr odpadků, protože práce se provádí spíše v době kompilace než za běhu.

nicméně, to přijde s některými nevýhodami, pokud jde o snadné použití. Vzhledem k povaze vlastnictví v rzi existují některé vzory, které prostě nefungují v rzi. Například není triviální implementovat něco jako dvojitě propojený seznam nebo globální proměnnou. To se s časem pravděpodobně stane intuitivnějším a pro tyto problémy existují řešení, ale Rust určitě ukládá programátorovi omezení, která nejsou přítomna v jiných jazycích.

i když to není tak často mluvil jako Rust, Swift má také zajímavý příběh, pokud jde o správu paměti.

Swift má dva základní typy proměnných: referenční typy a typy hodnot. Obecně jsou referenční typy přiděleny haldy a jsou spravovány počítáním referencí. To znamená, že za běhu je sledován počet odkazů na referenční počítaný objekt a objekt je přerozdělen, když počet dosáhne nuly. Počítání referencí ve Swiftu je vždy atomové: to znamená, že pokaždé, když se změní počet referencí, musí existovat synchronizace mezi všemi vlákny CPU. To má tu výhodu, že eliminuje možnost chybného uvolnění odkazu ve vícevláknové aplikaci, ale přichází s významnými náklady na výkon, protože Synchronizace CPU je velmi nákladná.

Rust má také nástroje pro počítání referencí a počítání atomových referencí, ale ty jsou spíše opt-in než výchozí.

typy hodnot jsou naproti tomu obecně alokovány a jejich paměť je spravována staticky. Chování hodnotových typů ve Swiftu se však velmi liší od toho, jak Rust zpracovává paměť. Ve Swiftu jsou ve výchozím nastavení zkopírovány typy hodnot, které se nazývají „copy-on-write“, což znamená, že pokaždé, když je typ hodnoty zapsán do nové proměnné nebo předán funkci, je vytvořena kopie.

Copy-on-write kopírování ve výchozím nastavení dosahuje některé ze stejných cílů vypůjčit kontrolu: jako programátor obvykle nikdy nebudete muset starat o hodnotu mění záhadně kvůli nějaké neočekávané vedlejší efekt jinde v programu. Vyžaduje také o něco menší kognitivní zátěž než kontrola výpůjček, protože v Rustu jsou celé třídy chyb kompilace souvisejících s vlastnictvím, které ve Swiftu jednoduše neexistují. Nicméně, to přijde za cenu: tyto další kopie vyžadují další využití paměti a CPU cykly dokončit.

v Rustu je také možné kopírovat hodnoty jako způsob, jak umlčet chyby při kontrole, ale to přidává vizuální šum, protože kopie musí být explicitně specifikovány.

takže zde máme dobrý příklad kompromisů provedených těmito dvěma jazyky: Swift vám dává několik obecných předpokladů o tom, jak by měla být paměť spravována při zachování úrovně bezpečnosti. Je to trochu jako, jak programátor C++ může zpracovat paměť podle osvědčených postupů, než se hodně zamyslí nad optimalizací. Díky tomu je velmi snadné skočit a psát kód, aniž byste hodně přemýšleli o detailech na nízké úrovni, a také dosažení některých základních záruk bezpečnosti a správnosti běhu, které byste nedostali v jazyce, jako je Python nebo dokonce Golang. Nicméně přichází s některými výkonnostními útesy, které je snadné spadnout, aniž byste si to uvědomili, dokud nespustíte svůj program. Je možné psát vysoce výkonný Swift kód, ale to často vyžaduje pečlivé profilování a optimalizaci k dosažení.

Rust, na druhé straně, vám dává mnoho specifických nástrojů pro určení, jak by měla být paměť spravována, a pak umístí některá tvrdá omezení, jak je používáte, abyste se vyhnuli nebezpečnému chování. To vám dává velmi pěkné výkonnostní charakteristiky hned po vybalení z krabice, ale vyžaduje to, abyste převzali další kognitivní režii zajištění dodržování všech pravidel.

Moje cesta z toho byla, že i když tyto jazyky mají některé společné cíle, mají zásadně odlišné vlastnosti, které se hodí pro různé případy použití. Rust se například zdá být jasnou volbou pro něco, jako je vestavěný vývoj, kde je optimální využití paměti a cyklů CPU nesmírně důležité a kde smyčka kódu-kompilace-běh může být pomalejší,takže je cenné zachytit všechny možné problémy v době kompilace. Zatímco Swift může být lepší volbou pro něco, jako je věda o datech nebo logika bez serverů, kde výkon je druhotným problémem, a je cenné pracovat blíže k problémové doméně, aniž byste museli brát v úvahu mnoho detailů na nízké úrovni.

v každém případě budu mít velký zájem sledovat oba tyto jazyky v budoucnu a budu sledovat tento příspěvek s dalšími pozorováními o srovnání mezi Swift a Rust.

Napsat komentář

Vaše e-mailová adresa nebude zveřejněna.