Rust 筆記 -- 結(jié)構(gòu)體寿酌、枚舉、模塊系統(tǒng)硕蛹、集合

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)有具體的上下文

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ǔ)言中治拿,變量總是有 nullnon-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,
      }
}

matchif不同在于: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)包括:

  1. 包(package):構(gòu)建做修、測(cè)試、共享 crates抡草。
  2. crates:可生成庫(kù)(library)或者可執(zhí)行程序的模塊樹(shù)饰及。
  3. 模塊(module):控制路徑的組織方式、作用域以及私有性渠牲。
  4. 路徑(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.rssrc/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.rslib.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)始葵擎,通常包含 selfsuper 等關(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ù) crate src/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("Здравствуйте");

hellolen4多柑,因?yàn)樵?UTF-8 編碼中每個(gè)字符占用 1 個(gè)字節(jié),而 ano_hellolen24聂沙,而非 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]; // Зд

sano_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ì)雕憔。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末姨丈,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子翁潘,更是在濱河造成了極大的恐慌,老刑警劉巖俩莽,帶你破解...
    沈念sama閱讀 218,284評(píng)論 6 506
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異出刷,居然都是意外死亡馁龟,警方通過(guò)查閱死者的電腦和手機(jī)却音,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,115評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門(mén),熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人肌稻,你說(shuō)我怎么就攤上這事爹谭∨捣玻” “怎么了?”我有些...
    開(kāi)封第一講書(shū)人閱讀 164,614評(píng)論 0 354
  • 文/不壞的土叔 我叫張陵凉袱,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我涤躲,道長(zhǎng),這世上最難降的妖魔是什么缸托? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,671評(píng)論 1 293
  • 正文 為了忘掉前任,我火速辦了婚禮佩抹,結(jié)果婚禮上无宿,老公的妹妹穿的比我還像新娘。我一直安慰自己栏豺,他們只是感情好彬碱,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,699評(píng)論 6 392
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著奥洼,像睡著了一般巷疼。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上灵奖,一...
    開(kāi)封第一講書(shū)人閱讀 51,562評(píng)論 1 305
  • 那天嚼沿,我揣著相機(jī)與錄音,去河邊找鬼瓷患。 笑死爆阶,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的吆豹。 我是一名探鬼主播,決...
    沈念sama閱讀 40,309評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼调窍,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼湿刽!你這毒婦竟也來(lái)了雅镊?” 一聲冷哼從身側(cè)響起卓缰,我...
    開(kāi)封第一講書(shū)人閱讀 39,223評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤理肺,失蹤者是張志新(化名)和其女友劉穎乎完,沒(méi)想到半個(gè)月后典格,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,668評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡番官,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,859評(píng)論 3 336
  • 正文 我和宋清朗相戀三年山孔,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了譬挚。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片阶冈。...
    茶點(diǎn)故事閱讀 39,981評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖烹吵,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情蛉顽,我是刑警寧澤,帶...
    沈念sama閱讀 35,705評(píng)論 5 347
  • 正文 年R本政府宣布,位于F島的核電站,受9級(jí)特大地震影響蜜托,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,310評(píng)論 3 330
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望卑笨。 院中可真熱鬧采蚀,春花似錦、人聲如沸嵌赠。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 31,904評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)窖铡。三九已至费彼,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背蓝晒。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 33,023評(píng)論 1 270
  • 我被黑心中介騙來(lái)泰國(guó)打工口注, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留变擒,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 48,146評(píng)論 3 370
  • 正文 我出身青樓寝志,卻偏偏與公主長(zhǎng)得像娇斑,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子材部,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,933評(píng)論 2 355

推薦閱讀更多精彩內(nèi)容