

大約兩年前,Fish Shell 的核心維護者 @ridiculousfish 釋出了一個迅速成為社群焦點的拉取請求(Pull Request):#9512 – Rewrite it in Rust。這一提議最初只是為 Fish Shell 開發者們開的一個玩笑,並未打算作為廣為傳播的新聞稿。
然而,它意外地引起了廣泛關注和熱烈討論,成為了 Fish Shell 社群中的一個重要里程碑。隨著 fish 4.0 beta 版本的成功釋出,Fish Shell 完全告別了 C++,幾乎全部程式碼都由 Rust 編寫而成。本文將回顧這段轉型過程中的挑戰、收穫以及未來的展望。
背景:為什麼要用 Rust 重寫 Fish Shell?
Fish Shell 並不是第一次經歷語言轉換——它曾經從純 C 移植到了 C++。但是,這次從 C++ 轉向 Rust 是一個更為雄心勃勃的專案,因為 Rust 在 Fish Shell 初創於 2007 年時甚至還沒有誕生。在使用 C++ 的過程中,Fish Shell 團隊遇到了一系列挑戰:
-
工具鏈和編譯器/平臺差異:C++ 的工具鏈不夠成熟,尤其是在支援老舊系統(如 LTS Linux 和較舊版本的 macOS)方面。由於缺乏類似 rustup 這樣的工具,安裝最新的 C++ 編譯器變得非常複雜,這給打包者和潛在貢獻者帶來了額外的負擔。
-
易用性和安全性:C++ 的語法和特性(如模板、標頭檔案等)雖然強大,但也容易導致編譯錯誤和不安全的操作。特別是多執行緒程式設計,C++ 中的執行緒管理非常棘手,容易引發跨執行緒物件共享的問題,而這些問題往往只能透過事後檢查工具(如 Thread Sanitizer)來發現。
-
社群參與度低:C++ 並沒有吸引到足夠的貢獻者。在過去 11 年中,只有 17 個人對 C++ 程式碼庫有至少 10 次以上的提交。對於一個依賴社群貢獻的開源專案來說,這是一個令人擔憂的現象。
-
效能與易用性的權衡:C++ 強調效能最佳化,但在實際開發中,許多功能(如 string_view)雖然現代且高效,但容易引發使用後釋放(use-after-free)等安全隱患。
轉型中的挑戰與錯誤
在從 C++ 向 Rust 轉型的過程中,Fish Shell 團隊也遇到了一些彎路和錯誤。例如,團隊最初嘗試使用複雜的宏來簡化字串處理,但最終發現這種方法並不實用,因此改用了更傳統的 L!("foo") 宏呼叫。此外,團隊還遇到了一些因誤解原始碼細節而導致的 bug,通常這些 bug 會導致程式崩潰,因為開發者使用了 assert! 或其現代替代品 .unwrap() 來處理錯誤。儘管這些問題是淺層次的,但它們仍然需要團隊花費時間去解決。
另一個值得注意的問題是關於 libc crate 中的 time_t 型別。團隊最初試圖繞過一個關於 time_t 將在未來切換為 64 位的棄用警告,添加了許多包裝器以保持對該型別的無關性。然而,後來他們意識到這個問題並不會影響 Fish Shell,因為他們並沒有在不同 C 庫之間傳遞 time_t。這個問題的解決讓團隊意識到,有時過度最佳化反而會帶來更多複雜性。
此外,團隊在啟用連結時最佳化(LTO)並將其設定為 CMake 預設的釋出構建時,無意中增加了構建時間,給開發者帶來了不便。儘管這些挑戰存在,但團隊最終找到了解決方案,確保了專案的順利推進。
收穫與改進
儘管轉型過程中遇到了不少挑戰,但 Fish Shell 團隊也從中獲得了許多寶貴的收穫。首先,Rust 的工具鏈和生態系統為 Fish Shell 帶來了顯著的好處。團隊不再依賴複雜的 C++ 工具鏈,而是可以透過 rustup 快速安裝和升級編譯器,極大地簡化了開發流程。Rust 的編譯器錯誤資訊也非常友好,幫助開發者更快地定位和解決問題。
其次,團隊成功擺脫了對 curses 庫的依賴。在 C++ 版本中,curses 用於訪問終端資訊(如 terminfo),但這帶來了許多問題,尤其是在不同作業系統上安裝和配置 curses 時。現在,Fish Shell 使用了一個專門的 Rust crate 來處理 terminfo,消除了這些麻煩。使用者不再需要手動安裝 curses,cargo 會自動下載並構建所需的依賴項。此外,Fish Shell 仍然可以讀取 terminfo 檔案,這意味著使用者只需要在執行時安裝這些檔案,或者使用內建的 xterm-256color 定義。
另一個重要的改進是 Fish Shell 現在可以建立“自安裝”包,將所有函式、補全和其他資原始檔嵌入到二進位制檔案中,並在執行時寫入。這使得 Fish Shell 可以生成靜態連結的版本,特別適用於 Linux 系統。透過使用 musl 而不是 glibc,團隊解決了 glibc 在某些情況下不可避免的崩潰問題。這種自包含的二進位制檔案使得使用者可以在沒有 root 許可權的情況下,透過 SSH 登入遠端伺服器並立即使用 Fish Shell,極大地方便了使用者的日常使用。
未竟之事與未來展望
儘管轉型取得了巨大成功,但 Fish Shell 團隊也承認有一些目標尚未實現。例如,團隊原本希望完全擺脫 CMake,但由於 Cargo 在安裝方面的侷限性,CMake 仍然被保留下來,儘管其作用已經大大簡化。Cargo 專注於構建二進位制檔案,而 Fish Shell 需要處理大量的指令碼檔案、文件和測試套件,因此 CMake 仍然是不可或缺的一部分。團隊表示,未來可能會考慮使用其他任務管理工具(如 Just 或 make)來替代 CMake,但這並不是當前的優先事項。
此外,Cygwin 作為 Windows 上的一個重要平臺,目前暫時失去了支援,因為 Rust 尚未提供針對 Cygwin 的目標。團隊希望這種情況能夠在未來得到改善,但在現階段,Windows 使用者仍然可以透過 WSL(Windows Subsystem for Linux)來使用 Fish Shell。
最後成果
經過近兩年的努力,Fish Shell 成功完成了從 C++ 到 Rust 的轉型。這一過程不僅解決了許多長期存在的技術問題,還為專案帶來了新的活力和更多的可能性。Rust 的安全性、易用性和強大的工具鏈使得 Fish Shell 的程式碼庫更加健壯和易於維護。
儘管轉型並非一帆風順,但團隊透過不斷學習和調整,最終實現了預期的目標。未來,Fish Shell 將繼續探索更多創新功能,併為使用者提供更好的體驗。
作者:碼農匠人參考:
https://fishshell.com/blog/rustport/
相關閱讀: