Kotlin可以從Rust中學到什麼 - Cedric

banq發表於2021-11-16

在開始之前想重申一下,我的觀點不是要在兩種語言之間發起語言戰爭,也不是試圖將一種語言變成另一種語言。我花了很多時間分析我想要討論的特性,並自動排除了對一種語言非常有意義而在另一種語言中很荒謬的特性。例如,在 Rust 中要求垃圾收集是愚蠢的(因為它的主要主張是對記憶體分配的非常嚴格的控制)並且反過來,Kotlin 採用借用檢查器是沒有意義的,因為 Kotlin 收集垃圾是其主要訴求之一。

 

巨集

我一直對語言中的巨集有愛恨交加的關係,尤其是不衛生的。至少,巨集應該完全整合在語言中,這需要兩個條件:

  • 編譯器需要了解巨集(例如,與 C 和 C++ 中的前處理器不同)。
  • 巨集需要擁有對靜態型別 AST 的完全訪問許可權,並能夠安全地修改此 AST。

Rust 巨集滿足了這兩個要求,因此,解鎖了一組非常有趣的功能,我很確定我們才剛剛開始探索。

例如,dbg!()巨集:

let a = 2;
let b = 3;
dbg!(a + b);

會列印:

[src\main.rs:158] a + b = 5

注意:不僅僅是原始檔和行號,還有正在顯示的完整表示式(“ a + b”)。

Kotlin 在這方面並非完全沒有武裝,因為註解和註解處理器的組合提供了一組功能,與您在 Rust 中可以通過巨集和屬性實現的功能相去甚遠。主要區別在於,雖然 Kotlin 的方法只允許合法的 Kotlin 程式碼出現在 Kotlin 原始檔中,但 Rust 允許任何任意語法作為巨集引數出現,並且編譯器生成正確的 Rust 取決於巨集會接受。

一方面,能夠在 Rust 原始檔中編寫任何型別的程式碼很好(這就是 React 對 JSX 所做的),另一方面,濫用的可能性很高,人們理所當然地擔心有一天Rust 原始檔看起來與 Rust 程式碼完全不同。然而,到目前為止,我的恐懼從未成為現實,我遇到的大多數巨集都非常節儉地使用自定義語法。

巨集的另一個非常重要的方面是 Rust IDE 理解它們(好吧,至少 CLion 理解它們,並且可能所有 IDE 都可以,並且將會)並且它們會在出現問題時立即向您顯示錯誤。

巨集用於大量場景,併為 Rust 提供一些非常簡潔的 DSL 功能(例如,用於支援 SQL、Web、圖形等的庫)。

此外,巨集與……非常巧妙地整合在一起。

 

預處理的屬性

屬性是 Rust 版本的註釋,它們以或開頭#!:

#![crate_type = "lib"]

#[test]
fn test_foo() {}

這裡沒有什麼開創性的東西,但我想討論的是條件編譯方面。

在 Rust 中,通過將屬性和巨集與 結合來實現條件編譯cfg,它既可用作屬性又可用作巨集。

巨集版本允許您有條件地編譯語句或表示式:

#[cfg(target_os = "macos")]
fn macos_only() {}

在上面的程式碼中,函式macos_only()只有在作業系統是 macOS 時才會被編譯。

巨集版本cfg()允許您向條件新增更多邏輯:

let machine_kind = if cfg!(unix) {
    "unix"
} else { … }

上面的程式碼是一個巨集,這意味著它在編譯時,編譯器將完全忽略條件的任何部分。

您可能會想知道 Kotlin 中是否需要這樣的功能是有道理的,我也問過自己同樣的問題。

Rust 在多個作業系統上編譯為本地可執行檔案,如果您想在多個目標上釋出工件,這使得這種條件編譯非常必要。Kotlin 沒有這個問題,因為它生成了在 JVM 上執行的作業系統中立的可執行檔案。

儘管 Java 和 Kotlin 開發人員已經學會了不用前處理器,因為 C 前處理器給幾乎每個使用過它的人留下了如此糟糕的印象,但在我的職業生涯中,也有一些情況能夠進行條件編譯,包括或排除原始碼檔案,甚至只是語句、表示式或函式,都會派上用場。

不管你在這場辯論中的立場如何,我不得不說我真的很喜歡 Rust 生態系統中兩個截然不同的特性,巨集和屬性,能夠協同工作以產生如此有用和多功能的特性。

 

擴充套件特性

擴充套件特徵允許您“事後”使結構符合特徵,即使您不擁有這些特徵中的任何一個。最後一點需要重複:結構或特徵是否屬於您未編寫的庫並不重要。您仍然可以使該結構符合該特徵。

例如,如果我們想在u8型別上實現一個函式last_digit():

trait LastDigit {
    fn last_digit(&self) -> u8;
}

impl LastDigit for u8 {
    fn last_digit(&self) -> u8 {
        self % 10
    }
}

fn main() {
    println!("Last digit for 123: {}", 123.last_digit());
    // prints “3”
}

首先,我發現 Rust 語法優雅和簡約(甚至比 Haskell 更好,並且可以說,比我為 Kotlin 提出的更好)。其次,能夠以這種方式擴充套件特徵在建模問題方面釋放了很多可擴充套件性和功能,但我不會深入研究這個主題,因為它會花費太長時間(查詢“型別類”以瞭解您可以實現的目標)。

這種方法還允許 Rust 模仿 Kotlin 的擴充套件函式:

fun Type.function() {...}

同時提供一種更通用的機制來擴充套件函式和型別,但代價是語法稍微冗長一些。

 

Cargo交付

這可能令人驚訝,因為在 Gradle 中,Kotlin 擁有非常強大的構建和包管理器。這兩個工具當然具有相同的功能表面區域,允許構建複雜的專案,同時還管理庫下載和依賴項解析。

我認為Gradle 是一個更好的替代品的原因是因為它在宣告性語法和命令式方面之間進行了清晰的分離。簡而言之,標準的、通用的構建指令在宣告性cargo.toml檔案中指定,而臨時的、更具程式性的構建步驟則直接在 Rust 中的名為 的檔案中編寫build.rs,使用 Rust 程式碼呼叫一個相當輕量級的構建 API。

相比之下,Gradle 一團糟。首先是因為它開始在 Groovy 中被指定,現在它支援 Kotlin 作為構建語言(並且這種轉變仍在進行中,在它開始多年後),但也因為這兩者的文件仍然非常糟糕

我所說的“糟糕”並不是指“缺乏”:有很多文件,只是……糟糕、不堪重負,其中大部分已經過時或已棄用,等等……需要從 StackOverflow 複製/貼上數百行作為一旦你需要一些與眾不同的東西。外掛系統的定義非常鬆散,基本上允許所有外掛訪問 Gradle 內部結構中的任何內容。

顯然,我對這個主題非常固執,因為我建立了一個受 Gradle 啟發的構建工具,但使用了更現代的語法和外掛解析方法(稱為 Kobalt),但與此無關,我認為cargo設法取得了非常好的平衡在一個靈活的構建+依賴管理器工具之間,該工具充分涵蓋了所有預設配置,而不會隨著專案的增長而變得非常複雜。

 

u8, u16, ...

在 Rust 中,數字型別非常簡單:u8是 8 位無符號整數,i16是 16 位有符號整數,f32是 32 位浮點數,等等……

這對我來說是一種清新的空氣。在我開始使用這些型別之前,我從未完全確定我一直對 C、C++、Java 等定義這些型別的方式感到不舒服。每當我需要一個數字時,我都會使用int或Long作為預設值。在 C 中,我有時甚至long long沒有真正理解其中的含義。

Rust 迫使我密切關注所有這些型別,然後,每當我嘗試執行可能導致錯誤的強制轉換時,編譯器都會毫不留情地讓我保持誠實。我真的認為所有現代語言都應該遵循這個約定。

 

編譯器錯誤資訊

並不是說 Kotlin 的錯誤訊息很糟糕,但 Rust 確實在多個維度上在這裡樹立了新標準。

簡而言之,以下是您可以從 Rust 編譯器中得到的期望:

  • 帶有箭頭、顏色、問題部分的清晰描述的 ASCII 圖形。
  • 簡單的英語和詳細的錯誤訊息。
  • 關於如何解決問題的建議。
  • 相關文件的連結,您可以在其中找到有關該問題的更多資訊。

我當然希望未來的語言能夠獲得靈感。

 

可移植性

大約 25 年前,當 Java 出現時,JVM 做出了一個承諾:“一次編寫,隨處執行”(“WORA”)。

雖然這一承諾在早些年站不住腳,但不可否認,WORA 在今天已經成為現實,並且已經存在了幾十年。JVM 程式碼不僅可以編寫一次,到處執行,這樣的程式碼還可以在任何地方編寫,這對開發人員來說是一個重要的生產力提升。您可以在任何 Windows、macOS、Linux 上編寫程式碼,並在任何 Windows、macOS 和 Linux 上部署。

令人驚訝的是,即使 Rust 生成本機可執行檔案,它也具有這種多功能性。無論您在何種作業系統上編寫程式碼,生成大量可執行檔案都是不大的,而且這些可執行檔案是本機的,而且由於 LLVM 令人難以置信的技術成就,其額外的好處是效能也非常好。

在 Rust 之前,我已經接受了這樣一個事實,即如果我想在多個作業系統上執行,我必須付出在虛擬機器上執行的代價,但 Rust 現在表明你可以擁有你的蛋糕,也可以吃它。

Kotlin(以及整個 JVM)也開始吸取這一教訓,通過 GraalVM 等舉措,但為 JVM 程式碼生成可執行檔案仍然充滿限制和限制。

 

總結

TestNG是我在 2004 年左右開始的一個專案,唯一的目的是混淆事物。我想向 Java 世界展示我們可以比 JUnit 做得更好。我無意讓任何人喜歡和採用 TestNG:它是一個專案實驗室。一個實驗。我想做的就是證明我們可以做得更好。我真的希望 JUnit 團隊(或其他任何團隊)能夠看看 TestNG 並思考“哇,從來沒有想過!我們可以將這些想法融入 JUnit 並使其變得更好!”。

如果這兩個非常非常不同的世界(Rust 和 Kotlin 社群)從他們極快的發展速度中停下來,快速地看看彼此。我也會欣喜若狂。

我是一個要求很高的開發人員,我已經寫了 40 年的程式碼,並計劃在智力允許的情況下繼續這樣做。我對程式語言感到莫名其妙的熱情,我希望我的熱情通過這兩篇文章閃耀。

相關文章