編譯 | 言征
去年,Web開發(fā)公司MAInmatter對Web版 Rust 進(jìn)行了戰(zhàn)略押注,并發(fā)起了 EuroRust 會議,加入了 Rust 基金會,同時正在內(nèi)部以及開源領(lǐng)域從事許多 Rust 項(xiàng)目。
Mainmatter非常樂觀地認(rèn)為 Rust 將在未來幾個月和幾年內(nèi)在Web和云空間中起飛,并認(rèn)為Rust 是邁向 Web 開發(fā)新時代的第一步,開發(fā)人員可以利用這項(xiàng)技術(shù),在不放棄開發(fā)人員經(jīng)驗(yàn)和生產(chǎn)力的情況下,達(dá)到更高的、以前難以想象的效率、穩(wěn)定性、可靠性和可維護(hù)性水平。
這篇文章意在分享為什么Mainmatter有信心作這一押注,以及為什么我們相信 Rust 在Web和云領(lǐng)域擁有美好的未來。
一、大廠偏愛,Rust的未來
Rust 自從大約十年前登臺以來,就受到了很多開發(fā)人員的關(guān)注和喜愛。不僅開發(fā)人員喜歡這門語言,大公司的決策者也認(rèn)為 Rust 是一項(xiàng)偉大的技術(shù),并且在過去幾年里,該語言在整個行業(yè)得到了廣泛采用。
AWS在其平臺上大量使用 Rust ,google在 Android 中使用它,Microsoft在 windows 中使用它。從本質(zhì)上講,Rust 有望在以前使用的許多領(lǐng)域取代 C 和 C++:系統(tǒng)編程、操作系統(tǒng)、各種嵌入式系統(tǒng)、低級工具以及游戲和游戲引擎。
當(dāng)然,除了以上這些,未來具有更大潛力的領(lǐng)域是Web和云。Rust給這兩個領(lǐng)域帶來了無限想象的后端提升空間。一旦后端的開發(fā)提升到一個新的水平,就能讓團(tuán)隊(duì)能夠訪問以前無法實(shí)現(xiàn)的功能。
盡管 Rust 還很年輕,但已經(jīng)看到很多公司在Web和云中成功使用 Rust,比如:Truelayer、Discord、Temporal、Nando's、svix、Wingback等等。
值得一提的是,谷歌多年來也一直大力采用 Rust,最近表示,與他們使用的任何其他語言相比,他們并沒有真正看到 Rust 的生產(chǎn)力損失。
二、Rust做Web的雄心
雖然相對年輕,畢竟距離1.0 發(fā)布,Rust的生態(tài)系統(tǒng)也只走過了 8 年。但Rust以及其Web生態(tài)已經(jīng)達(dá)到了一定的成熟度,足夠使其成為構(gòu)建真實(shí)應(yīng)用程序的可行選擇。
正如arewewebyet.org所證實(shí)的,Rust 顯然已經(jīng)為Web做好了準(zhǔn)備:
首先,有tokio,一個異步運(yùn)行時,它是Web應(yīng)用程序的堅(jiān)實(shí)且高性能的基礎(chǔ);其次,最重要的是,Rust已經(jīng)有了成熟且維護(hù)良好的 Web 框架,例如axum和actix-web。所有相關(guān)數(shù)據(jù)存儲以及 ORM 都有成熟的驅(qū)動程序;最后,可以找到涵蓋構(gòu)建 Web 應(yīng)用程序的所有其他相關(guān)方面的庫,例如(反)序列化、國際化、模板化、可觀察性等。
總的來說,Rust的雄心勃勃,構(gòu)建 Web 后端提供了堅(jiān)實(shí)而穩(wěn)定的構(gòu)建塊。
三、有必要換Rust做Web?
當(dāng)然,有人可能會問:我為什么要換Rust?對于已經(jīng)使用 Ruby、JAVA、Elixir、TypeScript、Go 或其他任何語言的團(tuán)隊(duì)而言,換 Rust有哪些好處嗎?
有兩個關(guān)鍵方面使 Rust 成為 Web 構(gòu)建的絕佳選擇:一是它的效率和性能;二是其類型系統(tǒng)帶來的可靠性和可維護(hù)性帶來的好處。
1.效率和性能
Rust 以其高效和高性能而聞名。它將輕松超越 Web 應(yīng)用程序常用的 JavaScript、Ruby、Python/ target=_blank class=infotextkey>Python 等語言幾個數(shù)量級。其他語言可能具有更高的性能上限(例如 Java 或 C# 或 Go),但你需要投入大量的工程精力才能接近 Rust 工具包開箱即用提供的性能水平。
此外,Rust 還有一個關(guān)鍵優(yōu)勢:它不捆綁垃圾收集器。垃圾收集語言可以很快,但它們不能始終一致性地表現(xiàn)出色。垃圾收集器將引入暫停(pause)以釋放未使用的內(nèi)存,從而對應(yīng)用程序的尾部延遲產(chǎn)生負(fù)面影響。而Rust 不存在這個問題:它可以提供一致的性能,而不會出現(xiàn)這些峰值。
C 和 C++ 是唯一能夠?qū)崿F(xiàn)如此穩(wěn)定和一致性能的其他語言。不幸的是,這兩種語言往往搬起石頭砸自己腳,處處是陷阱,特別是在手動內(nèi)存管理時。正如 linux 的創(chuàng)始人 Linus Torvalds 所說:
“它離硬件太近了,你可以用它做任何事情。這很危險。就像玩雜耍電鋸一樣。我還發(fā)現(xiàn)它確實(shí)有很多陷阱,而且很容易被忽視。
由于 C 和 C++ 的這些危險,除了這兩種語言的專家或擁有專家團(tuán)隊(duì)時才能使用。否則,你得到的就是一個不穩(wěn)定且充滿安全漏洞的系統(tǒng)。
同時,別忘了,在 Web 領(lǐng)域,很少人具備這種專業(yè)知識,因?yàn)槊總€人大多都使用非常不同的語言,如 JavaScript、Python、Ruby、Elixir 等。反而Rust 就不會遇到同樣的陷阱,使開發(fā)人員能夠以以前的效率水平構(gòu)建軟件。
Rust 通常會比用于構(gòu)建 Web 后端的其他技術(shù)的性能好幾個數(shù)量級,同時保持顯著較低的內(nèi)存占用。
當(dāng)然,如果與其他技術(shù)相比, Rust Web 服務(wù)器可以在一小部分時間內(nèi)響應(yīng)請求,這也意味著它可以用更少的服務(wù)器響應(yīng)相同數(shù)量的請求,這又意味著更少的托管成本。
這對于中小型產(chǎn)品和公司來說,減少托管的云服務(wù)器數(shù)量,就意味每月就可以輕松減少不菲的費(fèi)用。
我們的 Python 服務(wù)平均約為 50 個請求/秒,NodeJS 約為 100 個請求/秒,Rust 約為 690 個請求/秒。我們可以在通常托管單個 Python 服務(wù)的 k8 EKS 節(jié)點(diǎn)上安裝 4 個 Rust 服務(wù)。——Reddit某用戶
然而,成本節(jié)省還只是好處之一,使用更少的服務(wù)器也意味著使用更少的能源。盡管使用可再生能源運(yùn)行數(shù)據(jù)中心固然很好,但最綠色的能源仍然是我們不使用的能源。Rust 或許不能解決氣候危機(jī),但這里要承認(rèn)的是,運(yùn)行我們編寫的軟件,也會真真切切地消耗資源,從而對現(xiàn)實(shí)世界產(chǎn)生影響。軟件行業(yè)往往會忘記這一點(diǎn)——如果我們能夠更有效地利用資源,并以更少的投入獲得相同的產(chǎn)出,這是選擇技術(shù)時的一個重要考慮。
2.可靠性和可維護(hù)性
雖然性能和效率很重要,但在許多情況下,可能更相關(guān)的原因則是 Rust 的強(qiáng)類型系統(tǒng)所帶來的可靠性和可維護(hù)性收益。
像這樣的代碼片段對于 Web 應(yīng)用程序(Ruby on Rails)來說是相當(dāng)?shù)湫偷模?/p>
復(fù)制
class User
attr :name
attr :active
attr :activation_date
def activate(activation_date)
self.active = true
self.activation_date = activation_date
save
end
def save
…
end
end
…
user.activate(Time.now)
雖然這段代碼非常簡潔且易于閱讀,并且編寫這樣的代碼可以讓你快速實(shí)現(xiàn)目標(biāo),但也存在問題。在這個的示例中,雖然我們可以看到用戶的屬性,但我們不知道這些屬性周圍可能有什么規(guī)則(例如,如果active是true,則activation_date可能也必須設(shè)置?如果active是false,則大概activation_date應(yīng)該是nil?)。為了驗(yàn)證這些假設(shè),我們必須研究該activate方法的實(shí)現(xiàn),這意味著需要付出相對較高的努力才能獲取信息。
查看該activate方法的調(diào)用,我們無法知道它是否會引發(fā)錯誤,或者我們應(yīng)該在哪個時區(qū)中度過時間。雖然 Ruby 可能有點(diǎn)極端,但考慮到其眾所周知的靈活性,許多這些問題在其他語言中也存在。讓我們以 Java 為例。我們?nèi)匀粺o法在類型系統(tǒng)中對圍繞active和activation_date屬性的規(guī)則進(jìn)行編碼,即使可以null,我們也有NullPointerException在運(yùn)行時獲取 s 的風(fēng)險。
隨著代碼庫的增長和開發(fā)團(tuán)隊(duì)的壯大,或者只是隨著一些人離開和加入而發(fā)生變化。從事代碼庫工作的每個人都對整個應(yīng)用程序以及整個代碼庫中所做的所有隱式假設(shè)都有一個完美的心智模型,但這很難做到,相反,理解這些概念需要人們認(rèn)真閱讀遺留代碼。這不僅降低了效率,而且還可能導(dǎo)致生產(chǎn)中的錯誤率增加。
與上面相同的代碼片段,但在 Rust 中更加清晰和富有表現(xiàn)力:
復(fù)制
enum User {
Inactive {
name: String,
},
Active {
name: String,
active: bool,
activation_date: DateTime<Utc>,
},
}
impl User {
fn activate(&self, activation_date: DateTime<Utc>) -> Result<(), DBError> {
match self {
User::Inactive { name } => {
let new_user = User::Active {
name: name.clone(),
active: true,
activation_date: activation_date,
};
new_user.save()
}
User::Active { .. } => Err(Error::default()),
}
}
fn save(&self) -> Result<(), DBError> {
…
}
}
首先,對于用戶模型,我們可以使用 Rust 的enum關(guān)聯(lián)數(shù)據(jù)。這樣,就可以完全清楚非活躍用戶和活躍用戶是什么樣子,以及在什么場景下可以設(shè)置哪些屬性——事實(shí)上,活躍用戶和非活躍用戶甚至不具有相同的屬性,但每個用戶都只具有對其有意義的屬性。它們代表各自的用戶狀態(tài)。此外,屬性的類型也被明確定義——不僅 Rust 是類型化的,而 Ruby 顯然是非類型化的,而且類型也非常精確,例如對于字段,activation_date預(yù)期的時區(qū)在類型中也是正確的。
該函數(shù)的簽名activate還顯式地編碼了 Rails 示例中隱含的許多信息。同樣,預(yù)期的時區(qū)activation_date在類型中是正確的,并且該函數(shù)返回Rust 的時區(qū)Result,這清楚地表明調(diào)用它時可能會發(fā)生錯誤。Result事實(shí)上,Rust 編譯器將要求處理的成功和錯誤變體,以便不會發(fā)生未處理的運(yùn)行時異常。
此外,activation_date當(dāng)調(diào)用函數(shù)時,函數(shù)的參數(shù)總是保證有一個值,因?yàn)?Rust 沒有隱式可空性的概念(與 Java 不同)。如果activation_date 可能在其計(jì)算位置沒有值,則它可能無法Option<DateTime<Utc>>傳遞給函數(shù)activate,因?yàn)樗念愋团c預(yù)期的不同DateTime<Utc>。Rust 編譯器只允許Some其變體的代碼路徑Option導(dǎo)致方法的調(diào)用activate,以便activation_date保證在函數(shù)運(yùn)行時有一個值。
雖然這顯然是一個相當(dāng)簡單的示例,但它很好地說明了 Rust 的兩個主要優(yōu)點(diǎn):
(1)Rails 示例中隱含的許多概念和規(guī)則都是通過 Rust 代碼中的類型顯式傳達(dá)的。可以清楚地區(qū)分活躍用戶和非活躍用戶,對于日期字段,甚至預(yù)期的時區(qū)也被編碼在類型中。這種表現(xiàn)力使代碼更容易理解,特別是對于代碼庫的新手來說,從而提高了可維護(hù)性。
(2)Rust 還大大提高了可靠性,因?yàn)槠渌Z言(包括 Java 或 Go 等類型化語言)中常見的整類錯誤將在編譯時而不是運(yùn)行時檢測到。編譯器保證函數(shù)activation_date的參數(shù)activate具有值以及要處理的函數(shù)可能返回的任何錯誤。
總體而言,當(dāng)每個人都關(guān)注 Rust 的性能時,Rust 帶來的可靠性和可維護(hù)性方面的改進(jìn)常常被忽視。然而,這些好處對于項(xiàng)目的長期成功可能比純粹的績效數(shù)字更相關(guān)。
四、Rust先苦后甜
由于 Rust 的主要優(yōu)點(diǎn)是可靠性、可維護(hù)性、效率和性能,因此該語言的用例顯然是與這四個方面特別相關(guān)的用例。但是,好處的代價是需要考慮在內(nèi)。
總體而言,Rust 仍然需要比其他技術(shù)更高的前期投資,特別是與 Web 項(xiàng)目中常用的技術(shù)相比:
雖然像 JavaScript 和 Ruby 這樣的語言是為了快速獲得結(jié)果而設(shè)計(jì)的,但 Rust 則沒有留下太多的自由度,并且要求程序在獲得工作結(jié)果之前通過所有編譯器的檢查。與這些語言相比,使用 Rust 就需要付出更多的初始工作。此外,人們在使用 Rust 之前還需要翻越一座山——那就是掌握 Rust 獨(dú)特的所有權(quán)系統(tǒng)。
然而,當(dāng)跨過項(xiàng)目的初始階段并將視野擴(kuò)展到更長的時間范圍時,可維護(hù)性、可靠性和穩(wěn)定性等方面變得極其重要,一開始使用 Rust 時進(jìn)行的額外投資會隨著時間的推移而帶來回報——
Rust 應(yīng)用程序更可靠,因此需要更少的時間投入到錯誤修復(fù)上,并且更易于維護(hù),因此更容易與不斷增長和變化的團(tuán)隊(duì)一起有效地工作。
最后就會呈現(xiàn)出:Rust工作量先大后小,先苦后甜。對于其他語言來說,情況往往是相反的:隨著時間的推移,隨著團(tuán)隊(duì)的成長,可靠性和可維護(hù)性挑戰(zhàn)的影響變得更大、成本更高,工作量也會增加。
五、用Rust前的幾個問題
根據(jù) Rust 的優(yōu)勢和投入曲線,每當(dāng)評估是否針對特定情況選擇 Rust 時,需要回答的主要問題是:
(1)團(tuán)隊(duì)是否已經(jīng)具備 Rust 專業(yè)知識(許多不使用 Rust 的團(tuán)隊(duì)實(shí)際上已經(jīng)擁有專業(yè)知識,因?yàn)楹芏嚅_發(fā)人員在空閑時間使用 Rust 編寫代碼)?
(2)可靠性方面有哪些要求?
(3)長期維護(hù)計(jì)劃是什么?
(4)系統(tǒng)構(gòu)建的規(guī)模有多大,Rust 在托管方面可以節(jié)省多少錢?
(5)根據(jù)以上問題的答案,額外的初始投資值得嗎?
雖然在某些情況下,結(jié)論是額外的初始投資不值得,但在某些情況下,評估顯然對 Rust 有利。我們看到的一些典型用例包括:
(1)對于實(shí)現(xiàn)產(chǎn)品關(guān)鍵業(yè)務(wù)邏輯的核心業(yè)務(wù)系統(tǒng)來說,可靠性、長期可維護(hù)性等方面是首要考慮的問題。
(2)對于金融系統(tǒng)來說,通常對錯誤的容忍度很低,而 Rust 帶來的穩(wěn)定性的提高可能是一個決定性因素。另外,性能是一項(xiàng)關(guān)鍵要求,在特定場景(例如交易系統(tǒng))中具有明顯的財(cái)務(wù)影響。
(3)任何必須能夠提供高吞吐量和性能的系統(tǒng)顯然都會從 Rust 中受益。位于多個微服務(wù)前面的代理服務(wù)器等系統(tǒng)必須具有最小的開銷和一致的性能。在這些情況下,垃圾收集語言及其不可靠的性能特征通常不是一個選擇。
(4)最后,對于任何大規(guī)模運(yùn)行的系統(tǒng),在托管成本方面都有很大的節(jié)省潛力。
一旦做出了使用 Rust 的決定,就有兩種主要的采用路徑——要么用 Rust 從頭開始(重新)編寫整個應(yīng)用程序,要么考慮與其他技術(shù)一起逐步采用。篇幅原因,就不再展開了。
原文鏈接:https://mainmatter.com/blog/2023/08/14/the-case-for-rust-on-the-web/#why