定義與實例化結構體
結構體(Structs)和我們在第三章討論過的元組類似。和元組一樣,結構體的每個部分可以是不同的型別。但與元組不同的地方是,你必須為每個資料部分命名以便表達每個數值的意義。因為有了這些名稱,結構體通常比元組還來的有彈性:你不需要依賴資料的順序來指定或存取實例中的值。
欲定義結構體,我們輸入關鍵字 struct
並為整個結構體命名。結構體的名稱需要能夠描述其所組合出的資料意義。然後在大括號內,我們對每個資料部分定義名稱與型別,我們會稱為欄位(fields)。舉例來說,範例 5-1 定義了一個儲存使用者帳號的結構體。
struct User { username: String, email: String, sign_in_count: u64, active: bool, } fn main() {}
範例 5-1:User
結構體定義
要在我們定義後使用該結構體,我們可以指定每個欄位的實際數值來建立結構體的實例(instance)。要建立實例的話,我們先寫出結構體的名稱再加上大括號,裡面會包含數個 key: value
的配對。key
是每個欄位的名稱,而 value
就是你想給予欄位的數值。欄位的順序可以不用和定義結構體時的順序一樣。換句話說,結構體的定義比較像是型別的通用樣板,然後實例會依據此樣板插入特定資料來將產生型別的數值。比如說,我們可以像範例 5-2 這樣宣告一個特定使用者。
struct User { username: String, email: String, sign_in_count: u64, active: bool, } fn main() { let user1 = User { email: String::from("[email protected]"), username: String::from("someusername123"), active: true, sign_in_count: 1, }; }
範例 5-2:產生一個 User
結構體的實例
要取得結構體中特定數值的話,我們可以使用句點。如果我們只是想要此使用者的電子郵件信箱,我們可以在任何我們想使用此值的地方使用 user1.email
。如果該實例可變的話,我們可以使用句點並賦值給該欄位來改變其值。範例 5-3 顯示了如何改變一個可變 User
實例中 email
欄位的值。
struct User { username: String, email: String, sign_in_count: u64, active: bool, } fn main() { let mut user1 = User { email: String::from("[email protected]"), username: String::from("someusername123"), active: true, sign_in_count: 1, }; user1.email = String::from("[email protected]"); }
範例 5-3:改變 User
中 email
欄位的值
請注意整個實例必須是可變的,Rust 不允許我們只標記特定欄位是可變的。再來,就像任何表達式一樣,我們可以在函式本體最後的表達式中,建立一個新的結構體實例作為回傳值。
範例 5-4 展示了 build_user
函式會依據給予的電子郵件和使用者名稱來回傳 User
實例。而 active
欄位取得數值 true
且 sign_in_count
取得數值 1
。
struct User { username: String, email: String, sign_in_count: u64, active: bool, } fn build_user(email: String, username: String) -> User { User { email: email, username: username, active: true, sign_in_count: 1, } } fn main() { let user1 = build_user( String::from("[email protected]"), String::from("someusername123"), ); }
範例 5-4:build_user
函式取得電子郵件與使用者名稱並回傳 User
實例
函式參數名稱與結構體欄位名稱相同是非常合理的,但是要重複寫 email
和 username
的欄位名稱與變數就有點冗長了。如果結構體有更多欄位的話,重複寫這些名稱就顯得有些煩人了。幸運的是,我們的確有更方便的語法!
當變數與欄位名稱相同時使用欄位初始化簡寫語法
由於範例 5-4 的參數名稱與結構體欄位名稱相同,我們可以使用欄位初始化簡寫(field init shorthand)語法來重寫 build_user
,讓它的結果相同但不必重複寫出 email
和 username
,如範例 5-5 所示。
struct User { username: String, email: String, sign_in_count: u64, active: bool, } fn build_user(email: String, username: String) -> User { User { email, username, active: true, sign_in_count: 1, } } fn main() { let user1 = build_user( String::from("[email protected]"), String::from("someusername123"), ); }
範例 5-5:build_user
函式使用欄位初始化簡寫,因為參數 email
與 username
結構體欄位相同
在此我們建立了 User
結構體的實例,它有一個欄位叫做 email
。我們希望用 build_user
函式中的參數 email
賦值給 email
欄位。然後因為 email
欄位與 email
參數有相同的名稱,我們只要寫 email
就好,不必寫 email: email
。
使用結構體更新語法從其他結構體建立實例
通常我們也會從舊的實例來產生新的實例,不過修改一些欄位數值,這時你可以使用結構體更新語法(struct update syntax)。
首先範例 5-6 顯示了我們沒有使用更新語法來建立新的 User
實例 user2
。我們設置了新的數值給 email
和 username
,但其他欄位就使用我們在範例 5-2 建立的 user1
相同的值。
struct User { username: String, email: String, sign_in_count: u64, active: bool, } fn main() { let user1 = User { email: String::from("[email protected]"), username: String::from("someusername123"), active: true, sign_in_count: 1, }; let user2 = User { email: String::from("[email protected]"), username: String::from("anotherusername567"), active: user1.active, sign_in_count: user1.sign_in_count, }; }
範例 5-6:從 user1
一些值中建立新的 User
實例
使用結構體更新語法,我們可以用較少的程式碼達到相同的效果,如範例 5-7 所示。..
語法表示剩下沒指明的欄位都會取得與所提供的實例相同的值。
struct User { username: String, email: String, sign_in_count: u64, active: bool, } fn main() { let user1 = User { email: String::from("[email protected]"), username: String::from("someusername123"), active: true, sign_in_count: 1, }; let user2 = User { email: String::from("[email protected]"), username: String::from("anotherusername567"), ..user1 }; }
範例 5-7:對新的 User
實例設置新的 email
和 username
數值,但其他欄位使用與 user1
變數實例中欄位相同的值
範例 5-7 的程式碼一樣產生了有不同 email
和 username
的 user2
實例,但是有與 user1
相同的 active
和 sign_in_count
。
使用無名稱欄位的元組結構體來建立不同型別
你還可以定義結構體讓它長得像是元組那樣,我們稱作元組結構體(tuple structs)。元組結構體仍然有定義整個結構的名稱,但是它們的欄位不會有名稱,它們只會有欄位型別而已。元組結構體的用途在於當你想要為元組命名,好讓它跟其他不同型別的元組作出區別,以及對常規結構體每個欄位命名是冗長且不必要的時候。
要定義一個元組結構體,一樣先從 struct
關鍵字開始,其後再接著要定義的元組。舉例來說,以下是兩個使用元組結構體定義的 Color
和 Point
:
fn main() { struct Color(i32, i32, i32); struct Point(i32, i32, i32); let black = Color(0, 0, 0); let origin = Point(0, 0, 0); }
注意 black
與 origin
屬於不同型別,因為它們是不同的元組結構體實例。每個你定義的結構體都是專屬於自己的型別,就算它們的欄位型別一摸一樣。舉例來說,一個參數為 Color
的函式就無法接受 Point
引數,就算它們的型別都是三個 i32
的組合。除此之外,元組結構體實例的行為和元組類似:你可以將它們解構為獨立部分,你也可以使用 .
加上索引來取得每個數值等等。
無任何欄位的類單元結構體
你也可以定義沒有任何欄位的結構體!這些叫做類單元結構體(unit-like structs),因為它們的行為就很像 ()
單元型別(unit type)。類單元結構體很適合用在當你要實作一個特徵(trait)或某種型別,但你沒有任何需要儲存在型別中的資料。我們會在第十章討論特徵。
結構體資料的所有權
在範例 5-1 的
User
結構體定義中,我們使用了擁有所有權的String
型別,而不是&str
字串切片型別。這邊是故意這樣選擇的,因為我們希望解構體的實例可以擁有它所有的資料,並在整個結構體都有效時資料也是有效的。要在結構體中儲存別人擁有的資料引用是可行的,但這會用到生命週期(lifetimes),我們在第十章才會談到。生命週期能確保資料引用在結構體存在期間都是有效的。要是你沒有使用生命週期來用結構體儲存引用的話,會如以下出錯:
檔案名稱:src/main.rs
struct User { username: &str, email: &str, sign_in_count: u64, active: bool, } fn main() { let user1 = User { email: "[email protected]", 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:2:15 | 2 | username: &str, | ^ expected lifetime parameter error[E0106]: missing lifetime specifier --> src/main.rs:3:12 | 3 | email: &str, | ^ expected lifetime parameter error: aborting due to 2 previous errors For more information about this error, try `rustc --explain E0106`. error: could not compile `structs`. To learn more, run the command again with --verbose.
在第十章,我們將會討論如何修正這樣的錯誤,好讓你可以在結構體中儲存引用。但現在的話,我們先用有所有權的
String
而非&str
引用來避免錯誤。
- translators: [Ngô͘ Io̍k-ūi [email protected]]
- commit: d44317c
- updated: 2020-09-10