Rust 結構體的定義和實例化

2023-03-22 15:09 更新
ch05-01-defining-structs.md
commit dd7e05275822d6cf790bcdae6983b3234141b5e7

結構體和我們在“元組類型”部分論過的元組類似,它們都包含多個相關的值。和元組一樣,結構體的每一部分可以是不同類型。但不同于元組,結構體需要命名各部分數(shù)據(jù)以便能清楚的表明其值的意義。由于有了這些名字,結構體比元組更靈活:不需要依賴順序來指定或訪問實例中的值。

定義結構體,需要使用 struct 關鍵字并為整個結構體提供一個名字。結構體的名字需要描述它所組合的數(shù)據(jù)的意義。接著,在大括號中,定義每一部分數(shù)據(jù)的名字和類型,我們稱為 字段field)。例如,示例 5-1 展示了一個存儲用戶賬號信息的結構體:

struct User {
    active: bool,
    username: String,
    email: String,
    sign_in_count: u64,
}

示例 5-1:User 結構體定義

一旦定義了結構體后,為了使用它,通過為每個字段指定具體值來創(chuàng)建這個結構體的 實例。創(chuàng)建一個實例需要以結構體的名字開頭,接著在大括號中使用 key: value 鍵-值對的形式提供字段,其中 key 是字段的名字,value 是需要存儲在字段中的數(shù)據(jù)值。實例中字段的順序不需要和它們在結構體中聲明的順序一致。換句話說,結構體的定義就像一個類型的通用模板,而實例則會在這個模板中放入特定數(shù)據(jù)來創(chuàng)建這個類型的值。例如,可以像示例 5-2 這樣來聲明一個特定的用戶:

fn main() {
    let user1 = User {
        email: String::from("someone@example.com"),
        username: String::from("someusername123"),
        active: true,
        sign_in_count: 1,
    };
}

示例 5-2:創(chuàng)建 User 結構體的實例

為了從結構體中獲取某個特定的值,可以使用點號。舉個例子,想要用戶的郵箱地址,可以用 user1.email。如果結構體的實例是可變的,我們可以使用點號并為對應的字段賦值。示例 5-3 展示了如何改變一個可變的 User 實例中 email 字段的值:

fn main() {
    let mut user1 = User {
        email: String::from("someone@example.com"),
        username: String::from("someusername123"),
        active: true,
        sign_in_count: 1,
    };

    user1.email = String::from("anotheremail@example.com");
}

示例 5-3:改變 User 實例 email 字段的值

注意整個實例必須是可變的;Rust 并不允許只將某個字段標記為可變。另外需要注意同其他任何表達式一樣,我們可以在函數(shù)體的最后一個表達式中構造一個結構體的新實例,來隱式地返回這個實例。

示例 5-4 顯示了一個 build_user 函數(shù),它返回一個帶有給定的 email 和用戶名的 User 結構體實例。active 字段的值為 true,并且 sign_in_count 的值為 1。

fn build_user(email: String, username: String) -> User {
    User {
        email: email,
        username: username,
        active: true,
        sign_in_count: 1,
    }
}

示例 5-4:build_user 函數(shù)獲取 email 和用戶名并返回 User 實例

為函數(shù)參數(shù)起與結構體字段相同的名字是可以理解的,但是不得不重復 email 和 username 字段名稱與變量有些啰嗦。如果結構體有更多字段,重復每個名稱就更加煩人了。幸運的是,有一個方便的簡寫語法!

使用字段初始化簡寫語法

因為示例 5-4 中的參數(shù)名與字段名都完全相同,我們可以使用 字段初始化簡寫語法field init shorthand)來重寫 build_user,這樣其行為與之前完全相同,不過無需重復 email 和 username 了,如示例 5-5 所示。

fn build_user(email: String, username: String) -> User {
    User {
        email,
        username,
        active: true,
        sign_in_count: 1,
    }
}

示例 5-5:build_user 函數(shù)使用了字段初始化簡寫語法,因為 email 和 username 參數(shù)與結構體字段同名

這里我們創(chuàng)建了一個新的 User 結構體實例,它有一個叫做 email 的字段。我們想要將 email 字段的值設置為 build_user 函數(shù) email 參數(shù)的值。因為 email 字段與 email 參數(shù)有著相同的名稱,則只需編寫 email 而不是 email: email。

使用結構體更新語法從其他實例創(chuàng)建實例

使用舊實例的大部分值但改變其部分值來創(chuàng)建一個新的結構體實例通常是很有用的。這可以通過 結構體更新語法struct update syntax)實現(xiàn)。

首先,示例 5-6 展示了不使用更新語法時,如何在 user2 中創(chuàng)建一個新 User 實例。我們?yōu)?nbsp;email 設置了新的值,其他值則使用了實例 5-2 中創(chuàng)建的 user1 中的同名值:

fn main() {
    // --snip--

    let user2 = User {
        active: user1.active,
        username: user1.username,
        email: String::from("another@example.com"),
        sign_in_count: user1.sign_in_count,
    };
}

示例 5-6:使用 user1 中的一個值創(chuàng)建一個新的 User 實例

使用結構體更新語法,我們可以通過更少的代碼來達到相同的效果,如示例 5-7 所示。.. 語法指定了剩余未顯式設置值的字段應有與給定實例對應字段相同的值。

fn main() {
    // --snip--

    let user2 = User {
        email: String::from("another@example.com"),
        ..user1
    };
}

示例 5-7:使用結構體更新語法為一個 User 實例設置一個新的 email 值,不過其余值來自 user1 變量中實例的字段

示例 5-7 中的代碼也在 user2 中創(chuàng)建了一個新實例,但該實例中 email 字段的值與 user1 不同,而 username、 active 和 sign_in_count 字段的值與 user1 相同。..user1 必須放在最后,以指定其余的字段應從 user1 的相應字段中獲取其值,但我們可以選擇以任何順序為任意字段指定值,而不用考慮結構體定義中字段的順序。

請注意,結構更新語法就像帶有 = 的賦值,因為它移動了數(shù)據(jù),就像我們在“變量與數(shù)據(jù)交互的方式(一):移動”部分講到的一樣。在這個例子中,我們在創(chuàng)建 user2 后不能再使用 user1,因為 user1 的 username 字段中的 String 被移到 user2 中。如果我們給 user2 的 email 和 username 都賦予新的 String 值,從而只使用 user1 的 active 和 sign_in_count 值,那么 user1 在創(chuàng)建 user2 后仍然有效。active 和 sign_in_count 的類型是實現(xiàn) Copy trait 的類型,所以我們在“變量與數(shù)據(jù)交互的方式(二):克隆” 部分討論的行為同樣適用。

使用沒有命名字段的元組結構體來創(chuàng)建不同的類型

也可以定義與元組(在第三章討論過)類似的結構體,稱為 元組結構體tuple structs)。元組結構體有著結構體名稱提供的含義,但沒有具體的字段名,只有字段的類型。當你想給整個元組取一個名字,并使元組成為與其他元組不同的類型時,元組結構體是很有用的,這時像常規(guī)結構體那樣為每個字段命名就顯得多余和形式化了。

要定義元組結構體,以 struct 關鍵字和結構體名開頭并后跟元組中的類型。例如,下面是兩個分別叫做 Color 和 Point 元組結構體的定義和用法:

struct Color(i32, i32, i32);
struct Point(i32, i32, i32);

fn main() {
    let black = Color(0, 0, 0);
    let origin = Point(0, 0, 0);
}

注意 black 和 origin 值的類型不同,因為它們是不同的元組結構體的實例。你定義的每一個結構體有其自己的類型,即使結構體中的字段可能有著相同的類型。例如,一個獲取 Color 類型參數(shù)的函數(shù)不能接受 Point 作為參數(shù),即便這兩個類型都由三個 i32 值組成。在其他方面,元組結構體實例類似于元組,你可以將它們解構為單獨的部分,也可以使用 . 后跟索引來訪問單獨的值,等等。

沒有任何字段的類單元結構體

我們也可以定義一個沒有任何字段的結構體!它們被稱為 類單元結構體unit-like structs)因為它們類似于 (),即“元組類型”一節(jié)中提到的 unit 類型。類單元結構體常常在你想要在某個類型上實現(xiàn) trait 但不需要在類型中存儲數(shù)據(jù)的時候發(fā)揮作用。我們將在第十章介紹 trait。下面是一個聲明和實例化一個名為 AlwaysEqual 的 unit 結構的例子。

struct AlwaysEqual;

fn main() {
    let subject = AlwaysEqual;
}

要定義 AlwaysEqual,我們使用 struct 關鍵字,我們想要的名稱,然后是一個分號。不需要花括號或圓括號!然后,我們可以以類似的方式在 subject 變量中獲得 AlwaysEqual 的實例:使用我們定義的名稱,不需要任何花括號或圓括號。想象一下,我們將實現(xiàn)這個類型的行為,即每個實例始終等于每一個其他類型的實例,也許是為了獲得一個已知的結果以便進行測試。我們不需要任何數(shù)據(jù)來實現(xiàn)這種行為,你將在第十章中,看到如何定義特性并在任何類型上實現(xiàn)它們,包括類單元結構體。

結構體數(shù)據(jù)的所有權

在示例 5-1 中的 User 結構體的定義中,我們使用了自身擁有所有權的 String 類型而不是 &str 字符串 slice 類型。這是一個有意而為之的選擇,因為我們想要這個結構體擁有它所有的數(shù)據(jù),為此只要整個結構體是有效的話其數(shù)據(jù)也是有效的。

可以使結構體存儲被其他對象擁有的數(shù)據(jù)的引用,不過這么做的話需要用上 生命周期lifetimes),這是一個第十章會討論的 Rust 功能。生命周期確保結構體引用的數(shù)據(jù)有效性跟結構體本身保持一致。如果你嘗試在結構體中存儲一個引用而不指定生命周期將是無效的,比如這樣:

文件名: src/main.rs

struct User {
    active: bool,
    username: &str,
    email: &str,
    sign_in_count: u64,
}

fn main() {
    let user1 = User {
        email: "someone@example.com",
        username: "someusername123",
        active: true,
        sign_in_count: 1,
    };
}

編譯器會抱怨它需要生命周期標識符:

$ cargo run
   Compiling structs v0.1.0 (file:///projects/structs)
error[E0106]: missing lifetime specifier
 --> src/main.rs:3:15
  |
3 |     username: &str,
  |               ^ expected named lifetime parameter
  |
help: consider introducing a named lifetime parameter
  |
1 ~ struct User<'a> {
2 |     active: bool,
3 ~     username: &'a str,
  |

error[E0106]: missing lifetime specifier
 --> src/main.rs:4:12
  |
4 |     email: &str,
  |            ^ expected named lifetime parameter
  |
help: consider introducing a named lifetime parameter
  |
1 ~ struct User<'a> {
2 |     active: bool,
3 |     username: &str,
4 ~     email: &'a str,
  |

For more information about this error, try `rustc --explain E0106`.
error: could not compile `structs` due to 2 previous errors

第十章會講到如何修復這個問題以便在結構體中存儲引用,不過現(xiàn)在,我們會使用像 String 這類擁有所有權的類型來替代 &str 這樣的引用以修正這個錯誤。


以上內容是否對您有幫助:
在線筆記
App下載
App下載

掃描二維碼

下載編程獅App

公眾號
微信公眾號

編程獅公眾號