The Rust Programming Language
Rust 編程語(yǔ)言筆記醇疼。
來(lái)源:The Rust Programming Language By Steve Klabnik, Carol Nichols 。
翻譯參考:Rust 語(yǔ)言術(shù)語(yǔ)中英文對(duì)照表
結(jié)構(gòu)體
初始化
Rust 的結(jié)構(gòu)體類(lèi)似于 C法焰,使用關(guān)鍵字 struct
聲明秧荆。
struct User {
active: bool,
sign_in_count: u32,
username: String,
email: String
}
結(jié)構(gòu)體中的每個(gè)元素稱(chēng)為“字段”(field),字段是可變的(mutable)埃仪,使用 .
來(lái)訪問(wèn)字段的值辰如。
創(chuàng)建實(shí)例
為了使用結(jié)構(gòu)體,需要根據(jù)結(jié)構(gòu)體創(chuàng)建一個(gè)實(shí)例(instance)贵试,并給該結(jié)構(gòu)體的字段賦值琉兜,賦值的順序可以不同于結(jié)構(gòu)體定義的順序。
使得字段可變毙玻,必須給實(shí)例添加 mut
關(guān)鍵字豌蟋,Rust 不允許給某一個(gè)或幾個(gè)字段添加 mut
關(guān)鍵字。
struct User {
active: bool,
sign_in_count: u32,
username: String,
email: String
}
fn main() {
let mut user1 = User {
active: false,
sign_in_count: 1,
username: String::from("someusername"),
email: String::from("someuseremail"),
};
user1.email = "anotheremail";
}
可以使用 結(jié)構(gòu)體更新語(yǔ)法 ..
來(lái)從其他實(shí)例來(lái)創(chuàng)建新實(shí)例:
struct User {
active: bool,
sign_in_count: u32,
username: String,
email: String
}
fn main() {
let user1 = User {
active: false,
sign_in_count: 1,
username: String::from("someusername"),
email: String::from("someuseremail"),
};
/*
// regular
let user2 = User {
active: user1.active,
username: user1.username,
email: String::from("another@example.com"),
sign_in_count: user1.sign_in_count,
};
*/
let user_2 = User {
active: true,
..user1
}
}
上面的代碼表示桑滩,除了字段 active
之外梧疲,user_2
的其他字段值和 user1
相等。
注:..user1
后沒(méi)有 ,
,而且必須放在最后一行幌氮。
元組結(jié)構(gòu)體
元組結(jié)構(gòu)體(tuple struct) 類(lèi)似于元組缭受。可以理解為給元組分配了有意義的名稱(chēng)该互,但是并沒(méi)有確切的字段米者,只有字段的類(lèi)型。
struct Color(i32, i32, i32);
struct Point(i32, i32, i32);
fn main() {
let red = Color(0, 0, 0);
let origin = Point(0, 0, 0);
}
上面的兩個(gè)實(shí)例雖然有著相同的字段類(lèi)型以及字段值宇智,但依然是不同的實(shí)例蔓搞。
每個(gè)結(jié)構(gòu)體實(shí)例的類(lèi)型就是其定義的類(lèi)型,即便它們有完全相同的字段且字段的類(lèi)型一致随橘。
類(lèi)單元結(jié)構(gòu)體
類(lèi)單元結(jié)構(gòu)體(unit-like struct)指的是不含任何數(shù)據(jù)的結(jié)構(gòu)體喂分。類(lèi)似于不含成員的元組--單元(unit) ()
。
struct ULS;
fn main() {
let subject = ULS;
}
函數(shù) VS 方法
關(guān)聯(lián)和區(qū)別
在一些編程語(yǔ)言中机蔗,函數(shù)(function)和方法(method)通常有著相同的含義蒲祈。在 Rust 中,兩者的關(guān)聯(lián)和區(qū)別如下:
- 關(guān)聯(lián)
- 都使用
fn
關(guān)鍵字聲明 - 都有參數(shù)和返回值
- 都可以被調(diào)用
- 都使用
- 區(qū)別
- 方法的第一個(gè)參數(shù)永遠(yuǎn)是
self
萝嘁,表示被調(diào)用的方法作用的實(shí)例(instance) - 方法通常被定義在一個(gè)結(jié)構(gòu)體梆掸、枚舉或者 對(duì)象的上下文(context)下,而函數(shù)通常沒(méi)有具體的上下文
- 方法的第一個(gè)參數(shù)永遠(yuǎn)是
Rust 使用方法的原因是提高代碼的組織性酿愧。impl
緊緊關(guān)聯(lián)著作用的結(jié)構(gòu)體。
定義方法
方法使用 fn
關(guān)鍵字聲明邀泉,通常寫(xiě)在 impl(implementation)
塊(block)中嬉挡。
struct Rectangle {
width: u32,
height: u32,
}
impl Rectangle {
fn area(&self) -> u32 {
self.width * self.height
}
}
fn main() {
let rect1 = Rectangle {
width: 30,
height: 50,
};
println!("The are of rectangle {}", rect1.area());
}
方法的第一個(gè)參數(shù)是 self
,其實(shí)是 self: Self
的簡(jiǎn)潔表示汇恤。如果不希望方法帶走 ownership庞钢,應(yīng)該使用 &self
,如果希望更改數(shù)據(jù)因谎,使用 &mut self
基括。
類(lèi)似函數(shù),方法同樣使用
.
運(yùn)算符調(diào)用财岔。與 C风皿、C++ 等語(yǔ)言不同,Rust 不支持使用->
運(yùn)算符來(lái)調(diào)用方法匠璧,而是通過(guò)被稱(chēng)為 自動(dòng)引用和解引用 的方式來(lái)調(diào)用方法桐款。大致原理為:當(dāng)調(diào)用object.method()
時(shí),Rust 會(huì)自動(dòng)添加&
夷恍,&mut
魔眨,*
,因此object
匹配了方法的簽名以下兩行代碼作用相同:
p1.distance(&p2); (&p1).distance(&p2);
getters
如果對(duì)結(jié)構(gòu)體實(shí)現(xiàn)了同名的字段和方法,那么 object.field
表示訪問(wèn)字段遏暴,object.method()
表示調(diào)用方法侄刽。
通常,調(diào)用同名的方法表示希望獲取其同名的字段的值朋凉,這類(lèi)方法被稱(chēng)為 getters州丹。一些編程語(yǔ)言會(huì)自動(dòng)實(shí)現(xiàn) getters,但是 Rust 并非如此侥啤。
關(guān)聯(lián)函數(shù)
定義在 impl
塊下的函數(shù)都被稱(chēng)為 關(guān)聯(lián)函數(shù)(Associated Function)当叭,因?yàn)樗鼈冏饔糜?impl
后的結(jié)構(gòu)體。
也可以定義第一個(gè)參數(shù)不為 self
的關(guān)聯(lián)函數(shù)盖灸,這類(lèi)函數(shù)通過(guò) ::
作用蚁鳖,例如:String::from()
。
impl Rectangle {
fn square(size: u32) -> Self {
Self {
width: size,
height: size,
}
}
}
let sq = Rectangle::square(3);
所以 ::
語(yǔ)法同時(shí)用于關(guān)聯(lián)函數(shù)和模塊(module)的命名空間赁炎。
多個(gè)參數(shù)的方法
impl Rectangle {
fn area(&self) -> u32 {
self.width * self.height
}
fn can_hold(&self, other: &Rectangle) -> bool {
self.width > other.width && self.height > other.height
}
}
Rust 允許使用多個(gè) impl
塊聲明方法醉箕,但是在本例中,兩個(gè)方法放在一個(gè) impl
中可讀性更好徙垫。
枚舉
枚舉類(lèi)型(enumerations / enums)定義窮舉所有可能的值的數(shù)據(jù)類(lèi)型讥裤。
定義枚舉
枚舉類(lèi)型使用 enum
關(guān)鍵字來(lái)聲明,使用 ::
來(lái)獲取枚舉的值姻报。
enum IpAddrKind {
V4,
V6,
}
let four = IpAddrKind::V4;
let six = IpAddrKind::V6;
fn route(ip_kind: IpAddrKind) {} // 函數(shù)聲明并傳入枚舉類(lèi)型
route(IpAddrKind::V4);
route(IpAddrKind::V6); // 調(diào)用函數(shù)并傳入枚舉類(lèi)型
枚舉類(lèi)型的一個(gè)好處是:枚舉值可以是不同類(lèi)型的己英。
例如:
enum IpAddr {
V4(u8, u8, u8, u8),
V6(String),
}
let home = IpAddr::V4(127, 0, 0, 1);
let loopback = IpAddr::V6(String::from("::1"));
impl 方法
類(lèi)似于結(jié)構(gòu)體,可以使用 impl
給枚舉類(lèi)型定義方法:
enum Message {
Quit,
Move { x: i32, y: i32 },
Write(String),
ChangeColor(i32, i32, i32),
}
impl Message {
fn call(&self) {
// something to do
}
}
let m = Message::Write(String::from("Hello"));
m.call();
Option 枚舉類(lèi)型
Option
是一種標(biāo)準(zhǔn)庫(kù)定義的枚舉類(lèi)型吴旋。
在編程語(yǔ)言的設(shè)計(jì)中损肛,排除和包含特性一樣重要。例如:Rust 就排除了 null
荣瑟。在其他實(shí)現(xiàn) null
的編程語(yǔ)言中治拿,變量總是有 null
和 non-null
兩種狀態(tài),null
表示沒(méi)有值笆焰。
Rust 使用 Option<T>
枚舉類(lèi)型來(lái)表示 null
劫谅,其定義為:
enum Option<T> {
None,
Some(T),
}
let some_number = Some(5);
let some_char = Some('e');
let absent_number: Option<i32> = None;
不能把類(lèi)型不同的值做算術(shù)運(yùn)算,
let x: i8 = 5;
let y: Option<i8> = Some(5);
let sum = x + y;
上述代碼編譯器會(huì)報(bào)錯(cuò)嚷掠。
模式匹配
在 Rust 中捏检,使用 match
來(lái)對(duì)某個(gè)值和一系列模式進(jìn)行匹配。模式可以是字面量不皆、變量以及其他類(lèi)型未檩。
每個(gè)匹配(arm)都由模式和代碼組成,每個(gè) arm 之間用 ,
分隔粟焊。模式和代碼之間用 =>
相連冤狡。
如果代碼為多行孙蒙,需要放入括號(hào) {}
。代碼由表達(dá)式組成悲雳,如果匹配成功挎峦,該表達(dá)式的值作為整個(gè) match
的返回值。
例如:
enum Coin {
Penny,
Nickel,
Dime,
Quarter,
}
fn value_in_cents(coin: Coin) -> u8 {
match coin {
/*
Coin::Penny => {
println!("That's an penny!");
1
},
*/
Coin::Penny => 1,
Coin::Nickel => 5,
Coin::Dime => 10,
Coin::Quarter => 25,
}
}
match
和 if
的不同在于:if
的條件結(jié)果必須是 bool
合瓢,而 match
可以是任意類(lèi)型坦胶。
模式綁定值
match
的匹配(arm)還有一個(gè)常用的特性是把值綁定給匹配的模式。
例如:
enum UsState {
Alabama,
Alaska,
}
enum Coin {
Penny,
Nickel,
Dime,
Quarter(UsState),
}
fn value_in_cents(coin: Coine) -> u8 {
match coin {
Coin::Peeny => 1,
Coin::Nickle => 5,
Coin::Dime => 10,
Coin::Quarter(state) => {
println!("State quarter from {:?}", state);
25
}
}
}
上面的代碼中晴楔,如果調(diào)用 value_in_cents(Coin::Quarter(UsState::Alaska))
, coin
將會(huì)匹配到 Coin::Quarter(Alaska)
這一行顿苇,此時(shí) state
的值就和 UsState::Alaska
綁定了。
匹配和 Option
如下例:
fn plus_one(x: Option<i32>) -> Option<i32> {
match x {
None => None,
Some(i) => Some(i + 1),
}
}
let five = Some(5);
let six = plus_one(five); // 6
let none = plus_one(None); // None
一個(gè)不漏
match
還有一個(gè)特性是:必須匹配(arm)所有可能的模式税弃。
例如上面的代碼纪岁,如果改為:
fn plus_one(x: Option<i32>) -> Option<i32> {
match x {
Some(i) => Some(i + 1),
}
}
編譯器會(huì)報(bào)錯(cuò),因?yàn)闆](méi)有處理 None
模式的匹配则果。
所以 match
在 Rust 中是 窮盡(exhaustive) 的幔翰。
_ 占位符來(lái)匹配所有的模式
let dice_roll = 9;
match dice_roll {
3 => add_fancy_hat(),
7 => remove_fancy_hat(),
other => move_player(other),
// _ => reroll()
// _ => () // do nothing
}
fn add_fancy_hat() {}
fn remove_fancy_hat() {}
fn move_player(num_spaces: u8) {}
fn reroll() {}
在上述代碼中,使用 other
代表其他同種處理的匹配西壮∫旁觯或者使用 _
占位符來(lái)表示。
if let
if let
語(yǔ)法提供了一種更簡(jiǎn)潔的方式來(lái)處理某種模式匹配成功并忽略其他選項(xiàng)的情況款青。
let config_max = Some(3u8);
match config_max {
Some(max) => println!("The maximum is configured to be {}", max),
_ => (),
}
// same as
let config_max = Some(3u8);
if let Some(max) = config_max {
println!("The maximum is configured to be {}", max);
}
模塊系統(tǒng)
Rust 的模塊系統(tǒng)(Module System)包括:
- 包(package):構(gòu)建做修、測(cè)試、共享 crates抡草。
- crates:可生成庫(kù)(library)或者可執(zhí)行程序的模塊樹(shù)饰及。
- 模塊(module):控制路徑的組織方式、作用域以及私有性渠牲。
- 路徑(path):命名一個(gè)實(shí)體的方式听盖,例如:結(jié)構(gòu)體喉镰、函數(shù)、模塊沽一。
包
包 是一系列 crates 的集合鼎兽。包中有名為 Cargo.toml
的文件定義了如何構(gòu)建這些 crates答姥。
使用 cargo new
命令后,Rust 會(huì)在當(dāng)前目錄創(chuàng)建一個(gè)包谚咬。
包中至少要包含一個(gè) crate鹦付。包可以包含多個(gè)二進(jìn)制 crates,但是最多只能包含一個(gè)庫(kù) crate择卦。
Cargo 是最常用的包敲长,其默認(rèn)把 src/main.rs
和 src/lib.rs
作為二進(jìn)制 crate 和庫(kù) crate郎嫁,并把兩者作為 crate root。當(dāng)使用 rustc
時(shí)祈噪,Rust 把這兩個(gè)文件(如果存在)編譯泽铛。
Crate
在 Rust 中,crate 指的是編譯器所編譯的源文件辑鲤,是編譯器一次編譯時(shí)的最小單位盔腔。
crate 包含多個(gè)模塊。
crate 分為二進(jìn)制 crate(binary crate)和庫(kù)(library crate)兩種:
二進(jìn)制 crate 是由 Rustaceans 所編寫(xiě)的代碼月褥,每個(gè)二進(jìn)制 crate 必須包含一個(gè)
main
函數(shù)弛随。庫(kù) crate 不含
main
函數(shù),不能被編譯為可執(zhí)行程序宁赤,而是作為一種共享方式在項(xiàng)目中舀透。
兩種 crate 分別在 src
路徑下以 main.rs
和 lib.rs
兩種文件名稱(chēng)存在。
crate root 指的是 Rust 編譯器編譯的源文件礁击,以及 crate 的根模塊盐杂。
模塊
總覽
假設(shè)有以下文件結(jié)構(gòu):
backyard
|--Cargo.lock
|--Cargo.toml
|--src
|--garden
| |--vegetable.rs
|--garden.rs
|--main.rs
// src/main.rs
use crate::garden::vegetables::Asparagus;
pub mod garden;
fn main() {
let plant = Asparagus {};
println!("I'm growing {:?}!", plant);
}
// garden.rs
pub mod vegetables;
// vegetables.rs
#[derive(Debug)]
pub struct Asparagus {}
- backyard 是 crate 目錄
-
src/main.rs
是 crate root -
pub mod garden
表示 garden 是一個(gè)模塊,可見(jiàn)性為pub
哆窿。這行代碼表示把garden.rs
里的內(nèi)容引入 -
pub mod vegetables
作用同上
所以链烈,模塊的工作原理:
從 crate root 開(kāi)始:當(dāng)編譯 crate 時(shí),編譯器首先找到 crate root 文件(通常是
main.rs
或者lib.rs
)來(lái)編譯-
定義模塊:在 crate root 文件中挚躯,可以用
mod
關(guān)鍵字聲明新的模塊强衡,例如:mod garden
,編譯器會(huì)在以下目錄尋找該模塊的代碼:- 行內(nèi)
src/garden.rs
src/garden/mod.rs
-
定義子模塊:在 除 crate root 的文件里還可以定義子模塊码荔,例如:
mod vegetables
漩勤,編譯器會(huì)在其父模塊的目錄下尋找子模塊的代碼:- 行內(nèi)
src/garden/vegetables.rs
src/garden/vegetables/mod.rs
模塊中的路徑:一旦聲明模塊后,可以通過(guò)路徑引入模塊缩搅。例如:在
vegetables
模塊內(nèi)聲明了Asparagus
越败,引入路徑為:crate::garden::vegetables::Asparagus
私有 vs 共有:默認(rèn)情況下,子模塊的內(nèi)容對(duì)父模塊是私有的硼瓣,使用
pub
關(guān)鍵字使其公有化use
關(guān)鍵字:使用use
來(lái)簡(jiǎn)化引用究飞。
優(yōu)勢(shì)
模塊的優(yōu)勢(shì):
- 提高代碼的可讀性和可重用性
- 隱私性
路徑
類(lèi)似于文件系統(tǒng),有絕對(duì)路徑和相對(duì)路徑兩種方式來(lái)表示層級(jí)關(guān)系:
-
絕對(duì)路徑:指的是從 crate root 開(kāi)始的完整路徑堂鲤。對(duì)于外部 crate 來(lái)說(shuō)亿傅,絕對(duì)路徑以 crate 的名稱(chēng)為開(kāi)始;對(duì)內(nèi)部 crate 來(lái)說(shuō)瘟栖,絕對(duì)路徑以字面量
crate
開(kāi)始 -
相對(duì)路徑:從當(dāng)前模塊開(kāi)始葵擎,通常包含
self
、super
等關(guān)鍵字
絕對(duì)路徑和相對(duì)路徑都使用 ::
表示層級(jí)間的分隔符半哟。
絕對(duì)和相對(duì)路徑各有優(yōu)劣酬滤,可以根據(jù)個(gè)人偏好進(jìn)行選擇签餐。在 Rust 中,一般使用絕對(duì)路徑盯串,原因是這樣使得依賴(lài)相對(duì)獨(dú)立贱田。
mod front_of_house {
mod hosting {
fn add_to_waitlist() {}
}
}
pub fn eat_at_restaurant() {
// Absolute path
crate::front_of_house::hosting::add_to_waitlist();
// Relative path
front_of_house::hosting::add_to_waitlist();
}
私有、公有
在 Rust 乃至整個(gè)計(jì)算機(jī)領(lǐng)域都有內(nèi)部實(shí)現(xiàn)對(duì)外部不可見(jiàn)的原則嘴脾。
私有的概念通常和作用域(scope)相關(guān)男摧。例如:定義在函數(shù) A 內(nèi)部定義了子函數(shù) B。對(duì)該子函數(shù) B 來(lái)說(shuō)译打,函數(shù) A 對(duì)其是可見(jiàn)的耗拓。但對(duì)函數(shù) A 的其他部分來(lái)說(shuō),子函數(shù) B 是不可見(jiàn)的奏司,
通常來(lái)講乔询,Rust 把項(xiàng)(items)設(shè)置為私有(privacy),或者稱(chēng)為不可見(jiàn)的韵洋。如果調(diào)用了不可見(jiàn)的對(duì)象竿刁,編譯器會(huì)彈出錯(cuò)誤。
在 Rust 中搪缨,默認(rèn)對(duì)父模塊私有的項(xiàng)(items)包括模塊食拜、函數(shù)、方法副编、結(jié)構(gòu)體负甸、枚舉、常量痹届。
可以使用 pub
關(guān)鍵字使項(xiàng)(items)變?yōu)?strong>可見(jiàn)呻待、公有的。
注:把某個(gè)外部對(duì)象標(biāo)識(shí)為 pub
并不意味著其內(nèi)部對(duì)象也被標(biāo)識(shí)為 pub
(枚舉類(lèi)型除外队腐,如果枚舉類(lèi)型使用了 pub
,那么枚舉的所有結(jié)果默認(rèn)也為 pub
)柴淘,例如:
fn main() {
pub fn outer_function() {
fn inner_function() {
// --snip--
}
}
outer_function(); // OK
inner_function(); // Error, because the function sign has no **pub**
}
pub enum IPAddr {
V4(String), // also **pub**
V6(String), // also **pub**
}
最佳實(shí)踐
一般來(lái)說(shuō),一個(gè)包同時(shí)包含二進(jìn)制 crate
src/main.rs
以及庫(kù) cratesrc/lib.rs
千绪。兩者默認(rèn)都含有包名梗脾。常用的范式是:在
src/lib.rs
中定義模塊樹(shù)盹靴,這樣瑞妇,在二進(jìn)制 crate 中調(diào)用任何公有的項(xiàng)(items)都可以以該包名為開(kāi)始作為路徑梭冠。
super
使用 super
關(guān)鍵字來(lái)引用父級(jí)路徑辕狰,這類(lèi)似于文件系統(tǒng)中的 ..
。
fn deliver_order() {}
mod back_of_house {
fn fix_incorrect_order() {
cook_order();
super::deliver_order();
}
fn cook_order() {}
}
use控漠、as
use
使用 use
關(guān)鍵字來(lái)引入路徑蔓倍。
Rust 的慣例是:在調(diào)用某個(gè)函數(shù)時(shí),其路徑應(yīng)該引入到父級(jí)盐捷。雖然引入到當(dāng)前級(jí)效果相同偶翅,但是前者使得函數(shù)定義更加清晰。例如:
use crate::galaxy::solar_system::earth;
earth();
use crate::galaxy::solar_system;
solar_system.earth(); // same thing, but this one is better.
再導(dǎo)出
再導(dǎo)出(re-exporting) 使得當(dāng)前作用域引入的對(duì)象也可以用于其他作用域碉渡。因?yàn)槟J(rèn)情況下聚谁,使用 use
關(guān)鍵把某個(gè)名稱(chēng)引入當(dāng)前作用域后,該名稱(chēng)對(duì)其他作用域是私有的滞诺。
使用 pub use
實(shí)現(xiàn)再導(dǎo)出:
mod front_of_house {
pub mod hosting {
pub fn add_to_waitlist() {}
}
}
pub use crate::front_of_house::hosting;
pub fn eat_at_restaurant() {
hosting::add_to_waitlist();
}
- 如果沒(méi)有再導(dǎo)出形导,外部代碼想調(diào)用
add_to_waitlist()
函數(shù)必須使用路徑:restaurant::front_of_house::hosting::add_to_waitlist()
- 再導(dǎo)出后,使用
restaurant::hosting::add_to_waitlist()
即可
嵌套路徑
為了避免使用多個(gè) use
的多行引入導(dǎo)致代碼可讀性變差习霹,可以把同一父對(duì)象下的子對(duì)象用花括號(hào)在同一行中朵耕。
use std::{io, fmt};
// same as
use std::io;
use std::fmt;
如果同時(shí)引入了父對(duì)象和其子對(duì)象,使用 self
關(guān)鍵字表示該父對(duì)象淋叶。
use std::io::{self, Result};
// same as
use std::io;
use std::io::Result;
Glob 運(yùn)算符
如果要引入全部對(duì)象憔披,使用全局 glob 運(yùn)算符 *
。
use std::io::*;
as
假如引入的對(duì)象名稱(chēng)過(guò)長(zhǎng)爸吮,可以使用 as
關(guān)鍵字來(lái)通過(guò)別名來(lái)引入芬膝。
use this_is_a_very_long_function_name as lfn;
lfn(); // much simpler
集合
Rust 的標(biāo)準(zhǔn)庫(kù)中包含了一系列常用的數(shù)據(jù)結(jié)構(gòu)被稱(chēng)為集合(collection)。最常用的是:
- 動(dòng)態(tài)數(shù)組 Vector
- 字符串 String
- 哈希表 HashMap
這些結(jié)構(gòu)的特點(diǎn)是:存儲(chǔ)在堆中形娇,可變長(zhǎng)锰霜,使用泛型實(shí)現(xiàn)。這意味著在編譯時(shí)癣缅,編譯器并不知道這些結(jié)構(gòu)的大小。
初始化集合的通用方法是 ::new()
動(dòng)態(tài)數(shù)組
動(dòng)態(tài)數(shù)組中的元素在內(nèi)存中緊挨著彼此存儲(chǔ)屡立。
動(dòng)態(tài)數(shù)組只能存儲(chǔ)同種類(lèi)型的數(shù)據(jù)膨俐,但是可以借助枚舉來(lái)存儲(chǔ)不同類(lèi)型的數(shù)據(jù)焚刺。
初始化
動(dòng)態(tài)數(shù)組 Vec<T>
的初始化兄淫,可以用 ::new()
來(lái)初始化一個(gè)空動(dòng)態(tài)數(shù)組拖叙,也可以使用 宏(macro) vec![]
顯式把動(dòng)態(tài)數(shù)組的成員列出來(lái)初始化動(dòng)態(tài)數(shù)組:
fn main() {
let v = vec![1, 2, 3, 4, 5];
let ano_v: Vec<i32> = Vec::new();
}
注:在第二種聲明中指明了存儲(chǔ)元素的類(lèi)型,否則 Rust 并不知道 Vector 要存儲(chǔ)什么類(lèi)型的數(shù)據(jù)挖滤。
讀寫(xiě)
寫(xiě)
使用 push
方法給動(dòng)態(tài)數(shù)組添加元素:
let mut v = Vec::new();
v.push(1);
v.push(2);
讀
使用 .get()
或者括號(hào)索引 []
的方式來(lái)訪問(wèn)動(dòng)態(tài)數(shù)組元素:
let third: &i32 = &v[2]; // 3
let two: Option<&i32> = v.get(2); // Some(2)
如上面的代碼所示斩松,使用 .get()
方法得到的是 Option<T>
數(shù)據(jù)類(lèi)型,而不是動(dòng)態(tài)數(shù)組元素 <T>
的類(lèi)型钧椰。
由于 .get()
方法得到的是 Option<T>
類(lèi)型嫡霞,因此可以使用 match
來(lái)對(duì)取得的值做判斷诊沪。
let two = v.get(2);
match two {
Some(two) => println!("The element is {}", two),
None => println!("No such element"),
}
越界
兩種訪問(wèn)方式對(duì)于動(dòng)態(tài)數(shù)組越界有著不同的處理方式:
let third = &v[100]; // index out of bound
let two = v.get(100); // None
使用 []
索引訪問(wèn)可以通過(guò)編譯,但在運(yùn)行時(shí)會(huì)出現(xiàn) index out of bound
索引越界的錯(cuò)誤渐裸;使用 .get()
方法會(huì)得到 None
。
下面的例子說(shuō)明了動(dòng)態(tài)數(shù)組的工作方式:
let mut v = vec![1, 2, 3, 4, 5];
let first = &v[0];
v.push(6);
println!("{}", first); // Error
上面的代碼不能通過(guò)編譯盆顾,原因是:由于動(dòng)態(tài)數(shù)組的元素在內(nèi)存中是緊挨著彼此存儲(chǔ)的您宪。因此宪巨,給動(dòng)態(tài)數(shù)組中添加新元素時(shí)捏卓,如果當(dāng)前的內(nèi)存空間位置不能容下新加入的元素,就需要分配一塊新的內(nèi)存空間并拷貝舊的元素到該空間蒜田。而引用訪問(wèn)動(dòng)態(tài)數(shù)組元素也許會(huì)導(dǎo)致訪問(wèn)到已經(jīng)被解除分配的空間冲粤。
遍歷
使用 for in
來(lái)遍歷動(dòng)態(tài)數(shù)組元素:
for i in &v {
println!("{}", i);
}
使用 mut
引用來(lái)遍歷并變動(dòng)態(tài)數(shù)組元素:
for i in &mut v {
*i += 50;
}
字符串
兩類(lèi)字符串的比較
Rust 語(yǔ)言的核心中只有一種字符串:字符串切片 str
,通常以 &str
形式出現(xiàn)科阎。
而 String
類(lèi)型由 Rust 標(biāo)準(zhǔn)庫(kù)實(shí)現(xiàn)的锣笨。
兩者都是 UTF-8 編碼的。
初始化
字符串 String
的初始化椭岩,可以用 ::new()
來(lái)初始化一個(gè)空字符串判哥,也可以用 ::from()
顯式初始化字符串挺身,或者先聲明字符串字面量章钾,然后轉(zhuǎn)化為 String
類(lèi)型的字符串:
fn main() {
let mut s = String::from("Hello");
let mut ano_s = String::new();
let s_in_stack: &str = "Hello World";
let s_in_heap: String = s_in_stack.to_string();
}
讀寫(xiě)
寫(xiě)
使用 push_str()
把字符串拼接至另一字符串尾:
s.push_str(", World");
println!("{}", s) // Hello, World
使用 push()
拼接一個(gè)字符到字符串尾:
let mut s = String::from("lo");
s.push('l');
println!("{}", s) // "lol"
使用 +
來(lái)拼接已有字符串:
let s1 = String::from("Hello, ");
let s2 = String::from("World");
let s = s1 + &s2;
println!("{}", s1); // Error
println!("{}", s2); // "World"
println!("{}", s); // "Hello, World"
注:拼接之后,s1
的 ownership 被轉(zhuǎn)移給 s
府寒,所以 s1
不能再被使用椰棘。這是因?yàn)?add
函數(shù)的簽名:
fn add(self, s: &str) -> String {
決定了 self
位置的變量的 ownership 被奪取。第二個(gè)變量需要使用 &
引用形式帆卓,而不是直接把兩個(gè)字符串的值相加剑令。
這里編譯器使用 強(qiáng)制轉(zhuǎn)換(coerce) 把 String
類(lèi)型轉(zhuǎn)化為 &str
吁津,當(dāng)調(diào)用 add
時(shí),Rust 會(huì)使用 解引用強(qiáng)制轉(zhuǎn)換(deref coercio n) 把 &s2
轉(zhuǎn)化為 &s2[..]
典尾。
如果不希望 s1
的 ownership 發(fā)生變化钾埂,可以使用 format!
宏(macro) 來(lái)拼接字符串:
let s1 = String::from("Hello, ");
let s2 = String::from("World");
let s = format!("{s1}{s2}");
println!("{}", s1); // "Hello, "
println!("{}", s2); // "World"
println!("{}", s); // "Hello, World"
讀
Rust 不支持索引訪問(wèn)字符串中的字符姜性。
let s1 = String::from("Code");
println!("{}", s1[0]); // Error
上面的代碼將不能通過(guò)編譯部念,原因和 String
類(lèi)型的內(nèi)部實(shí)現(xiàn)有關(guān):
String
類(lèi)型是對(duì) Vec<u8>
的包裝矢腻,所以:
let hello = String::from("Hola");
let ano_hello = String::from("Здравствуйте");
hello
的 len
為 4
多柑,因?yàn)樵?UTF-8 編碼中每個(gè)字符占用 1
個(gè)字節(jié),而 ano_hello
的 len
為 24
聂沙,而非 12
,因?yàn)樵?UTF-8 編碼中坷随,每個(gè) Unicode scalar 值占用 2
個(gè)字節(jié)驻龟。因此类溢,如果使用索引訪問(wèn)闯冷,將返回?zé)o意義的值窃躲。
可以使用 [..]
創(chuàng)建字符串切片:
let ano_hello = "Здравствуйте";
// let ano_hello = String::from("Здравствуйте"); // also Ok
let s = &ano_hello[0..4]; // Зд
s
是 ano_hello
的前 4
個(gè)字節(jié)蒂窒,而非字符秧秉。
類(lèi)型轉(zhuǎn)換
使用 to_string
把其他類(lèi)型轉(zhuǎn)化為字符串:
let i: i32 = 2;
let i_s: String = i.to_string(); // "2";
遍歷
使用 .chars()
獲得字符串的序列象迎,并用 for in
來(lái)遍歷以輸出字符串的字符:
let s = String::from("Hello");
for c in s.chars() {
println!("{}", c);
}
// H
// e
// l
// l
// o
類(lèi)似地砾淌,使用 .bytes()
或者字符對(duì)應(yīng)的字節(jié)序列,并用 for in
來(lái)遍歷以輸出字符串的字符:
let s = String::from("Hello");
for b in s.bytes() {
println!("{}", b);
}
// 72
// 101
// 108
// 108
// 111
哈希表
使用哈希表 HashMap<K, V>
前需要用 use
關(guān)鍵字引入:
use std::collections::HashMap;
哈希表的鍵類(lèi)型為 String i32
劫乱,鍵和值必須為相同類(lèi)型衷戈。
初始化
可以用 ::new()
來(lái)初始化一個(gè)空哈希表:
fn main() {
let hm = HashMap::new();
}
讀寫(xiě)
寫(xiě)
使用 .insert()
添加鍵值對(duì)到哈希表(注意:mut
關(guān)鍵字):
let mut scores = HashMap::new();
scores.insert(String::from("Blue"), 10);
scores.insert(String::from("Yellow"), 30);
scores.insert(String::from("Black"), 20);
讀
使用 .get()
根據(jù)鍵獲取值:
println!("the blue: {:?}", scores.get("Blue").unwrap()); // 10
println!("the blue: {:?}", scores.get("Yellow")); // Some(30)
println!("the blue: {:?}", scores.get("Red").copied().unwrap_or(0)); // None
和動(dòng)態(tài)數(shù)組類(lèi)似,獲取的值是 Option<&V>
類(lèi)型破花,可以使用 unwrap()
獲取 <T>
類(lèi)型蔚润。
遍歷
使用元組遍歷哈希表:
for (key, value) in &scores {
println!("{} {}", key, value);
}
// One Possible Outcome:
// Blue 10
// Yellow 30
// Black 20
注:遍歷的結(jié)果是無(wú)序的。
更新
哈希表的更新有幾種不同的方式:
如果給同一個(gè)鍵添加多個(gè)值除盏,結(jié)果是只保留最后一個(gè)值:
use std::collections::HashMap;
fn main() {
let mut map = HashMap::new();
map.insert(String::from("One"), 1);
map.insert(String::from("One"), 2);
println!("{:?}", map); // {"One": 2};
}
如果不存在鍵者蠕,使用 entry()
和 or_insert()
添加鍵值對(duì):
use std::collections::HashMap;
fn main() {
let mut map = HashMap::new();
map.insert(String::from("One"), 1);
map.entry(String::from("One")).or_insert(2);
map.entry(String::from("Two")).or_insert(2);
println!("{:?}", map); // {"One": 1, "Two": 2};
}
entry()
API 返回 Entry
枚舉類(lèi)型踱侣,該枚舉類(lèi)型返回指定的鍵是否存在探膊,or_insert()
構(gòu)建在 Entry
之上逞壁,如果鍵存在就不做變锐锣,如果不存在就添加該鍵值對(duì)雕憔。