Rust語言精要

變量

聲明變量關(guān)鍵字:let

變量值分為兩種類型:

  • 可變的(mut
  • 不可變

變量類型:

  • 布爾型 - bool 表示 true 或 false
  • 無符號整型- u8 u32 u64 u128 表示正整數(shù)
  • 有符號整型 - i8 i32 i64 i128 表示正負(fù)整數(shù)
  • 指針大小的整數(shù) - usize isize 表示內(nèi)存中內(nèi)容的索引和大小
  • 浮點(diǎn)數(shù) - f32 f64
  • 元組(tuple) - (value, value, ...) 用于在棧上傳遞固定序列的值
  • 數(shù)組 - 在編譯時(shí)已知的具有固定長度的相同元素的集合
  • 切片(slice) - 在運(yùn)行時(shí)已知長度的相同元素的集合
  • str(string slice) - 在運(yùn)行時(shí)已知長度的文本

可以通過將類型附加到數(shù)字的末尾來明確指定數(shù)字類型(如 13u322u8)。

使用as進(jìn)行類型轉(zhuǎn)換:

let a = 13u8;
let b = 7u32;
let c = a as u32 + b;

元組 & 數(shù)組

數(shù)組

let arr: [i32; 3] = [1, 2, 3];

數(shù)組初始就固定了長度,即使聲明為mut也只能修改索引上的元素,而不是數(shù)組本身。

元組

let tuple: (&'static str, i32, char) = ("hello", 5, 'c');
let (x, y ,z) = tuple;

當(dāng)元組只有一個(gè)值的時(shí)候,需要寫成(x, ),這是為了和括號中的其他值進(jìn)行區(qū)分。

表達(dá)式

let x = (let y = 6);    //error

let沒有右值語義,Rust中不允許這么用,可以這么寫:

let y = {
    let x = 3;
    x
};

函數(shù)

函數(shù)

多個(gè)返回值

函數(shù)可以通過元組來返回多個(gè)值。

元組元素可以通過他們的索引來獲取。

支持各種形式的解構(gòu),允許我們以符合人類工程學(xué)的方式提取數(shù)據(jù)結(jié)構(gòu)的子片段。

返回空

如果沒有為函數(shù)指定返回類型,它將返回一個(gè)空的元組,也稱為單元

一個(gè)空的元組用 () 表示。

函數(shù)指針

函數(shù)指針實(shí)現(xiàn)了所有三個(gè)閉包 trait(Fn、FnMut 和 FnOnce),所以總是可以在調(diào)用期望閉包的函數(shù)時(shí)傳遞函數(shù)指針作為參數(shù)。傾向于編寫使用泛型和閉包 trait 的函數(shù),這樣它就能接受函數(shù)或閉包作為參數(shù)。

函數(shù)作為參數(shù)

pub fn math(op: fn(i32, i32) -> i32, a: i32, b: i32) -> i32 {
    op(a, b)
}
fn sum(a: i32, b:i32) -> i32 {
    a + b
}
fn main() {
    let a =2;
    let b =3;
    assert_eq!(math(sum, a, ,b), 5);
}

函數(shù)作為返回值

fn is_true() -> bool { true }
fn true_maker() -> fn() -> bool { is_true }
fn main() {
    assert_eq!(true_maker()(), true);
}

閉包

閉包在函數(shù)中的應(yīng)用,常常與trait結(jié)合。

閉包作為參數(shù)

fn closure_math<F: Fn() -> i32>(op: F) -> i32 {
    op()
}
fn main() {
    let a = 2;
    let b = 3;
    assert_eq!(closure_math(|| a + b ), 5);
}

閉包作為返回值

fn two_times_impl() -> impl Fn(i32) -> i32 {
    let i = 2;
    move |j| j * i
}
fn main() {
    let result = two_times_impl();
    assert_eq!(result(2), 4);
}

閉包默認(rèn)會(huì)按引用捕獲變量(在此例中為 i )。如果將此閉包返回,則引用也會(huì)跟著返回。而 i 會(huì)被銷毀,所以引用變?yōu)閼掖怪羔?。因此要加上move關(guān)鍵字,移動(dòng)所有權(quán)。

或者如下寫法,用Box<T>

fn return_clo() -> Box<dyn Fn(i32)->i32> {
    Box::new(|x| x+1)
}

fn main() {
    let c = return_clo();
    println!("1 + 1 = {}", c(1));
    println!("1 + 1 = {}", (*c)(1)); //解引用多態(tài)
    println!("Hello, world!");
}

捕獲環(huán)境值

閉包可以通過三種方式捕獲其環(huán)境,它們對應(yīng)函數(shù)的三種獲取參數(shù)的方式,分別是獲取所有權(quán)、可變借用、不可變借用。這三種捕獲值的方式被編碼為如下三個(gè)Fn trait:
(1)FnOnce消費(fèi)從周圍作用域捕獲的變量,閉包周圍的作用域被稱為其環(huán)境。為了消費(fèi)捕獲到的變量,閉包必須獲取其所有權(quán)并在定義閉包時(shí)將其移進(jìn)閉包。其名稱的Once部分代表了閉包不能多次獲取相同變量的所有權(quán)。
(2)FnMut獲取可變的借用值,所以可以改變其環(huán)境。
(3)Fn從其環(huán)境獲取不可變的借用值。
當(dāng)創(chuàng)建一個(gè)閉包時(shí),rust會(huì)根據(jù)其如何使用環(huán)境中的變量來推斷我們希望如何引用環(huán)境。由于所有閉包都可以被調(diào)用至少一次,因此所有閉包都實(shí)現(xiàn)了FnOnce。沒有移動(dòng)被捕獲變量的所有權(quán)到閉包的閉包也實(shí)現(xiàn)了FnMut,而不需要對捕獲的變量進(jìn)行可變訪問的閉包實(shí)現(xiàn)了Fn。

自動(dòng)推導(dǎo)

閉包會(huì)為每個(gè)參數(shù)和返回類型推導(dǎo)一個(gè)具體類型,但是不能推導(dǎo)兩次。如下錯(cuò)誤:

let example_closure = |x| x;
let s = example_closure(String::from("hello"));
let n = example_closure(5); //報(bào)錯(cuò),嘗試推導(dǎo)兩次,變成了不同的類型

與trait結(jié)合

struct Cacher<T> 
    where T: Fn(u32) -> u32
{
    calcuation: T,
    value: Option<u32>,
}

impl<T> Cacher<T>
    where T: Fn(u32) -> u32
{
    fn new(calcuation: T) -> Cacher<T> {
        Cacher {
            calcuation,
            value: None,
        }
    }
}

fn main() {
    let mut c = Cacher::new(|x| x+1);
}

流程控制

while

略。

for

for x in 0..5{
  printfln!("{}",x);
}
// 0..=5 包含5
let number = 3;
if number{} //error

Rust不能從number推斷出bool值。另,if后的判斷表達(dá)式不需要括號。

從塊表達(dá)式返回值

if let

let number = if condition {
    5
} else {
    6
};

if、else返回值的類型必須是相同的。當(dāng)所有ifelse if塊無法匹配時(shí),調(diào)用任何一個(gè)else塊,如果無else,則返回()。因此,此代碼中的if表達(dá)式的else塊(雖然沒有顯式寫出)返回值為(),與if塊中的i32類型不一致,報(bào)E0308錯(cuò)誤。舉例如下:

fn r(n: i32) -> i32 {
    if n > 0 {
        0
    }
    1
}

match let

let result = match food {
        "hotdog" => "is hotdog",
        // 注意,當(dāng)它只是一個(gè)返回表達(dá)式時(shí),大括號是可選的
        _ => "is not hotdog",
};

loop

let result = loop {
    counter += 1;
    if counter == 10 {
        // loop 可以被中斷以返回一個(gè)值。
        break counter * 2;
    }
};
//result等于20。

避免使用while true {...},使用loop。Rust使用LLVM,而LLVM沒有表達(dá)無限循環(huán)的方式,因此在某些時(shí)候會(huì)出錯(cuò)。如下:

let x;
while true { x = 1; break; }
println!("{}", x);

編譯器報(bào)錯(cuò),"use of possibly uninitialised variable"。

match

match 是窮盡的,意為所有可能的值都必須被考慮到。

match x {
        0 => {
            println!("found zero");
        }
        // 我們可以匹配多個(gè)值
        1 | 2 => {
            println!("found 1 or 2!");
        }
        // 我們可以匹配迭代器
        3..=9 => {
            println!("found a number 3 to 9 inclusively");
        }
        // 我們可以將匹配數(shù)值綁定到變量
        matched_num @ 10..=100 => {
            println!("found {} number between 10 to 100!", matched_num);
        }
        // 這是默認(rèn)匹配,如果沒有處理所有情況,則必須存在該匹配
        _ => {
            println!("found something else!");
        }
    }

結(jié)構(gòu)體

Rust中結(jié)構(gòu)體有:具名結(jié)構(gòu)體、元組結(jié)構(gòu)體、單元結(jié)構(gòu)體。

具名結(jié)構(gòu)體

struct SeaCreature {
    animal_type: String,
    name: String,
}

元祖結(jié)構(gòu)體

// 這仍然是一個(gè)在棧上的結(jié)構(gòu)體
let loc = Location(42, 32);

單元結(jié)構(gòu)體

struct Marker;

其中,元組結(jié)構(gòu)體只有一個(gè)字段時(shí),稱之為New Type模式。

方法(封裝特性)

與函數(shù)(function)不同,方法(method)是與特定數(shù)據(jù)類型關(guān)聯(lián)的函數(shù)。

靜態(tài)方法 — 屬于某個(gè)類型,調(diào)用時(shí)使用 :: 運(yùn)算符。

實(shí)例方法 — 屬于某個(gè)類型的實(shí)例,調(diào)用時(shí)使用 . 運(yùn)算符。

struct SeaCreature {
    noise: String,
}

impl SeaCreature {
    fn get_sound(&self) -> &str {
        &self.noise
    }
}

fn main() {
    let creature = SeaCreature {
      // 靜態(tài)方法
        noise: String::from("blub"),
    };
  // 實(shí)例方法
    println!("{}", creature.get_sound());
}

枚舉

enum Species {
    Crab,
    Octopus,
    Fish,
    Clam
}
match ferris.species {
        Species::Crab => println!("{} is a crab",ferris.name),
            _ => xxx,
}

enum 的元素可以有一個(gè)或多個(gè)數(shù)據(jù)類型,從而使其表現(xiàn)得像 C 語言中的聯(lián)合

當(dāng)使用 match 對一個(gè) enum 進(jìn)行模式匹配時(shí),可以將變量名稱綁定到每個(gè)數(shù)據(jù)值。

enum Weapon {
    Claw(i32, Size),
    Poison(PoisonType),
    None
}
...{
  ...
  weapon: Weapon::Claw(2, Size::Small),
}
match ferris.weapon {
  // num_claws 獲取 2
    Weapon::Claw(num_claws,size) => {
      ...
    }
}

泛型

// 一個(gè)部分定義的結(jié)構(gòu)體類型
struct BagOfHolding<T> {
    item: T,
}

fn main() {
    // 注意:通過使用泛型,我們創(chuàng)建了編譯時(shí)創(chuàng)建的類型,使代碼更大
    // Turbofish 使之顯式化
    let i32_bag = BagOfHolding::<i32> { item: 42 };
    let bool_bag = BagOfHolding::<bool> { item: true };
    
    // Rust 也可以推斷出泛型的類型!
    let float_bag = BagOfHolding { item: 3.14 };

    let bag_in_bag = BagOfHolding {
        item: BagOfHolding { item: "boom!" },
    };
}

常用的內(nèi)置泛型:

enum Option<T> {
    None,
    Some(T),
}
enum Result<T, E> {
    Ok(T),
    Err(E),
}

Result 如此常見以至于 Rust 有個(gè)強(qiáng)大的操作符 ? 來與之配合。

option

用法

Option主要有以下一些用法:

  • 初始化值;
  • 作為在整個(gè)輸入范圍內(nèi)沒有定義的函數(shù)的返回值;
  • 作為返回值,用None表示出現(xiàn)的簡單錯(cuò)誤;
  • 作為結(jié)構(gòu)體的可選字段;
  • 作為結(jié)構(gòu)體中可借出或者是可載入的字段;
  • 作為函數(shù)的可選參數(shù);
  • 代表空指針;
  • 用作復(fù)雜情況的返回值。

值復(fù)制方法

let x = 123u8;
let y: Option<&u8> = Some(&x);
let z = y.copied();

copied將引用轉(zhuǎn)換為值。

Vectors

Vec 有一個(gè)形如 iter() 的方法可以為一個(gè) vector 創(chuàng)建迭代器,這允許我們可以輕松地將 vector 用到 for 循環(huán)中去。

fn main() {
    // 我們可以顯式確定類型
    let mut i32_vec = Vec::<i32>::new(); // turbofish <3
    i32_vec.push(1);

    // 自動(dòng)檢測類型
    let mut float_vec = Vec::new();
    float_vec.push(1.3);

    // 宏!
    let string_vec = vec![String::from("Hello"), String::from("World")];

    for word in string_vec.iter() {
        println!("{}", word);
    }
}

內(nèi)存細(xì)節(jié):

  • Vec 是一個(gè)結(jié)構(gòu)體,但是內(nèi)部其實(shí)保存了在堆上固定長度數(shù)據(jù)的引用。
  • 一個(gè) vector 開始有默認(rèn)大小容量,當(dāng)更多的元素被添加進(jìn)來后,它會(huì)重新在堆上分配一個(gè)新的并具有更大容量的定長列表。(類似 C++ 的 vector)

迭代器

迭代器實(shí)現(xiàn)了Iteratortrait),定義于標(biāo)準(zhǔn)庫中。該trait定義如下:

trait Iterator {
    type Item;
    fn next(mut self) -> Option<Self::Item>;
    //省略其它內(nèi)容
}

如果希望迭代可變引用,可以使用iter_mut。

切片

Slice是對一個(gè)數(shù)組的引用片段,代表一個(gè)指向數(shù)組起始位置的指針和數(shù)組長度。

Slice是一種新的類型,而不是簡單的一個(gè)引用而已。

let arr: [i32;2]=[1,2];
assert_eq!((&arr).len(), 2);
assert_eq!(&arr.len(), &2);

str(字符串切片)

字符串常量是&'static str。str字符串是固定長度的,String字符串是可變長度的。

let hello_world = "Hello, World!"; 聲明了一個(gè) &str類型;

let hello_world: &'static str = "Hello, world!"; hello_world 與 字符串常量一樣。

utf8

// &a[3..7]表示螃蟹
let a = "hi ??";

// chars[3]表示螃蟹,chars[i]表示一個(gè)字符,每個(gè)char占4字節(jié)
let chars = "hi ??".chars().collect::<Vec<char>>();

String

String不能用index訪問的原因:

  • 避免根據(jù)UTF-8編碼得到的長度與期望的不一致;
  • 根據(jù)index訪問需要從頭遍歷(在Rust中需要判斷有效字符數(shù)量),所以訪問的時(shí)間復(fù)雜度不為O(1)。

String格式

let haiku: &'static str = "
        I write, erase, rewrite
        - Katsushika Hokusai";
println!("{}", haiku);
    
println!("hello \
world") // notice that the spacing before w is ignored

let a: &'static str = r#"
        <div class="advice">
            Raw strings are useful for some situations.
        </div>
        "#;

// 從文件讀取大量字符串
let 00_html = include_str!("00_en.html");

HashMap

創(chuàng)建

HashMap::new();
let scores: HashMap<_, _> = keys.iter().zip(values.iter()).collect(); 

讀取

let key = String::from("Blue");
let value = ss.get(&key); 

遍歷

for (key, value) in &ss

更新

ss.insert(String::from("Blue"), 20);//會(huì)將之前Blue對應(yīng)的值覆蓋掉
ss.entry(String::from("Yellow")).or_insert(20); // 沒有實(shí)體時(shí)插入

// 根據(jù)舊值更新
for word in text.split_whitespace() {
    let count = map.entry(word).or_insert(0);
    *count += 1;
}

所有權(quán)

所有權(quán)是Rust的特性。所有權(quán)解決了堆棧分配與回收問題。

內(nèi)存分配

語言 內(nèi)存回收機(jī)制
其他語言 GC會(huì)跟蹤聲明的變量,當(dāng)它不再被使用時(shí),自動(dòng)清除。如果沒有GC,程序員負(fù)責(zé)在恰當(dāng)?shù)臅r(shí)候釋放這段申請的內(nèi)存。
Rust 使用RAII(“資源獲取即初始化”),在變量invalid時(shí),調(diào)用drop回收。

Move,Copy,Clone

Move

let s1 = String::from("hello");
let s2 = s1;

s1移動(dòng)到了s2,不僅僅是shallow copy,s1還被置為invalid了。棧上的s1對象和s2對象進(jìn)行按位淺拷貝,堆上數(shù)據(jù)不變。

將所有者作為參數(shù)傳遞給函數(shù)時(shí),其所有權(quán)將移交至該函數(shù)的參數(shù)。 在一次移動(dòng)后,原函數(shù)中的變量將無法再被使用。在移動(dòng)期間,所有者的堆棧值將會(huì)被復(fù)制到函數(shù)調(diào)用的參數(shù)堆棧中。

Rust不會(huì)自動(dòng)創(chuàng)建“深拷貝”,需要自己用Clone()。但是,如果實(shí)現(xiàn)了Copytrait,那么值會(huì)被復(fù)制入棧。

Copy

實(shí)現(xiàn)Copy的類型在堆上沒有資源,值完全處于棧上。淺拷貝后,源與目標(biāo)對象都可以訪問,是獨(dú)立的數(shù)據(jù)。為了#[derive(Copy, Clone)]工作,成員也必須實(shí)現(xiàn)Copy。

在派生語句中的Clone是需要的,因?yàn)镃opy的定義類似這樣:pub trait Copy:Clone {},即要實(shí)現(xiàn)Copy需要先實(shí)現(xiàn)Clone

Copy與Drop不能同時(shí)存在。

Drop

變量在離開作用范圍時(shí),編譯器會(huì)自動(dòng)銷毀變量,如果變量類型有Drop trait,就先調(diào)用Drop::drop方法,做資源清理,一般會(huì)回收heap內(nèi)存等資源,然后再收回變量所占用的stack內(nèi)存。如果變量沒有Drop trait,那就只收回stack內(nèi)存。

如果類型實(shí)現(xiàn)了Copy trait,在copy語義中并不會(huì)調(diào)用Clone::clone方法,不會(huì)做deep copy,那就會(huì)出現(xiàn)兩個(gè)變量同時(shí)擁有一個(gè)資源(比如說是heap內(nèi)存等),在這兩個(gè)變量離開作用范圍時(shí),會(huì)分別調(diào)用Drop::drop方法釋放資源,這就會(huì)出現(xiàn)double free錯(cuò)誤。

Clone

幫助實(shí)現(xiàn)“深拷貝”。

釋放

釋放是分級進(jìn)行的。刪除一個(gè)結(jié)構(gòu)體時(shí),結(jié)構(gòu)體本身會(huì)先被釋放,緊接著才分別釋放相應(yīng)的子結(jié)構(gòu)體并以此類推。

內(nèi)存細(xì)節(jié):

  • Rust 通過自動(dòng)釋放內(nèi)存來幫助確保減少內(nèi)存泄漏。
  • 每個(gè)內(nèi)存資源僅會(huì)被釋放一次。

引用

引用默認(rèn)也是不可變的。可變引用才可以修改被引用的值。已經(jīng)被引用的變量,其所有權(quán)不可以被移動(dòng)。

可變引用(&mut)

  • 可變引用只能出現(xiàn)一次,避免數(shù)據(jù)競爭。
  • 已有不可變引用,可變引用就不能再出現(xiàn)。

解引用

使用 &mut 引用時(shí), 你可以通過 * 操作符來修改其指向的值。 你也可以使用 * 操作符來對所擁有的值進(jìn)行拷貝(前提是該值可以被拷貝)。

操作符"."可以自動(dòng)解引用:

let f = Foo { value: 42 };
let ref_ref_ref_f = &&&f;
println!("{}", ref_ref_ref_f.value);

解引用多態(tài)與可變性交互

解引用多態(tài)有如下三種情況:

  • 當(dāng) T: Deref<Target=U> 時(shí)從 &T 到 &U。
  • 當(dāng) T: DerefMut<Target=U> 時(shí)從 &mut T 到 &mut U。
  • 當(dāng) T: Deref<Target=U> 時(shí)從 &mut T 到 &U。(注意:此處反之是不可能的)

生命周期

生命周期的主要目標(biāo)是避免懸垂引用,大部分時(shí)候是可以隱含并且被推斷的。

顯式生命周期

盡管 Rust 不總是在代碼中將它展示出來,但編譯器會(huì)理解每一個(gè)變量的生命周期并進(jìn)行驗(yàn)證以確保一個(gè)引用不會(huì)有長于其所有者的存在時(shí)間。 同時(shí),函數(shù)可以通過使用一些符號來參數(shù)化函數(shù)簽名,以幫助界定哪些參數(shù)和返回值共享同一生命周期。 生命周期注解總是以 ' 開頭,例如 'a'b 以及 'c。

// 參數(shù) foo 和返回值共享同一生命周期
fn do_something<'a>(foo: &'a Foo) -> &'a i32 {
    return &foo.x;
}

// foo_b 和返回值共享同一生命周期
// foo_a 則擁有另一個(gè)不相關(guān)聯(lián)的生命周期
fn do_something<'a, 'b>(foo_a: &'a Foo, foo_b: &'b Foo) -> &'b i32 {
    println!("{}", foo_a.x);
    println!("{}", foo_b.x);
    return &foo_b.x;
}

// 靜態(tài)變量的范圍也可以被限制在一個(gè)函數(shù)內(nèi)
static mut SECRET: &'static str = "swordfish";

// 字符串字面值擁有 'static 生命周期
let msg: &'static str = "Hello World!";

隱式生命周期

三條規(guī)則確定不需要生命周期注解:

  • 第一條規(guī)則是:每一個(gè)是引用的參數(shù)都有它自己的生命周期參數(shù)。
  • 第二條規(guī)則是:如果只有一個(gè)輸入生命周期參數(shù),那么它被賦予所有輸出生命周期參數(shù):fn foo<'a>(x: &'a i32) -> &'a i32。
  • 第三條規(guī)則是:在struct的impl語句中,如果方法有多個(gè)輸入生命周期參數(shù),不過其中之一因?yàn)榉椒ǖ木壒蕿?&self&mut self,那么 self 的生命周期被賦給所有輸出生命周期參數(shù)。第三條規(guī)則使得方法更容易讀寫,因?yàn)橹恍韪俚姆枴?/li>
use std::str::FromStr;
pub struct Wrapper<'a>(&'a str);

impl<'a> FromStr for Wrapper<'a> {
    type Err = ();
    fn from_str(s: &str) -> Result<Self, Self::Err> {
        Ok(Wrapper(s))
    }
}

在上述例子中,fn from_str函數(shù)顯然是符合第二條規(guī)則,也就是說入?yún): &str的生命周期被賦予為輸出的生命周期。但是,輸出參數(shù)中的Self對應(yīng)的類型為結(jié)構(gòu)體Wrapper,而Wrapper是有生命周期的限制的,此時(shí)編譯器不知道如何判斷,因此報(bào)錯(cuò)。

結(jié)構(gòu)體生命周期

如果結(jié)構(gòu)體成員含有引用類型,則需要顯式指定生命周期。如下:

struct StuA<'a> {
    name: &'a str,
}

相應(yīng)的,在方法中,也需要聲明結(jié)構(gòu)體的生命周期:

impl<'b> StuA<'b> {
  // 隱式生命周期第二條規(guī)則
    fn do_something(&self) -> i32 {
        3
    }

    fn do_something2(&self, s: &str) -> &str{
      // 隱式生命周期第三條規(guī)則
    //相當(dāng)于fn do_something2<'b>(&'b self, s: &str) -> &'b str{
      // self.name與self生命周期相同,?
        self.name
    }

  // 返回值生命周期與s相同,而不是self,所以需要顯式指定
    fn do_something3<'a>(&self, s: &'a str) -> &'a str{
        s
    }
}

Trait(多態(tài))

在Rust中,trait是唯一的接口抽象方式。Rust中沒有繼承,貫徹的是組合優(yōu)于繼承和面向接口編程的思想。

trait相關(guān)詳情

注釋

文檔注釋

內(nèi)部支持Markdown標(biāo)記,也支持對文檔中的示例代碼進(jìn)行測試,可以用rustdoc生成HTML文檔。

  • /// :生成庫文檔,用于函數(shù)或結(jié)構(gòu)體的說明;
  • //! :生成庫文檔,用于說明整個(gè)模塊的功能;

println!宏

  • println!("{}", 2),nothing表示Display;
  • println!("{:?}", 2),?表示Debug;
  • o代表八進(jìn)制,x/X表示十六進(jìn)制,b表示二進(jìn)制;
  • p代表指針;
  • e/E表示指數(shù);

內(nèi)存布局

Rust 程序有 3 個(gè)存放數(shù)據(jù)的內(nèi)存區(qū)域:

  • 數(shù)據(jù)內(nèi)存 - 對于固定大小和靜態(tài)(即在整個(gè)程序聲明周期中都存在)的數(shù)據(jù)。 例如 “Hello World”字面值常量,該文本的字節(jié)只能讀取,因此它們位于該區(qū)域中。 編譯器對這類數(shù)據(jù)做了很多優(yōu)化,由于位置已知且固定,因此通常認(rèn)為編譯器使用起來非常快。
  • 棧內(nèi)存 - 對于在函數(shù)中聲明為變量的數(shù)據(jù)。 在函數(shù)調(diào)用期間,內(nèi)存的位置不會(huì)改變,因?yàn)榫幾g器可以優(yōu)化代碼,所以棧數(shù)據(jù)使用起來非常快。
  • 堆內(nèi)存 - 對于在程序運(yùn)行時(shí)創(chuàng)建的數(shù)據(jù)。 此區(qū)域中的數(shù)據(jù)可以添加、移動(dòng)、刪除、調(diào)整大小等。由于它的動(dòng)態(tài)特性,通常認(rèn)為它使用起來比較慢, 但是它允許更多創(chuàng)造性的內(nèi)存使用。當(dāng)數(shù)據(jù)添加到該區(qū)域時(shí),我們稱其為分配。 從本區(qū)域中刪除 數(shù)據(jù)后,我們將其稱為釋放。

結(jié)構(gòu)體內(nèi)存對齊

對齊規(guī)則:

  • 每種類型都有一個(gè)數(shù)據(jù)對齊屬性。在X86平臺(tái)上u64和f64都是按照32位對齊的。
  • 一種類型的大小是它對齊屬性的整數(shù)倍,這保證了這種類型的值在數(shù)組中的偏移量都是其類型尺寸的整數(shù)倍,可以按照偏移量進(jìn)行索引。需要注意的是,動(dòng)態(tài)尺寸類型的大小和對齊可能無法靜態(tài)獲取。
  • 結(jié)構(gòu)體的對齊屬性等于它所有成員的對齊屬性中最大的那個(gè)。Rust會(huì)在必要的位置填充空白數(shù)據(jù),以保證每一個(gè)成員都正確地對齊,同時(shí)整個(gè)類型的尺寸是對齊屬性的整數(shù)倍。
  • 不保證數(shù)據(jù)填充和成員順序,編譯器可能進(jìn)行優(yōu)化。
struct A {
    a: u8,
    b: u32,
    c: u16
}

按照前3條規(guī)則,A的大小應(yīng)該為12字節(jié),而實(shí)際上編譯后可能只有8字節(jié)。

指針

原生指針:

  • *const T - 指針常量。
  • *mut T - 可變指針。

取得指針?biāo)傅刂穬?nèi)的數(shù)據(jù),需要在unsafe{...}中,因?yàn)椴荒鼙WC該原生指針指向有效數(shù)據(jù)。

智能指針

智能指針通常使用結(jié)構(gòu)體實(shí)現(xiàn)。智能指針區(qū)別于常規(guī)結(jié)構(gòu)體的顯著特征在于其實(shí)現(xiàn)了Deref和Drop trait。

  • Deref trait允許智能指針結(jié)構(gòu)體實(shí)例表現(xiàn)的像引用一樣,這樣就可以編寫既用于引用,又用于智能指針的代碼。
  • Drop trait允許我們自定義當(dāng)智能指針離開作用域時(shí)執(zhí)行的代碼。

Box

Box將數(shù)據(jù)從棧上移動(dòng)到堆,棧上存放指向堆數(shù)據(jù)的指針。

struct Ocean {
    animals: Vec<Box<dyn NoiseMaker>>,
}
let ocean = Ocean {
        animals: vec![Box::new(ferris), Box::new(sarah)],
};

適用于:

  • 當(dāng)有一個(gè)在編譯時(shí)未知大小的類型,而又需要在確切大小的上下文中使用這個(gè)類型值的時(shí)候;(舉例子:在一個(gè)list環(huán)境下,存放數(shù)據(jù),但是每個(gè)元素的大小在編譯時(shí)又不確定);
  • 當(dāng)有大量數(shù)據(jù)并希望在確保數(shù)據(jù)不被拷貝的情況下轉(zhuǎn)移所有權(quán)的時(shí)候;
  • 當(dāng)希望擁有一個(gè)值并只關(guān)心它的類型是否實(shí)現(xiàn)了特定trait而不是其具體類型時(shí)。

Rc

引用計(jì)數(shù)指針,將數(shù)據(jù)從棧上移動(dòng)到堆。允許其他Rc指針不可變引用同一個(gè)數(shù)據(jù)。單線程。

let heap_pie = Rc::new(Pie);
let heap_pie2 = heap_pie.clone();

heap_pie2.eat();
heap_pie.eat();
// all reference count smart pointers are dropped now
// the heap data Pie finally deallocates

RefCell

一個(gè)智能指針容器??勺兣c不可變引用都可以,引用規(guī)則與之前一樣。單線程。

fn main() {
    // RefCell validates memory safety at runtime
    // notice: pie_cell is not mut!
    let pie_cell = RefCell::new(Pie{slices:8});
    {
        // but we can borrow mutable references!
        let mut mut_ref_pie = pie_cell.borrow_mut();
        mut_ref_pie.eat();
        mut_ref_pie.eat();
        // mut_ref_pie is dropped at end of scope
    }
    // now we can borrow immutably once our mutable reference drops
     let ref_pie = pie_cell.borrow();
     println!("{} slices left",ref_pie.slices);
}

內(nèi)部可變性

#[derive(Debug)]
enum List {
    Cons(Rc<RefCell<i32>>, Rc<List>),
    Nil,
}

可以擁有一個(gè)表面上不可變的List,但是通過RefCell<T>中提供內(nèi)部可變性方法來在需要時(shí)修改數(shù)據(jù)的方式。

弱引用

fn main() {
    let a = Rc::new(Cons(5, RefCell::new(Weak::new())));
  // 1, a strong count = 1, weak count = 0
    // 1, a tail = Some(RefCell { value: (Weak) })
    let b = Rc::new(Cons(10, RefCell::new(Weak::new())));
    if let Some(link) = b.tail() {
        *link.borrow_mut() = Rc::downgrade(&a);
    }
  // 2, a strong count = 1, weak count = 1
    // 2, b strong count = 1, weak count = 0
    // 2, b tail = Some(RefCell { value: (Weak) })
    if let Some(link) = a.tail() {
        *link.borrow_mut() = Rc::downgrade(&b);
    }
    // 3, a strong count = 1, weak count = 1
    // 3, b strong count = 1, weak count = 1
    // 3, a tail = Some(RefCell { value: (Weak) })
}

特點(diǎn):
(1)弱引用通過Rc::downgrade傳遞Rc實(shí)例的引用,調(diào)用Rc::downgrade會(huì)得到Weak<T>類型的智能指針,同時(shí)將weak_count加1(不是將strong_count加1)。
(2)區(qū)別在于 weak_count 無需計(jì)數(shù)為 0 就能使 Rc 實(shí)例被清理。只要strong_count為0就可以了。
(3)可以通過Rc::upgrade方法返回Option<Rc<T>>對象。

Mutex

智能指針容器,可變與不可變引用都可以??梢杂脕砭幣哦嗪薈PU線程任務(wù)。

內(nèi)部可變性

組合智能指針:Rc<Vec<Foo>>,Rc<RefCell<Foo>>, Arc<Mutex<Foo>>。

比較

RefCell<T>/Rc<T>Mutex<T>/Arc<T>的相似性

(1)Mutex<T>提供內(nèi)部可變性,類似于RefCell;

(2)RefCell<T>/Rc<T>是非線程安全的,而Mutex<T>/Arc<T>是線程安全的。

面向?qū)ο?/h1>

對象

結(jié)構(gòu)體、枚舉。

封裝

在Rust中,使用pub關(guān)鍵字來標(biāo)記模塊、類型、函數(shù)和方法是公有的,默認(rèn)情況下一切都是私有的。

繼承

Rust不支持繼承。但是Rust可以通過trait進(jìn)行行為共享。

trait對象

1、trait對象動(dòng)態(tài)分發(fā)
(1)對泛型類型使用trait bound編譯器進(jìn)行的方式是單態(tài)化處理,單態(tài)化的代碼進(jìn)行的是靜態(tài)分發(fā)(就是說編譯器在編譯的時(shí)候就知道調(diào)用了什么方法)。
(2)使用 trait 對象時(shí),Rust 必須使用動(dòng)態(tài)分發(fā)。編譯器無法知曉所有可能用于 trait 對象代碼的類型,所以它也不知道應(yīng)該調(diào)用哪個(gè)類型的哪個(gè)方法實(shí)現(xiàn)。為此,Rust 在運(yùn)行時(shí)使用 trait 對象中的指針來知曉需要調(diào)用哪個(gè)方法。

2、trait對象要求對象安全
只有 對象安全(object safe)的 trait 才可以組成 trait 對象。trait的方法滿足以下兩條要求才是對象安全的:

  • 返回值類型不為 Self(例如Clone不能作為對象安全的trait對象)
  • 方法沒有任何泛型類型參數(shù)

高級特性

類型別名

類型別名的主要用途是減少重復(fù)。

type Result<T> = std::result::Result<T, std::io::Error>;//result<T, E> 中 E 放入了 std::io::Error
pub trait Write { 
    fn write(&mut self, buf: &[u8]) -> Result<usize>; 
    fn flush(&mut self) -> Result<()>; 
}

從不返回的never type

Rust 有一個(gè)叫做 ! 的特殊類型。在類型理論術(shù)語中,它被稱為 empty type,因?yàn)樗鼪]有值。我們更傾向于稱之為 never type。在函數(shù)不返回的時(shí)候充當(dāng)返回值。

loop { 
        let mut guess = String::new(); 
        io::stdin().read_line(&mut guess) .expect("Failed to read line"); 
        let guess: u32 = match guess.trim().parse() { 
            Ok(num) => num, 
            Err(_) => continue, //continue 的值是 !。
            //當(dāng) Rust 要計(jì)算 guess 的類型時(shí),它查看這兩個(gè)分支。
            //前者是 u32 值,而后者是 ! 值。
            //因?yàn)?! 并沒有一個(gè)值,Rust 決定 guess 的類型是 u32
            };         

        println!("You guessed: {}", guess); 
    } 

說明:never type 可以強(qiáng)轉(zhuǎn)為任何其他類型。允許 match 的分支以 continue 結(jié)束是因?yàn)?continue 并不真正返回一個(gè)值;相反它把控制權(quán)交回上層循環(huán),所以在 Err 的情況,事實(shí)上并未對 guess 賦值。

動(dòng)態(tài)類型

動(dòng)態(tài)大小類型(dynamically sized types),有時(shí)被稱為 “DST” 或 “unsized types”,這些類型允許我們處理只有在運(yùn)行時(shí)才知道大小的類型。

str

// 錯(cuò)誤代碼
// let s1: str = "Hello there!"; 
// let s2: str = "How's it going?";
// 正確代碼為:
let s1: &str = "Hello there!"; 
let s2: &str = "How's it going?";

&str 則是 兩個(gè) 值:str 的地址和其長度。這樣,&str 就有了一個(gè)在編譯時(shí)可以知道的大?。核?usize 長度的兩倍。也就是說,無論字符串是多大,&str的大小我們總是知道的。
因此,引出動(dòng)態(tài)大小類型的黃金規(guī)則:必須將動(dòng)態(tài)大小類型的值置于某種指針之后。如:Box 或 Rc、&str等。

trait

每一個(gè) trait 都是一個(gè)可以通過 trait 名稱來引用的動(dòng)態(tài)大小類型。為了將 trait 用于 trait 對象,必須將他們放入指針之后,比如 &Trait 或 Box(Rc 也可以)。

Sized trait

為了處理 DST,Rust 用Sized trait 來決定一個(gè)類型的大小是否在編譯時(shí)可知。這個(gè) trait 自動(dòng)為編譯器在編譯時(shí)就知道大小的類型實(shí)現(xiàn)。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請結(jié)合常識與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容