Rust 元組的實際用例

元組是任意數量的不同類型值的集合,當函數必須返回多個結果時,它們就很有用。在本文中,我們將深入研究 Rust 中的元組,特別是與 Rust struct 的不同之處。

Rust 元組概述

Rust 的文檔將元組定義爲值的 “有限異構序列”:

創建元組只需在圓括號內寫入逗號分隔的值列表,並可選擇指定類型:

1fn main() {
2    let immutable: (String, u8) = (String::from("LogRocket"), 120);
3    println!("{} {}", immutable.0, immutable.1);
4}

在上面的例子中,我們創建了一個簡單的名爲 immutable 的元組,其中第一個值是 String,第二個值是 8 位的無符號整數。然後,我們通過從 0 開始按索引訪問元組的元素來打印它們。運行時,示例打印 “LogRocket 120”。

元組在默認情況下是不可變的。例如,如果我們試圖將一個新值賦給上面元組的一個元素,我們會得到一個錯誤:

1fn main() {
2    let immutable: (String, u8) = (String::from("LogRocket"), 120);
3    immutable.1 = 142; // cannot assign
4}

爲了將元組定義爲 mutable,我們必須在聲明時指定 mut:

1fn main() {
2    let mut mutable = (String::from("LogRocket"), 120);
3    mutable.1 = 142;
4    println!("{} {}", mutable.0, mutable.1);
5}

在任何一種情況下,我們都可以解構一個元組來初始化其他變量:

1fn main() {
2    let mut mutable = (String::from("LogRocket"), 120);
3    let (name, value) = mutable;
4    println!("{name}");
5}

上面的程序只打印 “LogRocket”。

最後,我們可以使用 type 爲元組類型分配別名:

1type Test = (String, u8);
2
3fn main() {
4    let immutable: Test = (String::from("LogRocket"), 120);
5    println!("{} {}", immutable.0, immutable.1);
6}

這使得代碼更具可讀性。然而,重要的是要注意,我們用 type 定義的只是一個別名。上面的 test 和 (String, u8) 在結構上是等效的。

結構體和元組結構體

在本節中,我們將介紹元組的兩個替代方案,即結構體和元組結構體。如果元組是具有匿名字段的匿名類型,則結構體是具有命名字段的命名類型。元組結構體處於它們的中間,因爲它爲我們提供了帶有匿名字段的命名類型。

結構體

結構體與元組相似,它們可以處理不同類型的多個值。主要的區別是結構體的元素都有一個名稱。因此,我們不是通過索引訪問它們,而是通過名稱訪問它們:

 1struct Person {
 2    name: String,
 3    age: u8
 4}
 5
 6fn main() {
 7    let p = Person {
 8        name: String::from("Jhon Doe"),
 9        age: 21
10    };
11
12    println!("Name: {}, age: {}", p.name, p.age);
13}

一般來說,爲了可讀性,使用結構體比使用元組更可取。然而,這並不總是正確的,我們將在下一節中看到。

元組結構體

元組結構體和結構體是一樣的,唯一的區別是我們不需要命名它們的字段:

1struct Wrapper(u8);
2
3fn main() {
4    let w = Wrapper(10);
5    println!("{}", w.0);
6}

由於字段沒有名稱,所以必須通過索引訪問它們。在上面的例子中,我們定義了一個結構體來包裝 u8。然後,爲了讀寫它,我們必須使用它的索引 0 來引用它。

比較元組、結構體和元組結構體

本節比較元組、結構體和元組結構體。首先,我們來看看幾個關鍵的差別。然後,我們將看到它們各自的實際用例。

實現區別

由於元組結構體只是沒有字段名稱的結構體,在本節中,我們將主要比較元組和元組結構體。我們在元組結構體中看到的也適用於結構體。事實上,元組結構體可以轉化爲規則結構體。

首先,元組結構體的元素在默認情況下是私有的,不能在定義它們的模塊之外訪問。此外,元組結構體定義了類型。因此,具有相同類型字段的兩個元組結構體是兩種不同的類型。

另一方面,元素類型相同的兩個元組是同一個類型。換句話說,元組在結構上是等價的,而元組結構體則不是。

其次,我們可以向元組結構體添加屬性,例如 #must_use 或 #[derived(…)](用於在結構體中自動實現 trait)。另一方面,元組不能有屬性,但在默認情況下確實實現了一些特徵。

第三,元組結構體默認實現 Fn * 特徵,允許它們像函數一樣被調用或作爲參數傳遞給高階函數,如:

1struct Name(String);
2
3fn main() {
4    let strings = [String::from("Log"), String::from("Rocket")];
5
6    let names = strings.map(Name);
7}

此外,元組結構體支持結構體更新語法,簡化了我們創建結構體新實例的方式,其中大多數值等於同一結構體的另一個實例:

 1struct Person(String, u8);
 2
 3fn main() {
 4    let john = Person {
 5            0: String::from("John"),
 6            1: 32
 7    };
 8    let another_john = Person {
 9            1: 25,
10            ..john
11    };
12
13    println!("name: {}, age: {}", another_john.0, another_john.1);
14}

在上面的例子中,我們首先創建了 Person 的一個實例,john,然後用它來創建一個新的實例,another_john,只編輯 Person 定義的字段的子集。

最後,我們可以在元組結構體上定義方法,而不能在元組上定義方法:

 1struct Person(String, u8);
 2
 3impl Person {
 4    fn is_adult(&self) -> bool {
 5            return &self.1 >= &18;
 6    }
 7}
 8
 9fn main() {
10    let p = Person {
11            0: String::from("John"),
12            1: 20
13    };
14
15    println!("{}", p.is_adult());
16}

實際用例

根據經驗,只要類型名帶有語義信息,我們就應該使用元組結構體。然後,當有多個字段時,我們應該轉移到結構體。

儘管如此,在某些情況下,沒有名稱更容易讀懂。例如,slice 的 as_chunks 方法定義如下:

1pub fn as_chunks<const N: usize>(&self) -> (&[[T; N]]&[T])

它基本上輸入一個常數 N,定義塊的大小,並返回一個元組,其中第一個元素是大小爲 N 的塊的數組,而第二個元素是數組的餘數 (也就是說,最後一個不足以組成一個新塊的值)。

在本例中,類型本身明確了結果的每個元素代表什麼。因此,有名字可能會有點過分。在這種情況下,元組是一個很好的解決方案。

儘管如此,我們通常處理複雜的數據類型,其中有名稱有助於我們閱讀代碼。例如,考慮一個處理圖書信息的程序,特別是書名、作者和價格。如果我們用一個元組來建模,我們可能會得到:

1type Book = (String, String, f32)

但是,我們的 Book 定義包含兩個 String 字段。我們怎麼知道哪個代表標題,哪個代表作者?在這種情況下,結構體是更好的選擇:

1struct Book {
2    title: String,
3    author: String,
4    price: f32
5}

不過,有時我們可能會發現自己使用的結構體只有一個元素。例如,使用類型來進一步增強代碼的清晰度。考慮一個用三個組件構建 URL 的函數:

首先,我們可能會提出一個具有以下簽名的函數:

 1fn make_url(
 2    subdomain: String,
 3    domain_name: String,
 4    top_level_domain: String
 5) -> String {
 6    todo!();
 7}
 8
 9fn main() {
10    make_url(
11        String::from("www"),
12        String::from("mydomain"),
13        String::from("com")
14    );
15}

在這裏,在一個結構中有很多字符串,但是,這很容易混淆參數的順序。此外,Rust 不支持命名參數,所以我們必須記住正確的順序。

使用元組結構體,我們可以編寫一個更加自解釋的簽名:

 1struct Subdomain(String);
 2struct DomainName(String);
 3struct TopLevelDomain(String);
 4struct URL(String);
 5
 6fn make_url(
 7    subdomain: Subdomain,
 8    domain_name: DomainName,
 9    top_level_domain: TopLevelDomain
10) -> URL {
11    todo!();
12}
13
14fn main() {
15    make_url(
16            Subdomain(String::from("www")),
17            DomainName(String::from("mydomain")),
18            TopLevelDomain(String::from("com"))
19    );
20}

在上面的例子中,我們利用元組結構來包裝字符串。

總結

在本文中,我們深入研究 Rust 中的元組。首先,我們簡要地瞭解了它們是什麼以及如何使用它們。其次,我們簡要介紹了可能的替代,即結構體和元組結構體。最後,我們查看比較了實現細節以及每個選項的實際用例。真正重要的選擇是它們如何影響代碼的可讀性和可維護性。

本文翻譯自:

https://blog.logrocket.com/practical-use-cases-rust-tuples/


本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源https://mp.weixin.qq.com/s/p_iFUczLRGvY064uEXKYSw