這篇文章是關於我通過解決Twitch上尚未解決的所有CtCI問題來學習Rust的經驗。
>Portrait of Ada Lovelace, Science Museum Group UK
>Rust logo, courtesy of Mozilla, CC-BY
Rust是一種現代系統級編程語言,在設計時考慮了安全性。 它提供了零成本的抽象,泛型,功能特性以及更多其他功能。 我最近開始努力正確地學習Rust,我想分享一些想法。
直到最近,我只在Rust中編寫了一些小型程序,並且在閲讀了" Programming Rust"的一半內容之後,我真的不瞭解Rust。 我認為了解該語言的一種好方法是解決"破解編碼面試"一書中的所有189個問題。 我不僅會用Rust來解決問題,而且決定在Twitch上進行開發。 我不會在觀眾面前進行技術演講或編碼,但對嘗試學習一種編程語言並解釋自己的工作並不陌生,這對我來説是新事物。
事情開始有點粗糙:技術問題,流問題,工具問題,而我一開始就很難理解內存範例。 試圖做到這一點,同時還向人們解釋我在做什麼,這很tricy。
我花了大約8個小時來實現一個鏈表:我記錄了自己的兩個4個小時的流,試圖弄清楚如何正確使用Rc,RefCell和Box。 有一陣子,我覺得我只是在敲擊鍵盤嘗試隨機組合,直到卡住了。 令人驚訝的是,人們紛紛收看節目。 我一定做對了。
在脱機閲讀了一些之後,這些概念開始為我所吸引。 完成我的鏈表實現後,事情變得簡單了。
>Linked list in Rust. VSCode has tight integration with RLS
我現在已經進入了本書的第4章,我覺得自己已經邁出了大步。 編譯後,Rust感覺自然,高效且令人非常滿意。 Rust的類型嚴格,並提供了出色的編譯器消息:如果您設法安撫了編譯器,那麼您的代碼很有可能會工作-除非存在任何邏輯缺陷。
Rust的一個可愛功能是編譯器可以多麼有用。 例如,眾所周知,C 代碼的編譯器消息很難解密。 儘管Clang的錯誤消息已取得了長足的進步,但Rust的編譯器的幫助又大了一個數量級。
>Example of rustc's error output
到目前為止,我將總結一些發現。 這是基於我最初的反應,我承認我缺乏Rust的專業知識,但是對於其他人來説,看看他們的經驗與我的經驗可能仍然很有趣。 我很遺憾地承認,我沒有對下面的每個問題進行徹底的研究,因此我的信息可能過時或不準確。
語言:好
首先,對Rust團隊以及為該項目做出貢獻的每個人都表示敬意。 這是我有過的最有趣的編程語言學習經歷之一。 我不知道Rust是否會像其他語言一樣吸引開發人員的注意力,但我認為它會持續存在。 關於細節:
· Rust代碼非常易於閲讀,並且不會因為難以解析C 或Scala等語言的語法而受苦。 它似乎具有我期望的功能,而挑戰僅在於確定要調用的函數。
· 具有諸如map(),filter(),find()之類的功能特性很令人高興。 定義高階函數並將閉包傳遞給它們很容易。 它並沒有使函數式編程像Ruby這樣的語言那麼容易,但是很接近。 實際上,令人驚訝的是,與C / C 相比,這種語言的性能是如此輕鬆。
· Rust迫使您認真考慮內存分配,因為您別無選擇。 最後,這意味着草率的代碼難以編寫,而好的代碼則易於編寫。 這些抽象也直接映射為編寫安全的併發代碼。
· Rust的零成本抽象使編寫好的代碼變得容易,而又不增加開銷。 特性可以提供現代的編程抽象,而不會降低性能。
· Rust代碼是安全的
· Rust的Result和Option提供了一種處理可能返回值的函數或可能包含值的變量的好方法。 在C,C 甚至Java中,一種常見的模式是函數在沒有返回值的情況下返回空指針。 在大多數情況下,當這種情況意外發生時,會導致某人的時間不好。
語言:不好
· 我發現有時需要unwrap(),as_ref()和借款()有點冗長。 我希望可以使用一些語法糖來減少必須以不同方式將這些調用鏈接在一起的次數。 我發現自己經常編寫類似於option.as_ref()。unwrap()。borrow()的代碼,感覺很討厭。
· 為了能夠在合理的時間內編譯代碼,需要對編譯器進行一定的權衡。 因此,在某些情況下,rustc無法推斷類型,或者需要一些人工幫助才能編譯代碼。 對我而言,我發現有時很難弄清編譯器需要什麼,以及為什麼它對我來説不明白。
· 有些事情有時會覺得太冗長。 例如,在str和String之間進行轉換,或者將引用而不是值傳遞給函數似乎是編譯器可以為我解決的問題。 我敢肯定有一個很好的理由説明為什麼會這樣,但是有時候覺得rustc太正確了。
· 必須處理每個函數的每個結果都是好的; 這意味着程序員必須考慮每個函數調用的情況。 有時會感到乏味。 ? 操作員可以減少一些冗長的細節,但是沒有很好的概括方法來處理故障類型。 諸如失敗和錯誤鏈之類的條板箱使此操作變得容易,但是您仍然需要為可能發生的每種錯誤類型明確定義一個案例。
語言:醜陋
· 巨集:WTF? 與其他語言相比,Rust宏感覺像是向左拐。 公平地講,我還無法理解它們,但是它們感覺就像是一種奇怪的螺栓附件,這種附件只是在Perl的啓發下設計出來的。 將來,我最終會花一些時間來正確理解它們,但現在我想避免使用它們。 像瘟疫。
工具:好
· Rust提供了不錯的工具,並通過RLS與VSCode等IDE集成。 RLS提供了棉絨,代碼完成,語法檢查和即時格式化的支持。
· Cargo是Rust強大的包裹管理器:如果您嘗試使用Rust,您可能會熟悉它。 在大多數情況下,與Cargo合作是一種樂趣。 已經有大量用於Cargo的插件,可提供其他功能,例如代碼覆蓋率。
· Cargo還是一個構建系統,可用於運行單元和集成測試。 通過某種程度的聲明性TOML語法,可以輕鬆配置構建和依賴項。
· Cargo與crates.io集成,crates.io是開源Rust項目的權威資源。 與PyPi或RubyGems一樣,您幾乎可以在crates.io上找到所有其他Rust軟件包。
· rustup是用於管理Rust安裝的首選工具。 您可以選擇穩定,測試版或每晚的頻道,並安裝所有以前版本的特定內部版本。 它還可以讓您安裝clippy和rustfmt之類的組件
· 如果您是像我這樣的完美主義者,那麼clippy是必不可少的代碼專家。 它將幫助您學習Rust的方式,並且可以捕獲許多您可能不會注意到的常見錯誤。 對我來説,當我知道解決問題的方法時,clippy會很有幫助,但我不知道正確的方法。
· rustfmt是Rust的自以為是的代碼格式化程序。 我認為,自以為是的格式化程序是可行的方法。 當所有內容都遵循相同的標準時,就無需爭論代碼格式。
· sccache將通過減少編譯時間來加快處理速度。 但是,請注意,sccache不適用於RLS,因此您不能在IDE中使用它。
工具:不好
好的,好的,在繼續討論Rust的問題之前,我們都應該承認這是一項正在進行的工作。 Rust工具的發展非常迅速,但是我認為它還有很長的路要走。 我將重點介紹一些需要改進的地方:
· 編譯感覺很慢。 它不僅速度很慢,而且我發現我經常不得不重新編譯軟件包。 我瞭解必要性,但有時還是很煩人。 sccache有幫助,但仍然感覺很慢。 有一些方法可以減輕這種情況,例如使用貨物檢查代替貨物建造。
· RLS使用racer來完成代碼,但我發現它充其量只能做到命中或錯過。 我通常希望完成的功能不存在,不存在的功能會顯示為完成選項。 我尚未進行詳盡的分析,但是這些建議似乎只有大約75%的時間是正確的。 其原因可能僅僅是由於RLS的速度較慢。
· 沒有REPL:這可能是不公平的,因為也沒有不錯的C REPL,但是如今,REPL附帶了許多語言。 GitHub上有一個關於此的公開問題。 好的REPL不是必需的,但會有所幫助。
工具:醜陋
· RLS速度慢,越野車和崩潰。 至少對我來説,我發現我經常需要在VSCode中重新啓動RLS。 RLS是一個很棒的工具,但充其量確實感覺像是Beta版。 我發現自己必須暫停並等待RLS趕上來,這樣我才能確保自己不會編寫錯誤的代碼。 有時候,我認為最好禁用RLS,編寫代碼,然後像以前在Vim中進行所有編碼一樣嘗試對其進行編譯。 RLS似乎變得束手無策,更多的是分心。
庫:好
· Rust生態系統中大量可用的庫令人驚訝。 似乎有一種淘金熱來耗盡並實現所有Rust庫,並使您的名字在Rust歷史上永垂不朽。 您可以在crates.io或GitHub上找到大部分期望的內容。 每次搜索都會為我要找的內容提供2或3種不同的實現方式時,我常常會感到驚訝。
· 我使用的大多數庫都按預期工作,其中許多超出了預期。 這與替代方法有一個微妙而重要的區別。
>New York Public Library in 1908, from the Library of Congress
庫:不好
· 儘管庫很多,但我發現其中許多庫是不完整,不成熟或完全廢棄的。 Rust社區似乎仍處於起步階段,但它每天都在進步。
· 有時有太多選擇。 例如,我想使用日誌記錄庫,但我發現有一長串可供選擇的選項。 有很多選擇是可以的,但是對於這樣的事情,我只想告訴他們使用什麼。 Java生態系統在java.util.logging,log4j,logback,log4j2,slf4j和tinylog中也存在類似的問題。 到目前為止,我仍然不知道哪個Java日誌記錄庫是最適合使用的。 對於Rust,我只是決定使用env_logger,因為它是列表中的第一個選項。
· 雖然不如Node.js生態系統那麼糟糕,但每個庫的依賴項列表已經相當長。 我寫了一個名為LabHub的GitHub小型機器人,但我仍然驚訝於引入了多少個依賴項。 對我來説,這意味着碎片化和重複,可以通過將一些廣泛需要的功能緩慢地移植到標準庫中來改進。
庫:醜陋
· 我注意到,除了一個相對簡單的應用程序的大量依賴關係之外,我還多次編譯同一庫的不同版本。 我認為Cargo為了保持向後兼容性而試圖變得更聰明。 它會根據語義版本來猜測要包括哪些庫。 令我擔心的是,當您擁有一個依賴於其他也有漏洞的其他古老版本庫的庫時,會發生什麼。 毫無疑問,Rust的作者已經考慮過這一點,但是看起來仍然很奇怪。 公平地説,處理依賴項的依賴項是一個非常棘手的問題。 值得慶幸的是,有一個適用於Cargo的樹狀工具,可以幫助您解決這些問題,然後您可以強制依賴項升級其依賴項。
>10 different libraries with 2 different versions in the same package
最後的想法
Rust是一門很棒的語言。 如果您熱衷於編程,請嘗試一下。 我希望你喜歡它。
>Me attempting to have a thought
關於我:自12歲起我就一直在編寫代碼,為許多不同的開源項目做出了貢獻,並在Airbnb,Mesosphere和Citadel等多家公司工作。 在github.com/brndnmtthws的GitHub上找到我,在twitch.tv/brndnmtthws的Twitch上找到我,或在twitter.com/brndnmtthws的Twitter上找到我。