ch05-01-defining-structs.md
commit dd7e05275822d6cf790bcdae6983b3234141b5e7
結(jié)構(gòu)體和我們?cè)?a href="http://www.15014759268.cn/rust_lang/rust_lang-pjuf3qap.html">“元組類型”部分論過(guò)的元組類似,它們都包含多個(gè)相關(guān)的值。和元組一樣,結(jié)構(gòu)體的每一部分可以是不同類型。但不同于元組,結(jié)構(gòu)體需要命名各部分?jǐn)?shù)據(jù)以便能清楚的表明其值的意義。由于有了這些名字,結(jié)構(gòu)體比元組更靈活:不需要依賴順序來(lái)指定或訪問(wèn)實(shí)例中的值。
定義結(jié)構(gòu)體,需要使用 struct
關(guān)鍵字并為整個(gè)結(jié)構(gòu)體提供一個(gè)名字。結(jié)構(gòu)體的名字需要描述它所組合的數(shù)據(jù)的意義。接著,在大括號(hào)中,定義每一部分?jǐn)?shù)據(jù)的名字和類型,我們稱為 字段(field)。例如,示例 5-1 展示了一個(gè)存儲(chǔ)用戶賬號(hào)信息的結(jié)構(gòu)體:
struct User {
active: bool,
username: String,
email: String,
sign_in_count: u64,
}
示例 5-1:User
結(jié)構(gòu)體定義
一旦定義了結(jié)構(gòu)體后,為了使用它,通過(guò)為每個(gè)字段指定具體值來(lái)創(chuàng)建這個(gè)結(jié)構(gòu)體的 實(shí)例。創(chuàng)建一個(gè)實(shí)例需要以結(jié)構(gòu)體的名字開頭,接著在大括號(hào)中使用 key: value
鍵-值對(duì)的形式提供字段,其中 key 是字段的名字,value 是需要存儲(chǔ)在字段中的數(shù)據(jù)值。實(shí)例中字段的順序不需要和它們?cè)诮Y(jié)構(gòu)體中聲明的順序一致。換句話說(shuō),結(jié)構(gòu)體的定義就像一個(gè)類型的通用模板,而實(shí)例則會(huì)在這個(gè)模板中放入特定數(shù)據(jù)來(lái)創(chuàng)建這個(gè)類型的值。例如,可以像示例
5-2 這樣來(lái)聲明一個(gè)特定的用戶:
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
結(jié)構(gòu)體的實(shí)例
為了從結(jié)構(gòu)體中獲取某個(gè)特定的值,可以使用點(diǎn)號(hào)。舉個(gè)例子,想要用戶的郵箱地址,可以用 user1.email
。如果結(jié)構(gòu)體的實(shí)例是可變的,我們可以使用點(diǎn)號(hào)并為對(duì)應(yīng)的字段賦值。示例 5-3 展示了如何改變一個(gè)可變的 User
實(shí)例中 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
實(shí)例 email
字段的值
注意整個(gè)實(shí)例必須是可變的;Rust 并不允許只將某個(gè)字段標(biāo)記為可變。另外需要注意同其他任何表達(dá)式一樣,我們可以在函數(shù)體的最后一個(gè)表達(dá)式中構(gòu)造一個(gè)結(jié)構(gòu)體的新實(shí)例,來(lái)隱式地返回這個(gè)實(shí)例。
示例 5-4 顯示了一個(gè) build_user
函數(shù),它返回一個(gè)帶有給定的 email 和用戶名的 User
結(jié)構(gòu)體實(shí)例。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ù)參數(shù)起與結(jié)構(gòu)體字段相同的名字是可以理解的,但是不得不重復(fù) email
和 username
字段名稱與變量有些啰嗦。如果結(jié)構(gòu)體有更多字段,重復(fù)每個(gè)名稱就更加煩人了。幸運(yùn)的是,有一個(gè)方便的簡(jiǎn)寫語(yǔ)法!
因?yàn)槭纠?5-4 中的參數(shù)名與字段名都完全相同,我們可以使用 字段初始化簡(jiǎn)寫語(yǔ)法(field init shorthand)來(lái)重寫 build_user
,這樣其行為與之前完全相同,不過(guò)無(wú)需重復(fù) 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ù)使用了字段初始化簡(jiǎn)寫語(yǔ)法,因?yàn)?nbsp;email
和 username
參數(shù)與結(jié)構(gòu)體字段同名
這里我們創(chuàng)建了一個(gè)新的 User
結(jié)構(gòu)體實(shí)例,它有一個(gè)叫做 email
的字段。我們想要將 email
字段的值設(shè)置為 build_user
函數(shù) email
參數(shù)的值。因?yàn)?nbsp;email
字段與 email
參數(shù)有著相同的名稱,則只需編寫 email
而不是 email: email
。
使用舊實(shí)例的大部分值但改變其部分值來(lái)創(chuàng)建一個(gè)新的結(jié)構(gòu)體實(shí)例通常是很有用的。這可以通過(guò) 結(jié)構(gòu)體更新語(yǔ)法(struct update syntax)實(shí)現(xiàn)。
首先,示例 5-6 展示了不使用更新語(yǔ)法時(shí),如何在 user2
中創(chuàng)建一個(gè)新 User
實(shí)例。我們?yōu)?nbsp;email
設(shè)置了新的值,其他值則使用了實(shí)例 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
中的一個(gè)值創(chuàng)建一個(gè)新的 User
實(shí)例
使用結(jié)構(gòu)體更新語(yǔ)法,我們可以通過(guò)更少的代碼來(lái)達(dá)到相同的效果,如示例 5-7 所示。..
語(yǔ)法指定了剩余未顯式設(shè)置值的字段應(yīng)有與給定實(shí)例對(duì)應(yīng)字段相同的值。
fn main() {
// --snip--
let user2 = User {
email: String::from("another@example.com"),
..user1
};
}
示例 5-7:使用結(jié)構(gòu)體更新語(yǔ)法為一個(gè) User
實(shí)例設(shè)置一個(gè)新的 email
值,不過(guò)其余值來(lái)自 user1
變量中實(shí)例的字段
示例 5-7 中的代碼也在 user2
中創(chuàng)建了一個(gè)新實(shí)例,但該實(shí)例中 email
字段的值與 user1
不同,而 username
、 active
和 sign_in_count
字段的值與 user1
相同。..user1
必須放在最后,以指定其余的字段應(yīng)從 user1
的相應(yīng)字段中獲取其值,但我們可以選擇以任何順序?yàn)槿我庾侄沃付ㄖ?,而不用考慮結(jié)構(gòu)體定義中字段的順序。
請(qǐng)注意,結(jié)構(gòu)更新語(yǔ)法就像帶有 =
的賦值,因?yàn)樗苿?dòng)了數(shù)據(jù),就像我們?cè)?a href="http://www.15014759268.cn/rust_lang/rust_lang-jywt3qau.html">“變量與數(shù)據(jù)交互的方式(一):移動(dòng)”部分講到的一樣。在這個(gè)例子中,我們?cè)趧?chuàng)建 user2
后不能再使用 user1
,因?yàn)?nbsp;user1
的 username
字段中的 String
被移到 user2
中。如果我們給 user2
的 email
和 username
都賦予新的 String
值,從而只使用 user1
的 active
和 sign_in_count
值,那么 user1
在創(chuàng)建 user2
后仍然有效。active
和 sign_in_count
的類型是實(shí)現(xiàn) Copy
trait
的類型,所以我們?cè)?a href="http://www.15014759268.cn/rust_lang/rust_lang-jywt3qau.html">“變量與數(shù)據(jù)交互的方式(二):克隆” 部分討論的行為同樣適用。
也可以定義與元組(在第三章討論過(guò))類似的結(jié)構(gòu)體,稱為 元組結(jié)構(gòu)體(tuple structs)。元組結(jié)構(gòu)體有著結(jié)構(gòu)體名稱提供的含義,但沒(méi)有具體的字段名,只有字段的類型。當(dāng)你想給整個(gè)元組取一個(gè)名字,并使元組成為與其他元組不同的類型時(shí),元組結(jié)構(gòu)體是很有用的,這時(shí)像常規(guī)結(jié)構(gòu)體那樣為每個(gè)字段命名就顯得多余和形式化了。
要定義元組結(jié)構(gòu)體,以 struct
關(guān)鍵字和結(jié)構(gòu)體名開頭并后跟元組中的類型。例如,下面是兩個(gè)分別叫做 Color
和 Point
元組結(jié)構(gòu)體的定義和用法:
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
值的類型不同,因?yàn)樗鼈兪遣煌脑M結(jié)構(gòu)體的實(shí)例。你定義的每一個(gè)結(jié)構(gòu)體有其自己的類型,即使結(jié)構(gòu)體中的字段可能有著相同的類型。例如,一個(gè)獲取 Color
類型參數(shù)的函數(shù)不能接受 Point
作為參數(shù),即便這兩個(gè)類型都由三個(gè) i32
值組成。在其他方面,元組結(jié)構(gòu)體實(shí)例類似于元組,你可以將它們解構(gòu)為單獨(dú)的部分,也可以使用 .
后跟索引來(lái)訪問(wèn)單獨(dú)的值,等等。
我們也可以定義一個(gè)沒(méi)有任何字段的結(jié)構(gòu)體!它們被稱為 類單元結(jié)構(gòu)體(unit-like structs)因?yàn)樗鼈冾愃朴?nbsp;()
,即“元組類型”一節(jié)中提到的 unit 類型。類單元結(jié)構(gòu)體常常在你想要在某個(gè)類型上實(shí)現(xiàn) trait 但不需要在類型中存儲(chǔ)數(shù)據(jù)的時(shí)候發(fā)揮作用。我們將在第十章介紹
trait。下面是一個(gè)聲明和實(shí)例化一個(gè)名為 AlwaysEqual
的 unit 結(jié)構(gòu)的例子。
struct AlwaysEqual;
fn main() {
let subject = AlwaysEqual;
}
要定義 AlwaysEqual
,我們使用 struct
關(guān)鍵字,我們想要的名稱,然后是一個(gè)分號(hào)。不需要花括號(hào)或圓括號(hào)!然后,我們可以以類似的方式在 subject
變量中獲得 AlwaysEqual
的實(shí)例:使用我們定義的名稱,不需要任何花括號(hào)或圓括號(hào)。想象一下,我們將實(shí)現(xiàn)這個(gè)類型的行為,即每個(gè)實(shí)例始終等于每一個(gè)其他類型的實(shí)例,也許是為了獲得一個(gè)已知的結(jié)果以便進(jìn)行測(cè)試。我們不需要任何數(shù)據(jù)來(lái)實(shí)現(xiàn)這種行為,你將在第十章中,看到如何定義特性并在任何類型上實(shí)現(xiàn)它們,包括類單元結(jié)構(gòu)體。
結(jié)構(gòu)體數(shù)據(jù)的所有權(quán)
在示例 5-1 中的
User
結(jié)構(gòu)體的定義中,我們使用了自身?yè)碛兴袡?quán)的String
類型而不是&str
字符串 slice 類型。這是一個(gè)有意而為之的選擇,因?yàn)槲覀兿胍@個(gè)結(jié)構(gòu)體擁有它所有的數(shù)據(jù),為此只要整個(gè)結(jié)構(gòu)體是有效的話其數(shù)據(jù)也是有效的。
可以使結(jié)構(gòu)體存儲(chǔ)被其他對(duì)象擁有的數(shù)據(jù)的引用,不過(guò)這么做的話需要用上 生命周期(lifetimes),這是一個(gè)第十章會(huì)討論的 Rust 功能。生命周期確保結(jié)構(gòu)體引用的數(shù)據(jù)有效性跟結(jié)構(gòu)體本身保持一致。如果你嘗試在結(jié)構(gòu)體中存儲(chǔ)一個(gè)引用而不指定生命周期將是無(wú)效的,比如這樣:
文件名: 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, }; }
編譯器會(huì)抱怨它需要生命周期標(biāo)識(shí)符:
$ 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
第十章會(huì)講到如何修復(fù)這個(gè)問(wèn)題以便在結(jié)構(gòu)體中存儲(chǔ)引用,不過(guò)現(xiàn)在,我們會(huì)使用像
String
這類擁有所有權(quán)的類型來(lái)替代&str
這樣的引用以修正這個(gè)錯(cuò)誤。
更多建議: