來源:InfoQ
2023 年,我們有一千個學習 Rust 的理由。
8 月7 日,Rust 基金會發布了2022 年度Rust 調查報告結果,報告顯示Rust 採用率不斷提高,超過90% 的調查受訪者表示自己是Rust 用戶;29.7% 的受訪者表示,他們在工作中的大部分編碼工作都使用Rust,比上一年顯著增加了51.8%。
毋庸置疑,Rust 以其卓越的內存安全性和並發性能正日益成為開發者關注的焦點。然而,同樣令人難以忽視的是 Go,這門曾被評選為年度編程語言的相對比較“老牌”的選手。
Go 語言誕生於 2009 年,一開始就因其獨特的並發模型和強大的性能優勢而受到了極大關注。值得注意的是,跟 Rust 語言一樣,Go 語言的創建者也同樣“討厭”C++,並且 Go 同樣也都是雲原生的主導語言。
而在Stack Overflow 2022 開發者調查中,對於“讓人愛恨交織的編程語言”這個問題,在7 萬份回復中,程序員們明顯也更為偏愛Rust,86% 的人表示喜歡Rust,而64% 的人表示喜歡Go。面對 Rust 的火爆現狀,一些開發者發出了靈魂提問:2023 年,Go 還值得學習嗎?
另外,這兩天,到底是該選 Rust 還是選 Go,也成為了 Hacker News 上的一個熱門話題:
一位挺Rust 的網友說道:“我也為這個選擇煩惱了很久。最終Rust 勝出了。首先,我感覺Rust 更接近於以前Pascal 時代的東西,你可以控制一切;其次,如果wasm 和相關技術大爆發,Rust 將是一個更安全的選擇;然後,我們已經有了Python 用於快速開發,因此選擇一些更極端的東西是有道理的,Go 在某種程度上處於中間地帶。最後, Rust 應用於內核且備受關注,所以不太可能會被淘汰。”
另一位持反對意見的開發者則表示,“我從事Go 開發已經快十年了,但最近我也嘗試了下Rust。我認為目前有一些對Rust 的強制性和誤導性偏好,從我在各種初創公司的經驗,包括我目前所在的公司來看,對於後端開發來說,Go 是迄今為止最佳選擇!注意,在性能、功能或其他方面……這兩種語言非常非常相似!”
不得不說的是,Go 和 Rust 絕對都是優秀的編程語言。它們現代、強大、應用廣泛,而且有著卓越的性能表現。但如果直接對比 Go 和 Rust 誰更好之類的,真的沒啥意義,因為每種編程語言都代表著背後一系列深層次的權衡。不同的語言會針對不同的需求進行優化,因此我們在選擇語言時,也應該考慮自己想要用它解決什麼樣的問題。所以我們將從 Go 和 Rust 語言的適用場景出發,探討下 Go 與 Rust 的設計之“道”。
雖然 Rust 和 Go 在語法和風格上差別很大,但它們都是構建軟件的一流工具。下面咱們開始具體分析。
Go 與 Rust:相似性
Rust 和 Go 有很多共同點,所以人們才經常把二者拿來相提並論。那它們有哪些共同目標?
Rust 是一種低級靜態類型的多範式編程語言,更多關注安全性和性能。
—Gints Dreimanis
而:
Go 是一種開源編程語言,能夠輕鬆構建起簡單、可靠且高效的軟件。
—golang.org
內存安全
Go 和 Rust 都屬於重視內存安全的現代編程語言。在 C 和 C++ 等舊語言發展的這幾十年間,我們已經清楚地意識到,引發錯誤和 bug 的核心原因之一,就是對內存的不安全/不正確訪問。
於是 Rust 和 Go 各自給出了不同的解決思路,但二者的目標都是在內存管理方面更智能、更安全,幫助開發者編寫出正確且性能極佳的程序。
快速、緊湊的可執行文件
二者都屬於編譯語言,也就是說可以將程序直接翻譯成可執行的機器代碼,這樣就能把程序部署成單一二進製文件。跟 Python 和 Ruby 等解釋性語言不同,我們不需要隨程序一同發布解釋器和大量的庫/依賴項。作為這個核心優勢的直接體現,Rust 和 Go 程序的運行速度往往比解釋性語言更快。
通用型語言
兩者都擁有優秀的標準庫和蓬勃發展的第三方生態系統,外加強大的商業支持與龐大的用戶基礎。 二者已經存在多年,並將在未來幾年繼續保持旺盛的發展勢頭。如今,學習 Go 或者 Rust 將是非常合理的時間和精力投入方向。
務實的編程風格
但如果您確實喜歡函數式編程風格,那 Rust 這邊的相關工具選項更多,這也是 Rust 優於 Go 的一點。
我們當然可以爭論什麼才是真正“面向對象”的語言。但公平地講,C++、Java 或者 C# 用戶所期望的那種面向對象編程風格,在 Go 或者 Rust 中確實不存在。
—Jack Mott
大規模開發
例如,C 程序員多年來一直在爭論應該把括號放在哪裡,還有代碼要不要用製表符或空格進行縮進;但Rust 和Go 早已使用標準格式化工具(Go 有gofmt, Rust 則是rustfmt)徹底解決了這些問題。它們會使用符合規範的風格自動重寫你的代碼。
並不是說這種特定的格式有多精妙,而是 Rust 和 Go 程序員更加務實、寧願選擇統一的執行標準。
gofmt 的風格也許不是每個人的最愛,但 gofmt 卻能幫到每一個人。
—Rob Pike
這兩種語言的另一大優勢,體現在構建管線上。二者都有優秀、內置且性能出色的標準構建與依賴項管理工具。就是說程序員不必跟複雜的第三方構建系統對抗,也用不著每隔幾年就學習一種新系統。
我在職業生涯早期用的是 Java 和 Ruby,所以編寫 Go 和 Rust 代碼一直讓我有點畏懼、覺得自己掌握不了。但等到進入谷歌並看到用 Go 編寫的服務時,我才真正鬆了口氣,因為我發現它很容易構建和運行。
Rust 也是如此。雖然我只在小規模項目上進行過研究,但也看得出它的易用性。我希望那些能夠無限配置的構建系統早點成為歷史,現在的新語言都附帶自己的專用構建工具而且能夠開箱即用,這樣不好嗎?
—Sam Rose
到底選 Rust 還是 Go?
聊了這麼多問題,再加上兩種語言都設計得如此精良、功能如此強大,那這場比拼到底有沒有結果?或者說,既然二者都是非常出色的選項,那為什麼人們還會在社交媒體上出離憤怒,撰寫長篇累牘的評論博文放出“白痴才用Rust”或者“Go 根本不能算編程語言”之類的狠話?
有些人當然只是為了宣洩情緒,但這顯然無助於解決實際問題。至少在項目中該用哪種語言、或者該靠哪種語言闖蕩編程世界這種事上,嗓門大顯然無助於做出正確選擇。
下面咱們回到成年人的討論,看看理性分析之下 Rust 和 Go 之間如何互有長短。
Go 對 Rust:性能
但 Rust 的性能還是要更勝一籌,甚至能夠與被稱為業界性能標杆的 C 和 C++ 相媲美。而且跟這些老牌語言不同的是,Rust 還提供內存安全與並發安全機制,同時幾乎不影響執行速度。 Rust 還允許開發者構建複雜抽象,又無需在運行時承受性能損失。
相比之下,雖然 Go 程序的性能也不錯,但其設計重心主要在於開發速度(包括編譯)、而非執行程度。 Go 程序員更傾向於代碼的清晰可讀,所以運行速度要稍遜幾分。
Go 編譯器也不會花費太多時間來生成最高效的機器碼,它更關心如何快速編譯大量代碼。 所以在運行時基準測試中,往往是 Rust 程序要壓 Go 程序一頭。
Rust 的運行時性能還具有良好的一致性和可預測性,因為它沒有使用垃圾收集。 Go 的垃圾收集器非常高效,而且做了優化以盡可能縮短暫停時長(隨著 Go 新版本的發布,暫停時長也是越來越短)。但無論如何,垃圾收集總會給程序的行為方式帶來一些不可預測性,而這對某些特定應用(比如嵌入式系統)而言可能很嚴重、甚至完全不可接受。
因為 Rust 的目標是讓程序員完全控制底層硬件,所以 Rust 程序都能深度優化以接近機器的最大理論性能。 如此一來,Rust 就在執行速度勝過其他一切的特定應用場景下成為最佳選項,此類用例包括遊戲編程、操作系統內核、網絡瀏覽器組件和實時控制系統等。
-
簡單性
如果一種編程語言過於難學、把大多數人都擋在了門外,那它的性能再強也沒有意義。 Go 在設計上似乎就是刻意要跟 C++ 等複雜度不斷提升的語言區分開來:它語法極少,關鍵字也極少,就連功能都不多。
這意味著 Go 語言很容易上手,稍微了解之後就能用它編寫出各種程序。
Go 確實非常容易學習。之前就經常聽人提到這一點,但實際使用後我仍驚訝於它竟能快速提高工作效率。感謝 Go 語言、相關文檔和工具,我只用了短短兩天就編寫出了有趣且可以提交的代碼。
—Rust 程序員對於 Go 語言的早期印象
這裡的重點就是“簡單性”三個字。當然,簡單並不代表容易。可一門小而簡單的語言,學起來肯定要比大而復雜的語言要輕鬆。實現一種效果的方法並不多,所以高質量的 Go 代碼看起來幾乎都是一個樣。這還帶來另一個好處:我們可以快速理解某項自己不熟悉的服務到底在做什麼。
Go 的核心本體雖然很小,但標準庫卻非常強大。 也就是說,除了 Go 語法之外,我們的學習曲線還必須考慮到標準庫這個部分。
另一方面,把功能從語言轉移到標準庫,意味著大家只需要專注學習跟當前開發需求相關的庫。 Go 在設計上也充分考慮到大規模軟件開發需求,能夠有力支持大型代碼庫和開發團隊。 在這類場景下,新加入的開發者必須能夠快速上手。為此,Go 社區一直將程序的簡單、明確、通用和直接放在首位。
使用 GO 語言,初級開發者往往更容易提高工作效率,但中級開發者則更難引入複雜的抽象並因此導致問題。正因為這種特性,在企業軟件開發領域,Rust 的吸引力往往不及 Go。
— Loris Cro
-
功能
Rust 比其他幾種編程語言支持更多複雜性,所以對應的實現範疇也更大些。
— Devathon
Rust 經過專門設計,包含多種強大且有用的功能,可以幫助程序員用最少的代碼完成更多任務。 例如,Rust 的匹配功能就可快速編寫出靈活且富有表達力的邏輯:
所以 Rust 當然有自己的學習曲線。但只要跨過了這道難關,後面就是一馬平川了。
如果您已經準備好學習更複雜的語法和語義(以及更高的代碼可讀性門檻),並以此換取最高水平的性能表現,那 Rust 甚至足以跟 C++ 和 D 分庭抗禮。 — Dave Cheney
Rust 和 Go 之間雖然彼此借鑒了一些功能(比如說泛型),但公平地講,Rust 的功能還是更勝一籌,Go 的功能相對要匱乏一點。
-
並發性
大多數語言都為並發編程(即同時執行多項操作)提供某種形式的支持,但 Go 則是從頭開始就為此而設計。 Go 不使用操作系統線程,而是提供一種輕量化的替代方案:goroutines。 每個 goroutine 都是個獨立執行的 Go 函數,Go 調度程序會將其映射至控制下的操作系統線程之一。也就是說,調度程序可以非常高效地管理大量並發 goroutine,且只須使用有限數量的操作系統線程。
因此,我們可以在單個程序中運行數百萬個並發 goroutine,又不必擔心引發嚴重的性能問題。正因為如此,Go 成為 Web 服務器和微服務等大規模並發應用場景下的完全解決方案。
Go 還為 goroutines 提供 channels,這是一種快速、安全、高效實現數據通信和共享的方法。 Go 的並發設計水平確實很高,使用體驗也相當輕鬆愉快。
一般來說,並發程序的設計難度很大,在任何語言中構建起可靠且正確的並發程序都絕非易事。但由於在立項之初就考慮到這方面需求,所以 Go 中的並發編程機制已經做得盡可能簡單且得到良好整合。
Go 讓我們能更輕鬆地構建起一個能精心解構的應用程序,這樣的應用程序可以作為一組微服務進行部署,並充分發揮並發性優勢。 Rust 也不是做不到,只是實現起來更難一些。從某種意義上講,Rust 更適合那些絕不允許因內存問題而引發安全漏洞的程序員;但相應的,他們在執行某些對其他語言(包括GO)來說較為簡單的任務時,就得付出更多心力。 —Sonya Koptyev
相比之下,Rust 中的並發機制剛剛落地、還沒有最終穩定,所以歡迎大家繼續關注這個活躍的開發方向。這樣也有好處,比如 Rust 的 rayon 庫就提供一種非常優雅且輕量級的方法,能夠將順序計算轉換為並行計算。
能有用於生成 goroutine 和使用 channels 的輕量級語法真的太棒了。這就是語法之力的直接體現,種種小細節也讓 Go 的並發編程體驗比其他語言好出一大截。
—Rust 程序員對 Go 的早期印象
雖然在 Rust 中實現並發程序可能不太容易,但仍然完全可行,而且這些程序還能獲得 Rust 精心設計的內存安全保障。 以標準庫的 Mutex 類為例:在 Go 當中,我們可能會在訪問某些內容前忘記獲取互斥鎖;但在 Rust 這邊則完全不需要擔心。
Go 專注於把並發作為最核心的概念之一。這倒不是說我們就沒法在 Rust 中實現跟 Go 類似的並發性效果,只是實現難度對於程序員多少是種考驗。 Go 專注於把並發作為最核心的概念之一。這倒不是說我們就沒法在 Rust 中實現跟 Go 類似的並發性效果,只是實現難度對於程序員多少是種考驗。
——Dave Cheney
-
安全性
前文已經提到,Go 和 Rust 都會以各自的方式防止各種常見的編程錯誤,特別是跟內存管理相關的問題。但 Rust 走得更遠,可以說是不遺餘力地保證大家不致搞出意料之外的安全紕漏。
Rust 的編譯器簡直是嚴格到迂腐,它會檢查我們使用的每個變量、引用的每個內存地址。它避免了潛在的數據競爭情況,還會通知你存在未定義行為。在 Rust 的世界中,並發和內存安全問題幾乎不可能出現。
—為什麼選擇 Rust?
也就是說,Rust 的編程體驗跟幾乎所有其他語言都有所不同,而且在剛剛接觸時可能相當具有挑戰。但在不少開發者看來,這份付出顯然物有所值。
對我來說,Rust 最大的優勢就是編譯器成了我的好助手,它不會放過任何檢測得到的 bug(說真的,有時候我感覺它就像會魔法)。
—Grzegorz Nosek
包括 Go 在內,很多語言也提供幫助程序員避免錯誤的工具,但 Rust 把這種效果提升到了新的水平。很多不正確的程序甚至根本沒辦法編譯。
在 Rust 中,各種庫工具都能幫助程序員防止用戶犯錯。 Rust 允許我們指定一段數據,然後保證它不歸屬於任何其他事物、也不會被任何其他事物所篡改。我想不起以往還有哪種語言會提供這麼多防止意外誤用的工具,這種感覺堪稱美妙。 — Sam Rose
“與借用檢查器作鬥爭”是 Rust 新人們必須要過的一關,但在大多數情況下,檢查器並不是真正的敵人。它發現的問題確實是代碼中的真實 bug(或者至少是潛在 bug)。 它可能迫使我們從根本上重構自己的程序來避免此類問題——如果各位確實把正確性和可靠性當作首要任務,那這種嚴格要求顯然是件好事。
換個角度想,不改變編程方式的新語言,能叫新語言嗎?而且在使用其他語言時,Rust 教會我們的安全思維同樣意義重大。
如果大家選擇了 Rust,往往是因為要使用它提供的保障性設計:關於空指針 / 數據競爭的安全性、可預測的運行時行為,還有對硬件的完全控制。如果這些對你來說毫無意義,那確實沒必要非得使用 Rust。畢竟這些好處背後是有代價的:上手很費勁。你得改掉壞習慣並掌握新概念。剛開始的時候,大家都會被借用檢查器折磨得死去活來。
— Matthias Endler
上手 Rust 編程模型的實際難度,可能取決於大家之前用過哪些其他語言。 Python 或者 Ruby 程序員可能覺得 Rust 限制太多,但其他人可能覺得這種清晰明確的約束也不錯。
如果你是一名 C 或者 C++ 程序員,曾經花幾個禮拜在語言中查找內存安全 bug,那你一定會愛上 Rust。於是“跟借用檢查器作鬥爭”就變成了“編譯器還能這麼用?爽!”
— Grzegorz Nosek
-
規模化
如今的服務器程序包含著數千萬行代碼,由成百上千名程序員編寫而成,並且幾乎每天都在更新。 Go 在設計和開發上,充分考慮到了此類環境下的工作效率提升需求。 Go 的設計考量因素包括嚴格的依賴項管理、軟件架構隨系統增長的適應性,還有跨組件邊界的健壯性。 — Rob Pike
當大家獨自或者在小團隊中解決問題時,要選簡單的語言還是豐富的語言純屬個人喜好。但隨著軟件規模的擴大、複雜度的提升、團隊的膨脹,兩類語言之間的差異才開始真正顯現出來。
對於大型應用程序和分佈式系統,代碼執行速度的重要性往往低於開發速度:像Go 這種刻意強調精簡設計的語言能夠縮短開發新手的適應時間,也讓他們能更快參與到大型代碼庫的貢獻當中。
使用 GO 語言,初級開發者往往更容易提高工作效率,但中級開發者則更難引入複雜的抽象並因此導致問題。正因為這種特性,在企業軟件開發領域,Rust 的吸引力往往不及 Go。
— Loris Cro
在涉及大規模軟件開發時,明確易讀總是比精巧優雅更重要。 Go 的局限性實際使其比 Rust 等更複雜、更強大的語言,要更適應企業和大型組織的需求。
Rust 與 Go:差異之處
雖然 Rust 和 Go 都是高人氣且得到廣泛應用的現代語言,但二者間並不是真正的競爭對手,因為它們所面向的用例可以說完全不同。
Go 的整個編程方法就跟 Rust 完全不同,這些特性一方面特別適合某些人,但另一方面也會徹底激怒某些人。這很正常,因為如果 Rust 和 Go 都在以基本相似的方式解決基本相同的問題,那我們幹嘛還需要兩種獨立的語言?
那麼,我們能不能從 Rust 和 Go 採取的方法入手,解讀它們各自的本質呢?下面就一起來試試。
-
垃圾收集
“要垃圾收集,還是不要垃圾收集”永遠是個沒有正確答案的問題。一般來說,垃圾收集和自動內存管理能幫助我們快速、輕鬆地開發出可靠且高效的程序。所以對某些開發者來說,這些都是必不可少的功能。
但也有人認為,垃圾收集和它帶來的性能開銷與全局暫停,會導致程序在運行時的行為變得不可預測,同時引入不可接受的延遲。這話當然也有道理。
Go 跟 Rust 這兩種語言可以說截然不同。儘管二者都可以被簡單描述成系統語言或者 C 的替代品,但它們的目標和應用場景、語言設計風格與功能優先級確實差異巨大。垃圾收集就是一大核心差異因素。 Go 中的垃圾收集讓語言變得更簡單、更小巧也更易於理解。 Rust 不設垃圾收集則讓它速度極快(這一點特別適合那些不僅要求高吞吐量、更要求低延遲的開發者),同時也實現了Go 根本不可能做到的一系列功能與編程模式(至少是在不犧牲性能的前提下)。
— PingCAP
-
貼近硬件
計算機編程的發展史,可以說是一段日益複雜的抽象發展歷程。它讓程序員們既能解決問題,又不用太多關注底層硬件的實際運行方式。這種設計讓程序更易於編寫、更具可移植性。但對於其他一些程序來說,訪問硬件及精確控製程序的執行方式反而更加重要。
Rust 的目標就是讓程序員能“貼近硬件”,奪回更多控制權;而 Go 則抽象掉了架構細節,讓程序員更貼近問題。
兩種語言各有不同的應用範圍。 Go 擅長編寫微服務和典型的“DevOps”任務,但它並不屬於系統編程語言。 Rust 在強調並發性、安全性及 / 或性能的任務中更為強大,可學習曲線也確實比 Go 更陡峭。
— Matthias Endler
-
性能為先
其實對大多數程序來說,性能的重要性是不及代碼可讀性的。但如果某些項目確實是以性能為先,那 Rust 中的很多設計權衡,將幫助大家把代碼的執行速度一路推向極限。
相比之下,Go 更關心代碼簡單性,甚至願意為此犧牲一些運行時性能。但 Go 的構建速度無與倫比,這對大規模代碼項目來說往往更加重要。
Rust 的執行速度優於 Go。在基準測試中,Rust 速度確實更快,某些情況下甚至能快出一個數量級。但在選擇 Rust 語言之前,請先認清一點:Go 在多數基準測試中也沒有落後太多,而且也仍然保持對 Java、C#、JavaScript 和 Python 等語言的性能優勢。
如果你需要的是頂級性能,那麼在這兩種語言中任意選擇都可以,速度表現絕不會令人失望。另外,如果你正在構建一款處理高強度負載的 Web 服務,而且要求能夠縱向 / 橫向靈活擴展,兩款語言也都能滿足需求。
— Andrew Lader
-
正確性
另一方面,如果不強求程序永不出錯,那取捨以會不同。大多數代碼都不會考慮到長期使用,但某些程序也確實在生產環境中多年運行。面對這些現實情況,也許我們有必要投入一點額外的時間,來開發並保證程序能夠正確、可靠運行,且未來不致引發沉重的維護負擔。
Go 和 Rust 都能幫助大家編寫出正確的程序,只是具體方式各有不同:Go 提供出色的內置測試框架,而 Rust 則專注通過借用檢查器消除運行時 bug。
我的看法是:對於明天就得發布的代碼,用 Go;如果是未來五年內必須能穩定運行的代碼,那麼選 Rust。
— Grzegorz Nosek
雖然 Go 和 Rust 都足以支撐起嚴肅的開發項目,但大家最好還是能充分了解它們的各種特性和優勢。總之,別人的想法並不重要:只有你自己能決定哪種編程語言更適合你的團隊及項目需求。
如果你想加快開發速度,比如說你有很多不同服務需要編寫,或者開發團隊本身規模龐大,那麼 Go 語言肯定是正確答案。 Go 特別關注並發性設計,而且會敏銳地揪出不安全的內存訪問行為(Rust 也可以),但又不強迫你逐一管理每處細節。
Go 快速而強大,但它的核心亮點還是幫助開發人員擺脫困境、專注於簡單性和統一性。在另一方面,如果你需要竭盡全力發揮每一絲性能空間,那 Rust 才是最理想的選擇。
—Andrew Lader
總結希望這篇文章能幫助大家理解 Rust 和 Go 的各自亮點。如果可能的話,各位最好能多少體驗一下這兩種語言,因為它們在任何技術道路上都非常有用,哪怕對業餘編程愛好者也是如此。
但如果您的時間只搞認真鑽研一門語言,那請千萬先把 Go 和 Rust 各自的專長和傾向性搞清楚,之後再做選擇。
當然,關於編程語言的知識只是成就一名成功軟件工程師的很少一部分。除了編程之外,工程師們還得精通設計、工程、架構、溝通和協作。只要大家能把後面這幾樣做好,那無論你選擇哪種編程語言,都將成為一名出色的軟件工程大牛。