Az interaktív, felhasználóbarát Fish Shell parancssori környezet ünnepek előtti nagy lépéseként debütált a 4.0 béta verziója. Ennek a kiadásnak egyik legfontosabb újdonsága, hogy a C++-ban írt kódot Rust programozási nyelvre portolták.
Miért váltottak Rustra?
Körülbelül két éve a Fish shell vezető fejlesztője, megnyitott egy pull requestet, amely gyorsan az egyik legolvasottabb bejegyzéssé vált a projekt történetében:
#9512 - Rewrite it in Rust (Írd újra Rustban)
Bevalljuk, nem számítottunk rá, hogy ekkora figyelmet kap. Eredetileg egy belső poénnak szántuk a fejlesztői közösség számára, nem pedig egy sajtóhírnek, amit széles körben megosztanak. Mi nem reklámoztuk, mások viszont igen, és rengeteg reakció érkezett. Az éles szemű olvasók gyorsan észrevették, hogy a PR (pull request) a Fish shell teljes C++ kódbázisának Rust nyelvre való átírását javasolta.
Egy régi-új történet: miért váltunk nyelvet?
A Fish fejlesztése során nem ez volt az első nyelvi váltás: korábban már történt egy áttérés a tiszta C-ről C++-ra. Azonban a Rustra való váltás sokkal nagyobb lépést jelentett, hiszen ez egy olyan nyelv, amely 2007-ben, a Fish indulásakor még nem is létezett. Most, hogy kiadtuk a Fish 4.0 béta verzióját, amely már 0% C++-t és közel 100% tiszta Rust-ot tartalmaz, érdemes visszatekinteni: mit tanultunk, mi működött jól, mi kevésbé, és hogyan tovább?
Miért hagytuk el a C++-t?
A C++-szal kapcsolatban az évek során egyre több problémába ütköztünk. Ezek közül a legfontosabbak:
- Eszközök és platformok közötti eltérések
- Biztonság és ergonómia (különösen szálkezelésnél)
- Közösségi támogatás
1. Eszközök és fordítók okozta gondok
A C++ fejlesztési környezete nehézkes, különösen akkor, ha a különböző rendszerek közötti kompatibilitás is szempont. Célunk, hogy a Fish up-to-date csomagjai elérhetők legyenek régebbi rendszereken, például LTS Linuxon vagy macOS korábbi verzióin.
Azonban nincs a C++ számára egy „rustup”-hoz hasonló eszköz, amely egységes módon biztosítaná a friss fordítókat. Ez azt jelenti, hogy a C++ újabb szabványainak használata megnehezíti a csomagkezelők és közreműködők életét. Például a C++11 szabványt már 2016-ban elkezdtük használni, de egészen 2020-ig frissítenünk kellett a build szervereink fordítóit.
2. Szálkezelés és ergonómia
A Fish szálkezelést használ a kiemelt funkcióihoz, például az automatikus kiegészítésekhez és a szintaxiskiemeléshez. Ugyanakkor egy hosszú távú célunk a belső parancsok párhuzamos futtatása.
Jelenleg a Fish beépített parancsai és funkciói szekvenciálisan hajtódnak végre, és nem futhatnak háttérben. Ennek feloldása lehetővé tenné például az aszinkron promptokat vagy a nem blokkoló automatikus kiegészítéseket, valamint teljesítménynövekedést is hozna.
A POSIX shell-ek szubshell-ekkel oldják meg ezt, de ez egy hibás absztrakció, ami számos problémát okozhat. Például egy csővezeték (pipe) belsejében nem lehet változókat állítani – legalábbis nem minden shell-ben.
3. Közösségi részvétel
A C++ nem vonzotta be a közreműködőket. A 11 év alatt mindössze 17 ember érte el a 10 commitot a C++ kódbázisban. Ráadásul a C++ kódok karbantartása és fejlesztése nem szívesen választott hobbi a fejlesztők körében.
Miért Rust?
Egyszerűen fogalmazva: a Rust szórakoztató és modern.
A Fish egy szabadidős projekt, ezért fontos, hogy a fejlesztők élvezzék, amit csinálnak. A Rust ezen kívül remek eszközöket kínál: a fordító hibajelzései nemcsak a C++-hoz képest, hanem abszolút értelemben is kiválóak.
A rustup telepítése és használata minimális erőfeszítést igényel, így egyszerűvé teszi az indulást, szemben a C++ fordítók frissítésének sokszor bonyolult folyamatával.
A Rust legnagyobb előnye azonban a Send és Sync típusok, amelyek statikusan garantálják a szálkezelés szabályait. Bár a „félelem nélküli párhuzamosság” túlzás, a Send és Sync kulcsfontosságú a teljesen párhuzamos végrehajtás helyes működéséhez.
Hogyan zajlott a portolás?
A Fish átdolgozását a „Thészeusz halai” megközelítés szerint végeztük el: az összetevőket egyenként helyettesítettük Rust-alapú megoldásokkal, miközben a shell végig működőképes maradt.
Az autocxx eszközt használtuk a C++ és Rust kódok közötti hidak generálására. Először a beépített parancsokat (builtins) portoltuk, mivel ezek önálló kis programként viszonylag könnyen átírhatók voltak.
Amikor összetettebb rendszerekhez értünk – például az input/output reader-hez, amely több mint 13 000 soros Rust kóddá vált –, egyéni projektekként oldottuk meg a feladatot.
Tapasztalatok: mi ment jól és mi nem?
Problémák a Rusttal
- Platformfüggetlenség: A Rust bizonyos rendszereken való adaptálása (pl. FreeBSD különböző verziói) nehézkes lehet, mivel manuálisan kell meghatározni a támogatott célplatformokat. Egy „has_fn” jelölés (például: #[cfg(has_fn = "fstatat")]) nagyban megkönnyítené a munkát.
- Hosszú build idők: A Rust link-idő-optimalizációja és a tesztelési beállítások miatt néha váratlanul hosszú fordítási idők léptek fel.
Előnyök
A Rustra való áttérés számos előnyt hozott:
- Nincs több curses-probléma: A Fish már nem használja az (n)curses könyvtárat, helyette egy Rust-alapú megoldást alkalmaz. Ez eltávolította a globális állapotok okozta bonyodalmakat, és egyszerűsítette a forráskód fordítását.
- Kódergonómia és hibakezelés: A Rust tisztább szintaxisa és erősebb típusellenőrzése segített elkerülni a C++-ban gyakori memóriabiztonsági problémákat.
Következtetés
A Fish Rust alapokra helyezése nem volt egyszerű feladat, de megérte. Bár voltak nehézségek és tanulságok, az eredmény egy modernebb, biztonságosabb és könnyebben karbantartható kódbázis. A Fish továbbra is egy hobbi projekt, de a Rust-alapú fejlesztés új lendületet adott a közösségnek, és hosszú távon is fenntarthatóbbá tette a shellt.
A Fish Shell 4.0 béta már elérhető tesztelésre, így a fejlesztők és lelkes felhasználók kipróbálhatják a legújabb funkciókat és fejlesztéseket. A végleges verzió a bétatesztelési fázis után várható.