mint sok fejlesztő, már jó ideje érdekel a Rust. Nem csak azért, mert úgy tűnik, oly sok címoldalára Hacker News, vagy azért, mert az új megközelítés a nyelv veszi a biztonság és a teljesítmény, hanem azért is, mert az emberek úgy tűnik, hogy beszélni róla egy bizonyos értelemben a szeretet és csodálat. Ráadásul a Rust különösen érdekes számomra, mert ugyanazokkal a célokkal és jellemzőkkel rendelkezik, mint a kedvenc nyelvem: Swift. Mivel nemrégiben időt szántam arra, hogy kipróbáljam a Rust-ot néhány kis személyes projektben, szerettem volna egy kis időt szánni a nyelv benyomásainak dokumentálására, különösen abban, hogy hogyan viszonyul a Swifthez.

The Big Picture

a Rustnak és a Swiftnek sok közös vonása van: mindkettő lefordított nyelv, erőteljes, modern típusú rendszerekkel és a biztonságra összpontosítva. Az olyan funkciók, mint az algebrai típusok és az opcionális értékek első osztályú kezelése, segítenek a hibák sok osztályát áthelyezni a futási időből a fordítási időbe mindkét nyelven.

tehát hogyan különböznek ezek a nyelvek? A legjobb módja annak, hogy jellemezzem a különbséget:

a Swift megkönnyíti a biztonságos kód írását.
a Rust megnehezíti a nem biztonságos kód írását.

ez a két állítás egyenértékű lehet, de van egy fontos különbség. Mindkét nyelv rendelkezik eszközökkel a biztonság eléréséhez, de ennek elérése érdekében különböző kompromisszumokat tesznek: a Swift az ergonómiát a teljesítmény rovására, míg a Rust a teljesítményt az ergonómia rovására helyezi előtérbe.

a kompromisszum: Teljesítmény vs ergonómia

a prioritások közötti különbség legnagyobb bizonyítéka az, hogy ezek a nyelvek hogyan viszonyulnak a memóriakezeléshez. A Rust-tal kezdem, mert a nyelv megközelítése a memóriakezeléshez az egyik egyedülálló értékesítési pont.

a Rust-ban a memóriát elsősorban statikusan kezelik (igen, vannak más memóriakezelési módok is, például a referenciaszámlálás, de ezeket egyelőre figyelmen kívül hagyjuk). Ez azt jelenti, hogy a Rust fordító elemzi a programot, és egy szabályrendszer szerint eldönti, hogy mikor kell a memóriát lefoglalni és felszabadítani.

a biztonság érdekében a Rust egy új stratégiát használ, amelyet kölcsönellenőrzésnek hívnak. A gyakorlatban ez úgy működik, hogy programozóként minden alkalommal, amikor egy változót (azaz egy memóriahelyre való hivatkozást) megkerülsz, meg kell adnod, hogy a hivatkozás változtatható vagy megváltoztathatatlan. A fordító ezután egy sor szabályt használ annak biztosítására, hogy egyetlen memóriadarabot ne mutáljon egyszerre két helyen, így bizonyítható, hogy a program nem rendelkezik adatversenyekkel.

ennek a megközelítésnek nagyon hasznos tulajdonságai vannak a memóriahasználat és a teljesítmény tekintetében. A kölcsönzés ellenőrzése nagyon óvatos lehet a memóriával, mivel általában elkerüli az értékek másolását. Ezenkívül elkerüli az olyan megoldások teljesítményét, mint a szemétgyűjtés, mivel a munkát fordítási időben, nem pedig futásidőben végzik.

azonban ez nem jön néhány hátránya, mint amennyire a könnyű használat. A rozsda tulajdonjogának jellege miatt vannak olyan tervezési minták, amelyek egyszerűen nem működnek a Rozsdában. Például, ez nem triviális, hogy végre valamit, mint egy kétszeresen összekapcsolt lista, vagy egy globális változó. Ez valószínűleg idővel intuitívabbá válik, és vannak megoldások ezekre a kérdésekre, de a Rust minden bizonnyal olyan korlátozásokat ró a programozóra, amelyek más nyelveken nincsenek jelen.

bár nem olyan gyakran beszélnek róla, mint a Rustról, a Swiftnek érdekes története van a memóriakezelés terén is.

a Swiftnek két alapvető változótípusa van: referenciatípusok és értéktípusok. Általában a referenciatípusok kupac-allokáltak, és referenciaszámlálással kezelik őket. Ez azt jelenti, hogy futásidőben a rendszer nyomon követi a referencia-megszámlált objektumra mutató hivatkozások számát, és az objektum akkor kerül kiosztásra, amikor a számlálás eléri a nullát. A Swift-ben a referenciaszámlálás mindig atomi: ez azt jelenti, hogy minden alkalommal, amikor a referenciaszám megváltozik, szinkronizálásnak kell lennie az összes CPU-szál között. Ennek az az előnye, hogy kiküszöböli annak lehetőségét, hogy egy hivatkozás tévesen felszabaduljon egy többszálú alkalmazásban, de jelentős teljesítményköltséggel jár, mivel a CPU szinkronizálása nagyon drága.

a Rust rendelkezik referenciaszámláló és atomi referenciaszámláló eszközökkel is, de ezek inkább opt-in, mint alapértelmezett eszközök.

az Értéktípusok ezzel szemben általában stack-allokáltak, és a memóriájuk statikusan van kezelve. Az értéktípusok viselkedése azonban a Swift-ben nagyban különbözik attól, hogy a Rust hogyan kezeli a memóriát. A Swift-ben az értéktípusok alapértelmezés szerint másolják az úgynevezett “copy-on-write” viselkedést, ami azt jelenti, hogy minden alkalommal, amikor egy értéktípust új változóba írnak, vagy átadnak egy függvénynek, másolatot készítenek.

a Másolás az írásra a másolás alapértelmezés szerint ugyanazokat a célokat éri el, mint a kölcsönzés ellenőrzése: programozóként általában soha nem kell aggódnia, hogy egy érték rejtélyes módon megváltozik a Program másutt váratlan mellékhatása miatt. Ez egy kicsit kevesebb kognitív terhelést is igényel, mint a kölcsönzés ellenőrzése, mivel a Rust-ban a tulajdonjoggal kapcsolatos fordítási idejű hibák egész osztályai vannak, amelyek egyszerűen nem léteznek a Swiftben. Ennek azonban ára van: ezek a további másolatok további memóriahasználatot és CPU-ciklusokat igényelnek.

a Rust-ban az értékek másolása is lehetséges, hogy elhallgattassa a kölcsönzési hibákat, de ez vizuális zajt ad, mivel a másolatokat kifejezetten meg kell adni.

tehát itt van egy jó példa a két nyelv kompromisszumaira: a Swift néhány átfogó feltételezést ad arról, hogyan kell kezelni a memóriát, miközben továbbra is fenntartja a biztonsági szintet. Kicsit olyan, mintha egy C++ programozó a legjobb gyakorlatok szerint kezelné a memóriát, mielőtt sokat gondolkodna az optimalizáláson. Ez nagyon egyszerűvé teszi a beugrást és a kód írását anélkül, hogy sokat gondolnánk az alacsony szintű részletekre, és néhány alapvető futásidejű biztonságot és helyességet garantálna, amit nem kapna meg egy olyan nyelven, mint a Python vagy akár a Golang. Azonban ez nem jön néhány teljesítmény sziklák, ami könnyen leesik anélkül, hogy észrevennénk, amíg nem fut a program. Lehetőség van nagy teljesítményű Swift kód írására,de ez gyakran gondos profilalkotást és optimalizálást igényel.

a Rust viszont sok speciális eszközt ad a memória kezelésének megadásához, majd kemény korlátozásokat vezet be a használatukra a nem biztonságos viselkedés elkerülése érdekében. Ez nagyon szép teljesítményjellemzőket ad a dobozból, de megköveteli, hogy vállalja az összes szabály betartásának biztosításának további kognitív költségeit.

ebből azt vettem ki, hogy bár ezeknek a nyelveknek vannak közös céljaik, alapvetően eltérő tulajdonságaik vannak, amelyek különböző használati esetekre adhatók. A Rust például egyértelmű választásnak tűnik a beágyazott fejlesztéshez, ahol a memória és a CPU ciklusok optimális használata rendkívül fontos, és ahol a code-compile-run ciklus lassabb lehet, ezért értékes, hogy fordításkor minden lehetséges problémát elkapjon. Míg a Swift jobb választás lehet az adattudományhoz vagy a kiszolgáló nélküli logikához, ahol a teljesítmény másodlagos aggodalomra ad okot, és érdemes közelebb dolgozni a problématerülethez anélkül, hogy sok alacsony szintű részletet figyelembe kellene venni.

mindenesetre nagyon érdekel, hogy a jövőben mindkét nyelvet kövessem, és ezt a bejegyzést további megfigyelésekkel fogom követni a Swift és a Rust összehasonlításáról.

Vélemény, hozzászólás?

Az e-mail-címet nem tesszük közzé.