The Rust Programming Language
Rust 編程語言筆記。
來源:The Rust Programming Language By Steve Klabnik, Carol Nichols 。
翻譯參考:Rust 語言術(shù)語中英文對照表
結(jié)構(gòu)體
初始化
Rust 的結(jié)構(gòu)體類似于 C,使用關(guān)鍵字 struct 聲明。
struct User {
active: bool,
sign_in_count: u32,
username: String,
email: String
}
結(jié)構(gòu)體中的每個元素稱為“字段”(field),字段是可變的(mutable),使用 . 來訪問字段的值。
創(chuàng)建實例
為了使用結(jié)構(gòu)體,需要根據(jù)結(jié)構(gòu)體創(chuàng)建一個實例(instance),并給該結(jié)構(gòu)體的字段賦值,賦值的順序可以不同于結(jié)構(gòu)體定義的順序。
使得字段可變,必須給實例添加 mut 關(guān)鍵字,Rust 不允許給某一個或幾個字段添加 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)體更新語法 .. 來從其他實例來創(chuàng)建新實例:
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 后沒有 ,,而且必須放在最后一行。
元組結(jié)構(gòu)體
元組結(jié)構(gòu)體(tuple struct) 類似于元組??梢岳斫鉃榻o元組分配了有意義的名稱,但是并沒有確切的字段,只有字段的類型。
struct Color(i32, i32, i32);
struct Point(i32, i32, i32);
fn main() {
let red = Color(0, 0, 0);
let origin = Point(0, 0, 0);
}
上面的兩個實例雖然有著相同的字段類型以及字段值,但依然是不同的實例。
每個結(jié)構(gòu)體實例的類型就是其定義的類型,即便它們有完全相同的字段且字段的類型一致。
類單元結(jié)構(gòu)體
類單元結(jié)構(gòu)體(unit-like struct)指的是不含任何數(shù)據(jù)的結(jié)構(gòu)體。類似于不含成員的元組--單元(unit) ()。
struct ULS;
fn main() {
let subject = ULS;
}
函數(shù) VS 方法
關(guān)聯(lián)和區(qū)別
在一些編程語言中,函數(shù)(function)和方法(method)通常有著相同的含義。在 Rust 中,兩者的關(guān)聯(lián)和區(qū)別如下:
- 關(guān)聯(lián)
- 都使用
fn關(guān)鍵字聲明 - 都有參數(shù)和返回值
- 都可以被調(diào)用
- 都使用
- 區(qū)別
- 方法的第一個參數(shù)永遠是
self,表示被調(diào)用的方法作用的實例(instance) - 方法通常被定義在一個結(jié)構(gòu)體、枚舉或者 對象的上下文(context)下,而函數(shù)通常沒有具體的上下文
- 方法的第一個參數(shù)永遠是
Rust 使用方法的原因是提高代碼的組織性。impl 緊緊關(guān)聯(lián)著作用的結(jié)構(gòu)體。
定義方法
方法使用 fn 關(guān)鍵字聲明,通常寫在 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());
}
方法的第一個參數(shù)是 self,其實是 self: Self 的簡潔表示。如果不希望方法帶走 ownership,應(yīng)該使用 &self,如果希望更改數(shù)據(jù),使用 &mut self。
類似函數(shù),方法同樣使用
.運算符調(diào)用。與 C、C++ 等語言不同,Rust 不支持使用->運算符來調(diào)用方法,而是通過被稱為 自動引用和解引用 的方式來調(diào)用方法。大致原理為:當(dāng)調(diào)用object.method()時,Rust 會自動添加&,&mut,*,因此object匹配了方法的簽名以下兩行代碼作用相同:
p1.distance(&p2); (&p1).distance(&p2);
getters
如果對結(jié)構(gòu)體實現(xiàn)了同名的字段和方法,那么 object.field 表示訪問字段,object.method() 表示調(diào)用方法。
通常,調(diào)用同名的方法表示希望獲取其同名的字段的值,這類方法被稱為 getters。一些編程語言會自動實現(xiàn) getters,但是 Rust 并非如此。
關(guān)聯(lián)函數(shù)
定義在 impl 塊下的函數(shù)都被稱為 關(guān)聯(lián)函數(shù)(Associated Function),因為它們作用于 impl 后的結(jié)構(gòu)體。
也可以定義第一個參數(shù)不為 self 的關(guān)聯(lián)函數(shù),這類函數(shù)通過 :: 作用,例如:String::from()。
impl Rectangle {
fn square(size: u32) -> Self {
Self {
width: size,
height: size,
}
}
}
let sq = Rectangle::square(3);
所以 :: 語法同時用于關(guān)聯(lián)函數(shù)和模塊(module)的命名空間。
多個參數(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 允許使用多個 impl 塊聲明方法,但是在本例中,兩個方法放在一個 impl 中可讀性更好。
枚舉
枚舉類型(enumerations / enums)定義窮舉所有可能的值的數(shù)據(jù)類型。
定義枚舉
枚舉類型使用 enum 關(guān)鍵字來聲明,使用 :: 來獲取枚舉的值。
enum IpAddrKind {
V4,
V6,
}
let four = IpAddrKind::V4;
let six = IpAddrKind::V6;
fn route(ip_kind: IpAddrKind) {} // 函數(shù)聲明并傳入枚舉類型
route(IpAddrKind::V4);
route(IpAddrKind::V6); // 調(diào)用函數(shù)并傳入枚舉類型
枚舉類型的一個好處是:枚舉值可以是不同類型的。
例如:
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 方法
類似于結(jié)構(gòu)體,可以使用 impl 給枚舉類型定義方法:
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 枚舉類型
Option 是一種標準庫定義的枚舉類型。
在編程語言的設(shè)計中,排除和包含特性一樣重要。例如:Rust 就排除了 null。在其他實現(xiàn) null 的編程語言中,變量總是有 null 和 non-null 兩種狀態(tài),null 表示沒有值。
Rust 使用 Option<T> 枚舉類型來表示 null,其定義為:
enum Option<T> {
None,
Some(T),
}
let some_number = Some(5);
let some_char = Some('e');
let absent_number: Option<i32> = None;
不能把類型不同的值做算術(shù)運算,
let x: i8 = 5;
let y: Option<i8> = Some(5);
let sum = x + y;
上述代碼編譯器會報錯。
模式匹配
在 Rust 中,使用 match 來對某個值和一系列模式進行匹配。模式可以是字面量、變量以及其他類型。
每個匹配(arm)都由模式和代碼組成,每個 arm 之間用 , 分隔。模式和代碼之間用 => 相連。
如果代碼為多行,需要放入括號 {}。代碼由表達式組成,如果匹配成功,該表達式的值作為整個 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 可以是任意類型。
模式綁定值
match 的匹配(arm)還有一個常用的特性是把值綁定給匹配的模式。
例如:
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 將會匹配到 Coin::Quarter(Alaska) 這一行,此時 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
一個不漏
match 還有一個特性是:必須匹配(arm)所有可能的模式。
例如上面的代碼,如果改為:
fn plus_one(x: Option<i32>) -> Option<i32> {
match x {
Some(i) => Some(i + 1),
}
}
編譯器會報錯,因為沒有處理 None 模式的匹配。
所以 match 在 Rust 中是 窮盡(exhaustive) 的。
_ 占位符來匹配所有的模式
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 代表其他同種處理的匹配?;蛘呤褂?_ 占位符來表示。
if let
if let 語法提供了一種更簡潔的方式來處理某種模式匹配成功并忽略其他選項的情況。
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)建、測試、共享 crates。
- crates:可生成庫(library)或者可執(zhí)行程序的模塊樹。
- 模塊(module):控制路徑的組織方式、作用域以及私有性。
- 路徑(path):命名一個實體的方式,例如:結(jié)構(gòu)體、函數(shù)、模塊。
包
包 是一系列 crates 的集合。包中有名為 Cargo.toml 的文件定義了如何構(gòu)建這些 crates。
使用 cargo new 命令后,Rust 會在當(dāng)前目錄創(chuàng)建一個包。
包中至少要包含一個 crate。包可以包含多個二進制 crates,但是最多只能包含一個庫 crate。
Cargo 是最常用的包,其默認把 src/main.rs 和 src/lib.rs 作為二進制 crate 和庫 crate,并把兩者作為 crate root。當(dāng)使用 rustc 時,Rust 把這兩個文件(如果存在)編譯。
Crate
在 Rust 中,crate 指的是編譯器所編譯的源文件,是編譯器一次編譯時的最小單位。
crate 包含多個模塊。
crate 分為二進制 crate(binary crate)和庫(library crate)兩種:
二進制 crate 是由 Rustaceans 所編寫的代碼,每個二進制 crate 必須包含一個
main函數(shù)。庫 crate 不含
main函數(shù),不能被編譯為可執(zhí)行程序,而是作為一種共享方式在項目中。
兩種 crate 分別在 src 路徑下以 main.rs 和 lib.rs 兩種文件名稱存在。
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 是一個模塊,可見性為pub。這行代碼表示把garden.rs里的內(nèi)容引入 -
pub mod vegetables作用同上
所以,模塊的工作原理:
從 crate root 開始:當(dāng)編譯 crate 時,編譯器首先找到 crate root 文件(通常是
main.rs或者lib.rs)來編譯-
定義模塊:在 crate root 文件中,可以用
mod關(guān)鍵字聲明新的模塊,例如:mod garden,編譯器會在以下目錄尋找該模塊的代碼:- 行內(nèi)
src/garden.rssrc/garden/mod.rs
-
定義子模塊:在 除 crate root 的文件里還可以定義子模塊,例如:
mod vegetables,編譯器會在其父模塊的目錄下尋找子模塊的代碼:- 行內(nèi)
src/garden/vegetables.rssrc/garden/vegetables/mod.rs
模塊中的路徑:一旦聲明模塊后,可以通過路徑引入模塊。例如:在
vegetables模塊內(nèi)聲明了Asparagus,引入路徑為:crate::garden::vegetables::Asparagus私有 vs 共有:默認情況下,子模塊的內(nèi)容對父模塊是私有的,使用
pub關(guān)鍵字使其公有化use關(guān)鍵字:使用use來簡化引用。
優(yōu)勢
模塊的優(yōu)勢:
- 提高代碼的可讀性和可重用性
- 隱私性
路徑
類似于文件系統(tǒng),有絕對路徑和相對路徑兩種方式來表示層級關(guān)系:
-
絕對路徑:指的是從 crate root 開始的完整路徑。對于外部 crate 來說,絕對路徑以 crate 的名稱為開始;對內(nèi)部 crate 來說,絕對路徑以字面量
crate開始 -
相對路徑:從當(dāng)前模塊開始,通常包含
self、super等關(guān)鍵字
絕對路徑和相對路徑都使用 :: 表示層級間的分隔符。
絕對和相對路徑各有優(yōu)劣,可以根據(jù)個人偏好進行選擇。在 Rust 中,一般使用絕對路徑,原因是這樣使得依賴相對獨立。
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 乃至整個計算機領(lǐng)域都有內(nèi)部實現(xiàn)對外部不可見的原則。
私有的概念通常和作用域(scope)相關(guān)。例如:定義在函數(shù) A 內(nèi)部定義了子函數(shù) B。對該子函數(shù) B 來說,函數(shù) A 對其是可見的。但對函數(shù) A 的其他部分來說,子函數(shù) B 是不可見的,
通常來講,Rust 把項(items)設(shè)置為私有(privacy),或者稱為不可見的。如果調(diào)用了不可見的對象,編譯器會彈出錯誤。
在 Rust 中,默認對父模塊私有的項(items)包括模塊、函數(shù)、方法、結(jié)構(gòu)體、枚舉、常量。
可以使用 pub 關(guān)鍵字使項(items)變?yōu)?strong>可見、公有的。
注:把某個外部對象標識為 pub 并不意味著其內(nèi)部對象也被標識為 pub(枚舉類型除外,如果枚舉類型使用了 pub,那么枚舉的所有結(jié)果默認也為 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**
}
最佳實踐
一般來說,一個包同時包含二進制 crate
src/main.rs以及庫 cratesrc/lib.rs。兩者默認都含有包名。常用的范式是:在
src/lib.rs中定義模塊樹,這樣,在二進制 crate 中調(diào)用任何公有的項(items)都可以以該包名為開始作為路徑。
super
使用 super 關(guān)鍵字來引用父級路徑,這類似于文件系統(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)鍵字來引入路徑。
Rust 的慣例是:在調(diào)用某個函數(shù)時,其路徑應(yīng)該引入到父級。雖然引入到當(dāng)前級效果相同,但是前者使得函數(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)前作用域引入的對象也可以用于其他作用域。因為默認情況下,使用 use 關(guān)鍵把某個名稱引入當(dāng)前作用域后,該名稱對其他作用域是私有的。
使用 pub use 實現(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();
}
- 如果沒有再導(dǎo)出,外部代碼想調(diào)用
add_to_waitlist()函數(shù)必須使用路徑:restaurant::front_of_house::hosting::add_to_waitlist() - 再導(dǎo)出后,使用
restaurant::hosting::add_to_waitlist()即可
嵌套路徑
為了避免使用多個 use 的多行引入導(dǎo)致代碼可讀性變差,可以把同一父對象下的子對象用花括號在同一行中。
use std::{io, fmt};
// same as
use std::io;
use std::fmt;
如果同時引入了父對象和其子對象,使用 self 關(guān)鍵字表示該父對象。
use std::io::{self, Result};
// same as
use std::io;
use std::io::Result;
Glob 運算符
如果要引入全部對象,使用全局 glob 運算符 *。
use std::io::*;
as
假如引入的對象名稱過長,可以使用 as 關(guān)鍵字來通過別名來引入。
use this_is_a_very_long_function_name as lfn;
lfn(); // much simpler
集合
Rust 的標準庫中包含了一系列常用的數(shù)據(jù)結(jié)構(gòu)被稱為集合(collection)。最常用的是:
- 動態(tài)數(shù)組 Vector
- 字符串 String
- 哈希表 HashMap
這些結(jié)構(gòu)的特點是:存儲在堆中,可變長,使用泛型實現(xiàn)。這意味著在編譯時,編譯器并不知道這些結(jié)構(gòu)的大小。
初始化集合的通用方法是 ::new()
動態(tài)數(shù)組
動態(tài)數(shù)組中的元素在內(nèi)存中緊挨著彼此存儲。
動態(tài)數(shù)組只能存儲同種類型的數(shù)據(jù),但是可以借助枚舉來存儲不同類型的數(shù)據(jù)。
初始化
動態(tài)數(shù)組 Vec<T> 的初始化,可以用 ::new() 來初始化一個空動態(tài)數(shù)組,也可以使用 宏(macro) vec![] 顯式把動態(tài)數(shù)組的成員列出來初始化動態(tài)數(shù)組:
fn main() {
let v = vec![1, 2, 3, 4, 5];
let ano_v: Vec<i32> = Vec::new();
}
注:在第二種聲明中指明了存儲元素的類型,否則 Rust 并不知道 Vector 要存儲什么類型的數(shù)據(jù)。
讀寫
寫
使用 push 方法給動態(tài)數(shù)組添加元素:
let mut v = Vec::new();
v.push(1);
v.push(2);
讀
使用 .get() 或者括號索引 [] 的方式來訪問動態(tài)數(shù)組元素:
let third: &i32 = &v[2]; // 3
let two: Option<&i32> = v.get(2); // Some(2)
如上面的代碼所示,使用 .get() 方法得到的是 Option<T> 數(shù)據(jù)類型,而不是動態(tài)數(shù)組元素 <T> 的類型。
由于 .get() 方法得到的是 Option<T> 類型,因此可以使用 match 來對取得的值做判斷。
let two = v.get(2);
match two {
Some(two) => println!("The element is {}", two),
None => println!("No such element"),
}
越界
兩種訪問方式對于動態(tài)數(shù)組越界有著不同的處理方式:
let third = &v[100]; // index out of bound
let two = v.get(100); // None
使用 [] 索引訪問可以通過編譯,但在運行時會出現(xiàn) index out of bound 索引越界的錯誤;使用 .get() 方法會得到 None。
下面的例子說明了動態(tài)數(shù)組的工作方式:
let mut v = vec![1, 2, 3, 4, 5];
let first = &v[0];
v.push(6);
println!("{}", first); // Error
上面的代碼不能通過編譯,原因是:由于動態(tài)數(shù)組的元素在內(nèi)存中是緊挨著彼此存儲的。因此,給動態(tài)數(shù)組中添加新元素時,如果當(dāng)前的內(nèi)存空間位置不能容下新加入的元素,就需要分配一塊新的內(nèi)存空間并拷貝舊的元素到該空間。而引用訪問動態(tài)數(shù)組元素也許會導(dǎo)致訪問到已經(jīng)被解除分配的空間。
遍歷
使用 for in 來遍歷動態(tài)數(shù)組元素:
for i in &v {
println!("{}", i);
}
使用 mut 引用來遍歷并變動態(tài)數(shù)組元素:
for i in &mut v {
*i += 50;
}
字符串
兩類字符串的比較
Rust 語言的核心中只有一種字符串:字符串切片 str,通常以 &str 形式出現(xiàn)。
而 String 類型由 Rust 標準庫實現(xiàn)的。
兩者都是 UTF-8 編碼的。
初始化
字符串 String 的初始化,可以用 ::new() 來初始化一個空字符串,也可以用 ::from()顯式初始化字符串,或者先聲明字符串字面量,然后轉(zhuǎn)化為 String 類型的字符串:
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();
}
讀寫
寫
使用 push_str() 把字符串拼接至另一字符串尾:
s.push_str(", World");
println!("{}", s) // Hello, World
使用 push() 拼接一個字符到字符串尾:
let mut s = String::from("lo");
s.push('l');
println!("{}", s) // "lol"
使用 + 來拼接已有字符串:
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 不能再被使用。這是因為 add 函數(shù)的簽名:
fn add(self, s: &str) -> String {
決定了 self 位置的變量的 ownership 被奪取。第二個變量需要使用 & 引用形式,而不是直接把兩個字符串的值相加。
這里編譯器使用 強制轉(zhuǎn)換(coerce) 把 String 類型轉(zhuǎn)化為 &str,當(dāng)調(diào)用 add 時,Rust 會使用 解引用強制轉(zhuǎn)換(deref coercio n) 把 &s2 轉(zhuǎn)化為 &s2[..]。
如果不希望 s1 的 ownership 發(fā)生變化,可以使用 format! 宏(macro) 來拼接字符串:
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 不支持索引訪問字符串中的字符。
let s1 = String::from("Code");
println!("{}", s1[0]); // Error
上面的代碼將不能通過編譯,原因和 String 類型的內(nèi)部實現(xiàn)有關(guān):
String 類型是對 Vec<u8> 的包裝,所以:
let hello = String::from("Hola");
let ano_hello = String::from("Здравствуйте");
hello 的 len 為 4,因為在 UTF-8 編碼中每個字符占用 1 個字節(jié),而 ano_hello 的 len 為 24,而非 12,因為在 UTF-8 編碼中,每個 Unicode scalar 值占用 2 個字節(jié)。因此,如果使用索引訪問,將返回?zé)o意義的值。
可以使用 [..] 創(chuàng)建字符串切片:
let ano_hello = "Здравствуйте";
// let ano_hello = String::from("Здравствуйте"); // also Ok
let s = &ano_hello[0..4]; // Зд
s 是 ano_hello 的前 4 個字節(jié),而非字符。
類型轉(zhuǎn)換
使用 to_string 把其他類型轉(zhuǎn)化為字符串:
let i: i32 = 2;
let i_s: String = i.to_string(); // "2";
遍歷
使用 .chars() 獲得字符串的序列,并用 for in 來遍歷以輸出字符串的字符:
let s = String::from("Hello");
for c in s.chars() {
println!("{}", c);
}
// H
// e
// l
// l
// o
類似地,使用 .bytes() 或者字符對應(yīng)的字節(jié)序列,并用 for in 來遍歷以輸出字符串的字符:
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;
哈希表的鍵類型為 String i32,鍵和值必須為相同類型。
初始化
可以用 ::new() 來初始化一個空哈希表:
fn main() {
let hm = HashMap::new();
}
讀寫
寫
使用 .insert() 添加鍵值對到哈希表(注意: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
和動態(tài)數(shù)組類似,獲取的值是 Option<&V> 類型,可以使用 unwrap() 獲取 <T> 類型。
遍歷
使用元組遍歷哈希表:
for (key, value) in &scores {
println!("{} {}", key, value);
}
// One Possible Outcome:
// Blue 10
// Yellow 30
// Black 20
注:遍歷的結(jié)果是無序的。
更新
哈希表的更新有幾種不同的方式:
如果給同一個鍵添加多個值,結(jié)果是只保留最后一個值:
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() 添加鍵值對:
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 枚舉類型,該枚舉類型返回指定的鍵是否存在,or_insert() 構(gòu)建在 Entry 之上,如果鍵存在就不做變,如果不存在就添加該鍵值對。