【CSDN 編者按】停止按照 JAVA 的方式編寫 Rust,這是我發(fā)現(xiàn)編寫 Rust 代碼的樂趣。
原文鏈接:https://jgayfer.com/dont-write-rust-like-java
作者 | James Gayfer譯者| 彎月
責(zé)編 | 夏萌
出品 | CSDN(ID:CSDNnews)
多年來,我一直對 Rust 很感興趣。類型安全、內(nèi)存安全并且強(qiáng)調(diào)正確性。誰能不愛?
我在開發(fā) Apollo(一款 Python/ target=_blank class=infotextkey>Python 應(yīng)用)時(shí)遇到過很多錯(cuò)誤,假如我使用的是 Rust,那么大多數(shù)錯(cuò)誤都可以被 Rust 編譯器捕獲(雖然達(dá)不到百分百,但比例應(yīng)該會(huì)很高)。一般來說,編譯器可以捕獲許多問題,而在使用動(dòng)態(tài)語言(如 Python 或 Ruby)時(shí),這些問題有可能進(jìn)入生產(chǎn)環(huán)境,盡管并非所有編譯器都能做到這一點(diǎn)。類型安全非常棒,但 Rust 十分注重正確性,這才是最吸引我的地方。
我在工作中編寫了大量 Java 代碼。雖然 Java 不是我最喜歡的語言,但它的編譯時(shí)檢查很強(qiáng)大。在進(jìn)行重大重構(gòu)時(shí),Java 沒有使用 Python 或Ruby 那么可怕。有了這樣的編譯器,遇到不正確或遺漏的導(dǎo)入語句,程序在運(yùn)行時(shí)就會(huì)停止。雖然我們通常會(huì)通過測試來發(fā)現(xiàn)這些問題,但是將這些檢查融入到語言中還是很有必要的。
然而 Java 編譯器并不完美。它無法防止許多種錯(cuò)誤,其中最令人頭疼的就是空引用。在Java 中幾乎所有東西都可以為 null,相關(guān)的錯(cuò)誤直到運(yùn)行時(shí)你才能發(fā)現(xiàn)。與之相反,Rust 擁有適當(dāng)?shù)慕Y(jié)構(gòu)來引導(dǎo)你處理未知值。當(dāng)然,你可以選擇忽略此類提示,但編譯器會(huì)強(qiáng)制你做出深思熟慮的決定。
那么,Rust 是否比 Java 更好呢?Rust確實(shí)有許多讓我很喜歡的地方。Rust 的承諾對我來說非常有吸引力。但我的 Rust 之旅并不全是陽光和彩虹。盡管 Rust 與 Java 有相似之處,但二者并不一樣。直到停止按照 Java 的方式編寫 Rust,我才發(fā)現(xiàn)了編寫 Rust 代碼的樂趣。
一切必須是接口
對于 Java 開發(fā)人員(我就是這樣的開發(fā)人員)來說,一切都是接口,雖然這個(gè)說法不完全準(zhǔn)確,但也有一定的道理。Java 中的接口使用起來很有趣。應(yīng)用程序由小的工作單元組成,每個(gè)工作單元都不了解另一個(gè)工作單元的內(nèi)部工作原理。建立這樣的依賴關(guān)系樹需要在前提付出不少努力,但一旦完成,就能擁有一支獨(dú)立服務(wù)的大軍供你使用。
然而,Rust 中沒有接口,有的是特征(trAIt)。這些特征在很多方面與 Java 中的接口很相似。然而,我們不應(yīng)該將 Rust 中的一切都寫成特征。記住,Rust 的內(nèi)存安全是一個(gè)很強(qiáng)大的功能。而代價(jià)是無法輕松“注入”實(shí)現(xiàn)特征的代碼。

上面的代碼無法編譯,因?yàn)榫幾g時(shí)無法確定 Named 的大小。為了解決這個(gè)問題,我們可以將這個(gè)特征放入Box ,這樣我們就可以指向堆上動(dòng)態(tài)分配的內(nèi)存(稱為 trait 對象)。Box 本身的大小已知,因此程序就可以編譯了。

Box不太方便使用,因此我不太喜歡這種模式。我會(huì)盡可能避開它們。我們可以使用泛型來指定 特征類型。

這兩種方式有何不同?初看之下結(jié)果是一樣的。實(shí)際上二者的差異在于動(dòng)態(tài)調(diào)度與靜態(tài)調(diào)度。對于特征對象,具體的類型是在運(yùn)行時(shí)解析的,而泛型的具體類型是在編譯時(shí)解析的。
實(shí)際上,這意味著只要我們可以在編譯時(shí)推斷所有類型,就可以不使用泛型。如果直到運(yùn)行時(shí)才能推斷類型,則必須使用 Box。
所有權(quán)
所有權(quán)的問題依然存在。如果上述 Named 特征是應(yīng)用程序中其他服務(wù)的必需依賴項(xiàng),該怎么辦?我們是否需要?jiǎng)?chuàng)建一個(gè)主Named ,然后將 &Named 傳遞給每個(gè)依賴項(xiàng),這樣就會(huì)引入生命周期?
還是說我們應(yīng)該使用 Arc,這樣依賴服務(wù)就可以寫為 Arc<Box<dyn Named>>,從而允許并發(fā)訪問所擁有的資源?
兩種方法我都嘗試過了,雖然可行,但都不太理想,尤其是當(dāng)應(yīng)用程序中的每項(xiàng)服務(wù)都受到影響時(shí)。
純函數(shù)
將 Rust 當(dāng)成純粹的面向?qū)ο笳Z言并不合適。雖然我仍然像上面的例子一樣編寫“服務(wù)對象”,但只在必要時(shí)使用它們,實(shí)際上我更推薦純函數(shù)。
我們來考慮一個(gè)處理結(jié)賬事件的函數(shù),該函數(shù)會(huì)更新系統(tǒng)中的客戶 ID。

雖然我們可以將這段處理編寫為一個(gè)服務(wù),其中的 UserRepo 是一個(gè)注入值,但上面我們已經(jīng)探討過了,這樣做會(huì)帶來一定的復(fù)雜性。此外,我們?nèi)匀豢梢暂p松注入 UserRepo 的不同實(shí)現(xiàn),例如提供不會(huì)訪問生產(chǎn)數(shù)據(jù)庫的實(shí)現(xiàn),因此也沒有理由將其編寫為服務(wù)。缺點(diǎn)在于,我們的函數(shù)簽名可能會(huì)有點(diǎn)“繁忙”,不過這種程度的“痛苦”根本不算什么。
擁抱真正的 Rust
以前我深陷“Rust 語言很難”的誤區(qū)不能自拔。一個(gè)重要原因是我堅(jiān)持按照其他語言的方式編寫 Rust 代碼。雖然汲取以往的經(jīng)驗(yàn)很重要,但擁抱 Rust 本身的慣用寫法對于掌握這門語言也很重要。只有轉(zhuǎn)變心態(tài)才能真正掌握 Rust。按照不適合的方式編寫 Rust 會(huì)讓我們陷入苦苦的掙扎,我們應(yīng)該擁抱它本來的樣子。






