在我周圍的每個(gè)人都知道我是Python 的忠實(shí)粉絲。大約15年前,當(dāng)我對(duì) Mathworks Matlab 感到厭倦時(shí),我開始使用Python。雖然Matlab的理念看起來(lái)不錯(cuò),但在掌握了Python之后,我再也沒有回頭。我甚至成為了我所在大學(xué)的Python傳道者,"傳播這個(gè)詞"。
會(huì)編碼并不等于成為軟件開發(fā)者。當(dāng)我了解到強(qiáng)類型、SOLID原則和通用編程架構(gòu)等主題時(shí),我也瞥見了其他編程語(yǔ)言以及它們?nèi)绾谓鉀Q問題。特別是Rust引起了我的興趣,因?yàn)槲医?jīng)常看到基于Rust的Python包(例如Polars)。
為了對(duì)Rust有一個(gè)合適的介紹,我參加了官方的Rustlings課程,這是一個(gè)包含96個(gè)小型編碼問題的本地Git存儲(chǔ)庫(kù)。盡管這是相當(dāng)可行的,但Rust與Python非常不同。Rust編譯器是一個(gè)非常嚴(yán)格的家伙,不接受"也許"這個(gè)答案。以下是我認(rèn)為Rust和Python之間的三個(gè)主要區(qū)別。
免責(zé)聲明:雖然我對(duì)Python相當(dāng)熟練,但我對(duì)其他語(yǔ)言了解有點(diǎn)生疏。我仍在學(xué)習(xí)Rust,可能對(duì)某些部分有誤解。
所有權(quán)和借用可能是Rust編程語(yǔ)言最基本的方面。它旨在確保內(nèi)存安全,而無(wú)需所謂的垃圾收集器。這是Rust的一個(gè)獨(dú)特概念,我尚未在其他語(yǔ)言中看到過。讓我們以一個(gè)例子開始,我們將值42分配給變量answer_of_life。Rust現(xiàn)在將在內(nèi)存中分配一些空間(這有點(diǎn)復(fù)雜,但現(xiàn)在我們簡(jiǎn)化一下),并將"所有權(quán)"附加到這個(gè)變量上。重要的是要知道一次只能有一個(gè)所有者。一些操作會(huì)"轉(zhuǎn)移所有權(quán)",使先前的變量引用無(wú)效。這通過防止諸如雙重釋放內(nèi)存、數(shù)據(jù)競(jìng)爭(zhēng)和懸空引用等問題來(lái)確保內(nèi)存安全。
fn main() { let s1 = String::from("Hello, Rust!"); // Ownership of the String is transferred from s1 to s2 let s2 = s1; // This results in a compilation println!("s1: {}", s1);} // s2 goes out of scope and memory is freed
一個(gè)在其他語(yǔ)言中也使用的術(shù)語(yǔ)是作用域。這可以被看作是代碼中的一個(gè)"生存區(qū)"。每當(dāng)代碼離開一個(gè)作用域時(shí),所有具有所有權(quán)的變量都將被釋放。這在Python中是根本不同的事情。Python使用垃圾收集器,在沒有對(duì)其的引用時(shí)釋放變量。在Source 1的例子中,將所有權(quán)從變量s1轉(zhuǎn)移到s2,此后變量s1將無(wú)法使用。
對(duì)于Python用戶來(lái)說(shuō),所有權(quán)可能會(huì)令人困惑,因?yàn)樵陂_始階段確實(shí)是一場(chǎng)真正的斗爭(zhēng)。在Source 1的例子中有點(diǎn)過于簡(jiǎn)單了。Rust強(qiáng)制你考慮一個(gè)變量是在哪里創(chuàng)建的以及它應(yīng)該如何被轉(zhuǎn)移。例如,當(dāng)你將參數(shù)傳遞給函數(shù)時(shí),所有權(quán)可以如Source 2中所示被轉(zhuǎn)移。
fn take_ownership(some_string: String) { // The ownership of the String is transferred to some_string println!("Got ownership: {}", some_string);} // some_string goes out of scope and the memory is freedfn main() { let my_string = String::from("Hello, ownership!"); // Ownership is transferred to the function and my_string is // no longer valid take_ownership(my_string); // This results in a compilation error as my_string is no // longer the owner of the String. println!("my_string: {}", my_string);} // my_string is no longer valid here, as it was moved to take_ownership
僅僅轉(zhuǎn)移所有權(quán)可能很麻煩,對(duì)于某些用例甚至可能行不通,因此Rust提出了所謂的借用系統(tǒng)。與轉(zhuǎn)移所有權(quán)不同,變量同意借用該變量,而原始變量仍保持所有權(quán)。默認(rèn)情況下,借用變量是不可變的,即只讀的,但通過添加mut關(guān)鍵字,借用甚至可以是可變的。在Source 3中,我展示了兩個(gè)不可變的借用和一個(gè)可變的借用的例子。當(dāng)函數(shù)超出范圍時(shí),所有變量都將被刪除。
fn main() { // s is the owner of the mutable String let mut s = String::from("Hello, Rust!"); let r1 = &s; // Immutable borrow let r2 = &s; // Another immutable borrow println!("r1: {}, r2: {}", r1, r2); let r3 = &mut s; // Mutable borrow r3.push_str(", and Pythonista!"); // Modifying the borrowed value println!("r3: {}", r3);} // r1, r2, r3, and s go out of scope and memory is automagically freed
生命周期是Rust中與借用和所有權(quán)相關(guān)的一個(gè)概念,它幫助編譯器強(qiáng)制規(guī)定引用可以有效存在多長(zhǎng)時(shí)間的規(guī)則。你可能會(huì)遇到這樣一種情況,你創(chuàng)建了一個(gè)結(jié)構(gòu)或一個(gè)函數(shù),它是使用兩個(gè)借用構(gòu)建的。這意味著現(xiàn)在函數(shù)或結(jié)構(gòu)的結(jié)果可能取決于先前的輸入。為了更明確地表示這一點(diǎn),我們可以通過注釋生命周期來(lái)表達(dá)關(guān)系。在Source 4中查看一個(gè)例子。
struct Quote<'a> { part: &'a str,} // We annotated this Struct such that its lifetime is linked to partfn main() { let novel = String::from("Do or do not. There is not try."); // We split novel on the period but split returns borrows. // This means that if novel goes out of scope, so does first_sentence. let first_sentence = novel.split('.') .next().expect("No period detected!"); // We have annotated the lifetime to be dependent of part. // If first_sentence goes out of scope, so does quote. let quote = Quote { part: first_sentence, };} // All will be deallocated
在Python中非常常見的一點(diǎn)在Rust中是不可能的:擁有一個(gè)值被設(shè)置為 None。這是一個(gè)刻意的設(shè)計(jì)選擇,符合Rust的安全性、可預(yù)測(cè)性和零成本抽象的目標(biāo)。安全性方面與Rust的所有權(quán)、借用和生命周期方面相似:防止引用指向未分配的內(nèi)存的可能性。通過不給予返回 None 的可能性,將導(dǎo)致更可預(yù)測(cè)性,因?yàn)樗鼜?qiáng)迫開發(fā)者明確處理數(shù)字可能不存在的情況。由于內(nèi)存安全和可預(yù)測(cè)的行為,Rust可以在不犧牲性能的情況下實(shí)現(xiàn)其所有高級(jí)語(yǔ)言功能。
僅僅拒絕 None 會(huì)使 Rust 變得糟糕,因此,創(chuàng)建者提出了一個(gè)不錯(cuò)的替代方案:枚舉 Option 和 Result。通過這些枚舉,我們可以明確表示值的存在或不存在。它還使錯(cuò)誤處理變得非常優(yōu)雅。讓我們考慮 Source 5 中使用 Option 的一個(gè)示例。
fn divide(x: f64, y: f64) -> Option<f64> { if y == 0.0 { None } else { Some(x / y) }} fn main() { let result = divide(10.0, 2.0); match result { Some(value) => println!("Result: {}", value), None => println!("Cannot divide by zero!"), }}
等一下!你不是說(shuō)沒有 None 嗎?這也是我第一次被欺騙的地方,但在這里,None 是一個(gè)不帶參數(shù)的特殊枚舉結(jié)構(gòu)。同樣,Some 也是一個(gè)特殊的結(jié)構(gòu),但它可以帶一個(gè)參數(shù)。我們的 divide() 函數(shù)返回這些可能的枚舉值之一,我們稍后可以檢查它是什么并采取相應(yīng)的操作。
沒有 None 并強(qiáng)制返回值使得 Rust 變得非常可預(yù)測(cè)。
主函數(shù)使用 match 結(jié)構(gòu)進(jìn)行結(jié)果處理,這非常方便。這在某種程度上類似于其他語(yǔ)言中的 switch/case 構(gòu)造,除了 Python(見圖2中Guido的回應(yīng))。match 檢查是枚舉 Some 還是枚舉 None,并執(zhí)行相應(yīng)的操作。
Option 枚舉是用于可以返回值或不返回值的函數(shù)的特殊結(jié)構(gòu)。對(duì)于可以返回值或錯(cuò)誤的函數(shù),Rust 還有一個(gè)更明確的枚舉,稱為 Result。思想完全相同,主要區(qū)別在于 Option 有一個(gè)默認(rèn)的“錯(cuò)誤”值 None,而 Result 需要一個(gè)顯式的“錯(cuò)誤”類型。在 Source 6 中,divide 函數(shù)使用 Result 重寫。
fn divide(x: f64, y: f64) -> Result<f64, &'static str> { if y == 0.0 { Err("Cannot divide by zero!") } else { Ok(x / y) }} fn main() { let result = divide(10.0, 0.0); match result { Ok(value) => println!("Result: {}", value), Err(err) => println!("Error: {}", err), }}
Rust的開發(fā)者們看到match結(jié)構(gòu)有時(shí)可能有點(diǎn)繁瑣,因此添加了if let和while let運(yùn)算符。這些運(yùn)算符類似于match,但通過一些美味的糖分提供了一些不錯(cuò)的語(yǔ)法糖。甚至還有一個(gè)非常酷的?運(yùn)算符(此處未顯示),為美味的糖分添加了一顆櫻桃!
let mut values = vec![Some(1), Some(2), None, Some(3)];while let Some(value) = values.pop() { if let Some(inner_value) = value { println!("Popped: {}", inner_value); } else { println!("Found None"); }}
使用Python時(shí),我學(xué)會(huì)了使用Optional關(guān)鍵字為結(jié)果類型化,可以是值,也可以是None。但我不得不承認(rèn)Rust非常巧妙地解決了這一部分。我可以想象Python社區(qū)也會(huì)朝著這種風(fēng)格發(fā)展,類似于強(qiáng)(更強(qiáng))類型化的趨勢(shì)。
Python和Rust都可以用于兩種編程范式:函數(shù)式編程(FP)和面向?qū)ο缶幊蹋∣OP)。但是Rust在實(shí)現(xiàn)這些所謂的對(duì)象的方式上有所不同。在Python中,我們有一個(gè)典型的類對(duì)象,我們可以將變量和方法與之關(guān)聯(lián)。與許多其他語(yǔ)言(如Java)一樣,我們現(xiàn)在可以將這個(gè)方法用作基礎(chǔ),并通過創(chuàng)建繼承方法和變量的新對(duì)象來(lái)擴(kuò)展功能。
在Rust中,沒有class關(guān)鍵字,對(duì)象與Python基本不同。Rust使用Trait系統(tǒng)進(jìn)行代碼重用和多態(tài)性,這可以提供與多重繼承相同的好處,但不會(huì)出現(xiàn)與多重繼承相關(guān)的問題。多重繼承通常用于將多個(gè)類的各種功能組合或共享,但它可能使代碼變得復(fù)雜和模糊。一個(gè)著名的問題是所謂的菱形問題,見Source 8。
class A: def method(self): print("Method in class A")class B(A): def method(self): print("Method in class B")class C(A): def method(self): print("Method in class C") class D(B, C): passobj = D()obj.method() # Ambiguity arises here
盡管我認(rèn)為我們可以輕松地解決這個(gè)問題,但如果我要?jiǎng)?chuàng)建一種新語(yǔ)言,我也會(huì)嘗試以不同的方式解決這個(gè)問題。對(duì)于多重繼承,目標(biāo)主要是與其他對(duì)象共享類似的功能。在Rust中,使用Trait系統(tǒng)更加優(yōu)雅地實(shí)現(xiàn)了這一點(diǎn)。這種方法不僅在Rust中使用,在Scala、Kotlin和Haskell等語(yǔ)言中也有類似的系統(tǒng)。
在Rust中,類是由Enums和Structs創(chuàng)建的。就它們自身而言,它們只是數(shù)據(jù)結(jié)構(gòu),但我們可以向這些類添加功能。我們可以直接這樣做,然而,通過使用traits,這些功能可以與多個(gè)“類”共享。使用traits的一個(gè)重要好處是我們可以事先檢查某個(gè)trait是否已實(shí)現(xiàn)。請(qǐng)看以下示例:
// Define a trait for characters that can speaktrait Speaker { fn speak(&self);}// Implement the Speaker trait for a Jedistruct Jedi { name: String,}impl Speaker for Jedi { fn speak(&self) { println!("{} says: May the Force be with you.", self.name); }}// Implement the Speaker trait for a Droidstruct Droid { model: String,}impl Speaker for Droid { fn speak(&self) { println!("{} says: Beep boop beep.", self.model); }}// Function that takes any type implementing the Speaker traitfn introduce(character: &dyn Speaker) { character.speak();}fn main() { let obi_wan = Jedi { name: String::from("Obi-Wan Kenobi"), }; let r2d2 = Droid { model: String::from("R2-D2"), }; // Call the introduce function with instances of Jedi and Droid introduce(&obi_wan); introduce(&r2d2);}
在這個(gè)例子中,我們有一個(gè)Speaker trait,代表可以說(shuō)話的角色。我們?yōu)閮煞N類型實(shí)現(xiàn)了這個(gè)trait:Jedi和Droid。每種類型都提供了自己的speak方法的實(shí)現(xiàn)。introduce函數(shù)接受任何實(shí)現(xiàn)Speaker trait的類型,并調(diào)用speak方法。在主函數(shù)中,我們創(chuàng)建了Jedi(奧比-萬(wàn)·克諾比)和Droid(R2-D2)的實(shí)例,并將它們傳遞給introduce函數(shù),展示了多態(tài)性。
對(duì)于我這個(gè)Pythonista 來(lái)說(shuō),Rust的trait系統(tǒng)曾經(jīng)非常令人困惑。花了一些時(shí)間我才欣賞到其語(yǔ)法的優(yōu)雅之處。
Rust是一門非常酷的語(yǔ)言,但絕對(duì)不是一門容易學(xué)習(xí)的語(yǔ)言。Rustlings課程向我展示了一些基礎(chǔ)知識(shí),但我遠(yuǎn)遠(yuǎn)不熟練到能夠承擔(dān)大型項(xiàng)目的程度。但我真的很喜歡Rust是如何迫使你編寫更好、更安全的代碼的。
Python仍然是我的日常首選。在工作中,我們的文檔流水線完全由Python構(gòu)建,而且在機(jī)器學(xué)習(xí)領(lǐng)域,我并沒有看到一切都轉(zhuǎn)向另一種語(yǔ)言。Python太容易學(xué)習(xí)了,即使你是一個(gè)糟糕的開發(fā)者,也能完成工作。
然而,有一些小的動(dòng)向朝著Rust。當(dāng)然,一些包如Polars和Pydantic是使用Rust構(gòu)建的,而HuggingFace也發(fā)布了他們自己用Rust構(gòu)建的第一個(gè)版本的名為Candle的機(jī)器學(xué)習(xí)框架。因此,我認(rèn)為學(xué)習(xí)一點(diǎn)Rust并不是一個(gè)壞主意!
本文鏈接:http://www.www897cc.com/showinfo-26-89710-0.htmlPython vs. Rust:打破三大障礙
聲明:本網(wǎng)頁(yè)內(nèi)容旨在傳播知識(shí),若有侵權(quán)等問題請(qǐng)及時(shí)與本網(wǎng)聯(lián)系,我們將在第一時(shí)間刪除處理。郵件:2376512515@qq.com