當前位置: 妍妍網 > 碼農

為什麽一周後我就快放棄 Rust 了

2024-09-19碼農

我在夏天慢慢地讀完了【Rust 程式語言】,並做了大量的筆記。上周我開始用它寫點東西,一周後我覺得我已經看到了足夠多的東西。我可能會放棄我投入的成本,因為我更看重我的時間和理智。

我原本以為借用檢查會是「問題」,但我很容易理解這些概念。你可以傳遞參照,或者複制它們。我在 C# 中編寫過低延遲交易系統,對多執行緒很熟悉,所以高級智慧指標對我來說很容易理解。

但這些都不是問題。

書中提到了泛型,在書面文字中看起來還不錯,但當我開始使用一個 web 框架時,這是一個很好的方法來提前了解開發體驗,我發現自己陷入了許多 TypeScript 開發者為自己和他們可憐的同事制造的糟糕的型別系統泥潭中。

我找不到合適的詞語來解釋,但 Rust 實際上與 TypeScript 非常相似,它的型別系統「非常強大」,以至於它最終主導了程式碼的編寫方式。

但它比 TypeScript 更糟糕,因為它還擁有一個半生不熟的異步系統,以及生命周期邊界、一堆小包裝的智慧指標型別,以及各種巧妙的功能,這些功能變得難以理解。你無法逆向工程解決問題,因為存在著多層復雜和疊加的型別推斷,甚至包括被稱為關聯型別的元素,每個元素都有自己的邊界和約束。

但正是編譯器進行的層層推斷,人類無法做到,至少我無法做到,除非忍受真正的頭痛和悲傷。當它工作正常,或者是你自己編寫的程式碼時,它很好用,但一旦你偏離框架文件,你就完蛋了。

它就像一個魔術方塊,對於它的大多數套用來說,它帶來的痛苦並不值得。

我將再次嘗試 Go。我讀過一本書,但沒有計畫可以使用它。我喜歡 Go 幾乎沒有功能、幾乎沒有改變或添加功能、只有一個做事方式、速度非常快,並且在某些測試中幾乎與 Rust 一樣快。此外,一些優秀的公司使用它——使用某種語言的公司很重要。

在超過 20 年的編碼生涯中,我意識到簡單性和生產力才是最重要的,它們會帶來深切的快樂。Rust 絕對不簡單,而且除了替換更糟糕的語言之外,不值得為它付出生產力的代價。

我讀過一些對 Rust 的評論,那些評論來自將數年時間投入到 Rust 中的人,我在尋找「它太棒了」這樣的斷言,但我看到的都是褒貶不一的評價。這可不是什麽好兆頭。我認為這麽多人喜歡 Rust 的原因可能是他們從哪裏遷移過來,以及他們可能在其他語言中使用了不好的實踐。

Rust 在某些型別上依賴於返回結果,而這些型別只是你可以在任何語言中使用的模式。

你知道,使用一個編譯的、型別安全的語言,擁有基本的泛型,並且永遠不會建立空值,可以讓你走得很遠。在我的經驗中,大多數問題都源於返回空值或使用空值例項化結構體的單一錯誤。第二組頭痛問題來自沒有盡早失敗,導致你最終得到的跟蹤資訊距離問題的根源很遠。

實際上,這兩件事構成了編碼的大部份痛苦。

編輯

由於這篇文章引起了關註和反駁,下面是一個問題的範例。請記住,我已經讀過這本書,從每一頁中手寫了筆記,然後將這些筆記輸入到我的 GitHub 筆記中。實際上,我也完成了書中的編碼教程。

但同時,我也在使用強型別語言編碼了 25 年。我編碼了將近 40 年,我的智商是 150。

我花了大約 2 個小時只是盯著下面的程式碼,試圖理解它以及它對我作為框架使用者的意義,並盡量避免過多地依賴 AI 來幫助我,因為我需要能夠訓練自己幾乎瞬間理解這種東西及其含義。

我可以從邏輯上把它分解,但我不想使用一種允許人們編寫「高爾迪之結」的語言,或者在一個可能出現「高爾迪之結」的計畫中工作,比如一個完整的應用程式。對於 TypeScript 程式碼來說,情況也是如此。我確信 Rust 非常適合小型元件。

當我編寫一個使用比這種方法更簡單的方法來實作類似中介軟體的功能的方法時,我改變了一些東西,我現在不記得是什麽了,但它似乎改變了返回值型別,並且無法從編譯錯誤、我的程式碼或框架的原始碼中知道如何解決這個難題,即需要返回什麽才能滿足所有推斷的型別,從而實作它所需的特征。

答案很可能距離我所在的程式碼很遠。這就像排查一個距離問題根源很遠的異常,但更糟糕的是,因為它是由遙遠的型別推斷體操造成的。

程式設計師有一種傾向,就是編寫一些「結」,從而扼殺了他們所有同事的快樂。這是一個累積的過程;他們慢慢地將「結」越來越多地堆積起來,將其全部保持在他們的短期記憶中,因此他們能夠理解它,並且看不到「結」,如果他們看到了,他們就會有沈沒成本謬誤。在我的經驗中,似乎存在一個程式碼復雜度守恒原理。極端的復雜度就是痛苦。

復雜度可以集中在一個區域,它通常是一種選擇,一種權衡,但經常會偶然發生,就像上面一樣。哎呀,我編織了一個「結」。你可以用更復雜的方式實作相同的邏輯,但不會帶來痛苦。復雜度被分散,這樣當你從宏觀角度來看時,它集中在整個程式碼庫中,但當你從微觀角度來看時,每個部份都是可理解的,並且沒有痛苦。

這就是我的理論,我認為這也是 Go 語言的建立者所持有的理論,以及他們為什麽認為超簡單的語言是 Google 這樣大型快速發展的公司擴充套件軟體的最佳方式。

這是一個例子。我不知道這對作為 Actix 框架使用者的我意味著什麽。我無法僅僅從程式碼中弄清楚它對我意味著什麽,我需要做什麽。我的潛意識感覺它正在試圖處理一個悖論,它無法向我的意識大腦表達,甚至無法提出正確的問題。

use std::future::{ready, Ready};
use actix_web::{
dev::{forward_ready, Service, ServiceRequest, ServiceResponse, Transform},
Error,
};
use futures_util::future::LocalBoxFuture;
// 中介軟體處理有兩步。
// 1. 中介軟體初始化,中介軟體工廠使用
// 下一個服務鏈作為參數呼叫。
// 2. 中介軟體的 call 方法使用普通請求呼叫。
pubstructSayHi;
// 中介軟體工廠是 `Transform` 特征
// `S` - 下一個服務的型別
// `B` - 響應主體型別
impl<S, B> Transform<S, ServiceRequest> for SayHi
where
S: Service<ServiceRequest, Response = ServiceResponse<B>, Error = Error>,
S::Future: 'static,
B: 'static,
{
typeResponse = ServiceResponse<B>;
typeError = Error;
typeInitError = ();
typeTransform = SayHiMiddleware<S>;
typeFuture = Ready<Result<Self::Transform, Self::InitError>>;
fnnew_transform(&self, service: S) -> Self::Future {
ready(Ok(SayHiMiddleware { service }))
}
}
pubstructSayHiMiddleware<S> {
service: S,
}
impl<S, B> Service<ServiceRequest> for SayHiMiddleware<S>
where
S: Service<ServiceRequest, Response = ServiceResponse<B>, Error = Error>,
S::Future: 'static,
B: 'static,
{
typeResponse = ServiceResponse<B>;
typeError = Error;
typeFuture = LocalBoxFuture<'staticResult<Self::Response, Self::Error>>;
forward_ready!(service);
fncall(&self, req: ServiceRequest) -> Self::Future {
println!("Hi from start. You requested: {}", req.path());
let fut = self.service.call(req);
Box::pin(asyncmove {
let res = fut.await?;
println!("Hi from response");
Ok(res)
})
}
}








文章精選

「Rust