柚子快報激活碼778899分享:開發(fā)語言 后端 rust基礎(chǔ)
柚子快報激活碼778899分享:開發(fā)語言 后端 rust基礎(chǔ)
RUST編程基礎(chǔ)
編程概念rust編程概念變量可變性變量遮蔽語句和表達式所有權(quán)生命周期Self和self錯誤處理注釋變量聲明順序問題
數(shù)據(jù)類型基本類型整數(shù)類型序列字符/布爾/單元類型
其他類型字符串和切片字符串切片
結(jié)構(gòu)體枚舉類型數(shù)組元組引用函數(shù)方法泛型和特征泛型特征特征對象
復(fù)合類型動態(tài)數(shù)組映射
類型轉(zhuǎn)換
全局變量編譯期初始化運行期初始化
打印問題流程控制模式匹配matchif let模式解構(gòu)式賦值
項目構(gòu)建模塊cargo
高級用法函數(shù)式編程閉包 迭代器IntoIterator特征Iterator特征消費者適配器迭代器適配器
智能指針 Box\
多線程rust中使用多線程線程消息傳遞線程同步Send和Sync特征
Unsafe Rust裸指針
宏編程聲明式宏過程宏
異步編程async/awaitFutruePin 同時運行多個Future
其他
編程概念
本節(jié)作為一個先驅(qū)章節(jié),主要是對rust編程中一些基本概念的解釋,或是rust語言中獨有名詞的解釋。
rust編程概念
變量可變性
在rust中,變量默認(rèn)是不可變的。
變量遮蔽
//以下行為是被運行的
let x = 10;
let x = "hello"; //對上一個x進行了遮蔽,是一個全新的變量(重新內(nèi)存分配)
語句和表達式
語句沒有返回值。
表達式有返回值。注意表達式后面不能跟分號’‘;’'。表達式如果不返回任何值,會隱式的返回一個單元類型()。
在rust中函數(shù)就是表達式。
所有權(quán)
在rust中,任何內(nèi)存對象都是有主人的,而且一般情況下完全屬于它的主人,綁定就是把這個對象綁定給一個變量,讓這個變量成為它的主人。
//變量的綁定(賦值)
let a;
a = 10; //給變量a綁定內(nèi)存對象(內(nèi)存對象中的值為10)
所有權(quán)原則:
Rust 中每一個值(這里的值可以理解為內(nèi)存對象)都被一個變量所擁有,該變量被稱為值的所有者一個值同時只能被一個變量所擁有,或者說一個值只能擁有一個所有者當(dāng)所有者(變量)離開作用域范圍時,這個值將被丟棄(drop)
所有權(quán)主要涉及堆區(qū)的淺復(fù)制問題,例如,
let s1 = String::from("hello");
let s2 = s1; //s2獲取了s1的所有權(quán),s1就會失效,因為rust這里是采用淺復(fù)制,存儲在堆區(qū)的hello的指針被賦予了s2,為了避免二次釋放問題(s1、s2都會釋放一次),rust認(rèn)為s1在這里會馬上失效。
在**棧區(qū)的變量(例如,內(nèi)置類型)**就沒有這個問題,可以參考c++的深復(fù)制和淺復(fù)制問題。
Rust 永遠也不會自動創(chuàng)建數(shù)據(jù)的 “深拷貝”。因此,任何自動的復(fù)制都不是深拷貝,如果我們確實需要深度復(fù)制 String 中堆上的數(shù)據(jù),而不僅僅是棧上的數(shù)據(jù),可以使用一個叫做 clone 的方法。如下:
let s1 = String::from("hello");
let s2 = s1.clone();
println!("s1 = {}, s2 = {}", s1, s2);
copy特征:如果一個類型有copy特征,一個舊的變量在被賦值給其他變量后仍然可用。賦值時根據(jù)Copy 性質(zhì)來進行直接復(fù)制或做所有權(quán)轉(zhuǎn)移(沒有實現(xiàn)copy時)。
這里給出一個可以copy通用的規(guī)則,任何基本類型的組合可以 Copy ,不需要分配內(nèi)存或某種形式資源的類型是可以 Copy 的,例如:
所有整數(shù)類型,比如u32所有整數(shù)類型,比如u32布爾類型bool,它的值是true 和false所有浮點數(shù)類型,比如f64字符類型char元組,當(dāng)且僅當(dāng)其包含的類型也都是Copy的時候。比如,(i32, i32) 是Copy的,但(i32, String)就不是不可變引用&T ,例如轉(zhuǎn)移所有權(quán)中的最后一個例子,但是注意: 可變引用 &mut T是不可以 Copy的
生命周期
生命周期標(biāo)注并不會改變?nèi)魏我玫膶嶋H作用域,標(biāo)記的生命周期只是為了取悅編譯器,讓編譯器不要難為我們。生命周期標(biāo)注方式,如下:
//函數(shù)中標(biāo)注生命周期,x、y 和返回值至少活得和 'a 一樣久
fn useless<'a>(first: &'a i32, second: &'a i32)-> &'a str {}
//結(jié)構(gòu)體中標(biāo)注生命周期,字段part至少活的和結(jié)構(gòu)體一樣久
struct ImportantExcerpt<'a> {
part: &'a str,
}
//結(jié)構(gòu)體方法中的生命周期標(biāo)注規(guī)則,'a: 'b,是生命周期約束語法,跟泛型約束非常相似,用于說明 'a 必須比 'b 活得久
impl<'a: 'b, 'b> ImportantExcerpt<'a> {
fn announce_and_return_part(&'a self, announcement: &'b str) -> &'b str {
return announcement;
}
}
生命周期消除法則:
每一個引用參數(shù)都會獲得獨自的生命周期。 例如一個引用參數(shù)的函數(shù)就有一個生命周期標(biāo)注: fn foo<'a>(x: &'a i32),兩個引用參數(shù)的有兩個生命周期標(biāo)注:fn foo<'a, 'b>(x: &'a i32, y: &'b i32), 依此類推。 若只有一個輸入生命周期(函數(shù)參數(shù)中只有一個引用類型),那么該生命周期會被賦給所有的輸出生命周期,也就是所有返回值的生命周期都等于該輸入生命周期。 例如函數(shù) fn foo(x: &i32) -> &i32,x 參數(shù)的生命周期會被自動賦給返回值 &i32,因此該函數(shù)等同于 fn foo<'a>(x: &'a i32) -> &'a i32。 若存在多個輸入生命周期,且其中一個是 &self 或 &mut self,則 &self 的生命周期被賦給所有的輸出生命周期**。** 擁有 &self 形式的參數(shù),說明該函數(shù)是一個 方法,該規(guī)則讓方法的使用便利度大幅提升。
在 Rust 中有一個非常特殊的生命周期,那就是 'static,擁有該生命周期的引用可以和整個程序活得一樣久。字符串字面量,它是被硬編碼進 Rust 的二進制文件中,因此這些字符串變量全部具有 'static 的生命周期。
let s: &'static str = "我沒啥優(yōu)點,就是活得久,嘿嘿";
Self和self
在 Rust 中,有兩個self,一個指代當(dāng)前的實例對象(self),一個指代特征或者方法類型的別名(Self)。
錯誤處理
1.panic
用于不可恢復(fù)錯誤時,當(dāng)使用panic,程序會退出(注意,如果在非main線程中使用panic時,只會讓該線程退出)。panic一般配合unwrap 和 expect使用,例如:
use std::fs::File;
fn main() {
let f = File::open("hello.txt").unwrap();//成功時返回OK(T),失敗時直接panic
//let f = File::open("hello.txt").expect("Failed to open hello.txt");//expect能帶上錯誤信息
}
2.Result
用于可恢復(fù)錯誤時,例如:
use std::fs::File;
fn main() {
let f = File::open("hello.txt");
let f = match f {
Ok(file) => file,
Err(error) => {
panic!("Problem opening the file: {:?}", error)
},
};
}
3.符號?
use std::fs::File;
use std::io;
use std::io::Read;
fn read_username_from_file() -> Result
let mut f = File::open("hello.txt")?;//打開成功,返回句柄給f,失敗,函數(shù)直接返回
let mut s = String::new();
f.read_to_string(&mut s)?;
Ok(s)
}
4.錯誤處理組合器
or()和and():
or(),表達式按照順序求值,若任何一個表達式的結(jié)果是 Some 或 Ok,則該值會立刻返回。and(),若兩個表達式的結(jié)果都是 Some 或 Ok,則第二個表達式中的值被返回。若任何一個的結(jié)果是 None 或 Err ,則立刻返回。 or_else()和and_then(),與1類似,唯一的區(qū)別在于,它們的第二個表達式是一個閉包。 filter,對Option結(jié)果過濾: fn main() {
let s1 = Some(3);
let s2 = Some(6);
let n = None;
let fn_is_even = |x: &i8| x % 2 == 0;
assert_eq!(s1.filter(fn_is_even), n); // Some(3) -> 3 is not even -> None
assert_eq!(s2.filter(fn_is_even), s2); // Some(6) -> 6 is even -> Some(6)
assert_eq!(n.filter(fn_is_even), n); // None -> no value -> None
}
map() 和 map_err(),map 可以將 Some 或 Ok 中的值映射為另一個;map_err可以將Err中的值映射成另一個。 map_or() 和 map_or_else(),map_or 在 map 的基礎(chǔ)上提供了一個默認(rèn)值;map_or_else 與 map_or 類似,但是它是通過一個閉包來提供默認(rèn)值。 ok_or() and ok_or_else(),將 Option 類型轉(zhuǎn)換為 Result 類型;ok_or 接收一個默認(rèn)的 Err 參數(shù);ok_or_else 接收一個閉包作為 Err 參數(shù)。
5.自定義錯誤類型
為了更好的定義錯誤,Rust 在標(biāo)準(zhǔn)庫中提供了一些可復(fù)用的特征,例如 std::error::Error 特征:
use std::fmt::{Debug, Display};
pub trait Error: Debug + Display {
fn source(&self) -> Option<&(Error + 'static)> { ... }
}
std::convert::From 特征可以將其它的錯誤類型轉(zhuǎn)換成自定義的錯誤類型:
pub trait From
fn from(_: T) -> Self;
}
6.將錯誤類型歸一化的方式
使用特征對象 Box
注釋
//行注釋
/*塊注釋*/
///文檔注釋
/**文檔塊注釋*/
//!包、模塊行注釋,這些注釋要添加到包、模塊的最上方!
/*!包、模塊行注釋*/
變量聲明順序問題
fn do1(c: String) {}:表示實參會將所有權(quán)傳遞給 cfn do2(c: &String) {}:表示實參的不可變引用(指針)傳遞給 c,實參需帶 & 聲明fn do3(c: &mut String) {}:表示實參可變引用(指針)傳遞給 c,實參需帶 let mut 聲明,且傳入需帶 &mutfn do4(mut c: String) {}:表示實參會將所有權(quán)傳遞給 c,且在函數(shù)體內(nèi) c 是可讀可寫的,實參無需 mut 聲明fn do5(mut c: &mut String) {}:表示實參可變引用指向的值傳遞給 c,且 c 在函數(shù)體內(nèi)部是可讀可寫的,實參需帶 let mut 聲明,且傳入需帶 &mut
一句話總結(jié):在函數(shù)參數(shù)中,冒號左邊的部分,如:mut c,這個 mut 是對?函數(shù)體內(nèi)部有效?;冒號右邊的部分,如:&mut String,這個 &mut 是針對外部實參傳入時的形式(聲明)說明。
fn main() {
let d1 = "str".to_string();
do1(d1);
let d2 = "str".to_string();
do2(&d2);
let mut d3 = "str".to_string();
do3(&mut d3);
let d4 = "str".to_string();
do4(d4);
let mut d5 = "str".to_string();
do5(&mut d5);
}
fn do1(c: String) {}
fn do2(c: &String) {}
fn do3(c: &mut String) {}
fn do4(mut c: String) {}
fn do5(mut c: &mut String) {}
//見圣經(jīng)閉包章節(jié)評論
數(shù)據(jù)類型
rust是一門靜態(tài)類型語言。意味著編譯器必須在編譯期間就知道我們所有變量的類型。
變量/常量聲明方式
let [mut] var_name [:type] [=xxx];
const var_name <:type> = xxx; //值得類型必須標(biāo)注
基本類型
整數(shù)類型
整數(shù)類型:
整型變量的字面表示:
整數(shù)溢出顯示處理函數(shù):
wrapping_xx、checked_xx、overflowing_xx、saturating_xx。
注意,在使用debug編譯時,如果出現(xiàn)整數(shù)溢出,則編譯會失敗。
Nan類型:
Nan,對于數(shù)學(xué)上未定義的結(jié)果,rust浮點數(shù)使用Nan來表示。需要注意的是,Nan無法進行比較,但是可以使用is_nan()函數(shù)來判斷數(shù)值是否是Nan。
序列
序列規(guī)則:
1到4,不包括5 :1…51到5,包括5:1…=5從0到4:…5從0到結(jié)束:0…從開始到結(jié)束:…
常用于循環(huán)遍歷:
for i in 1..5
{}
字符/布爾/單元類型
字符使用單引號’ '來表示。在rust中,字符使用Unicode編碼,大小為4個字節(jié)。
Rust 中的布爾類型有兩個可能的值:true 和 false。大小為1個字節(jié)。
單元類型就是(),例如,fn main函數(shù)的返回值就是單元類型。單元類型不占內(nèi)存。
其他類型
字符串和切片
字符串
字符串是由字符組成的連續(xù)集合,需要注意的是,Rust中的字符是 Unicode 類型,因此每個字符占據(jù) 4 個字節(jié)內(nèi)存空間,但是在字符串中不一樣,字符串是 UTF-8 編碼,也就是字符串中的字符所占的字節(jié)數(shù)是變化的(1 - 4)。
let s = String::from("hello world");//基礎(chǔ)庫中常用的字符串為String
let s1:&str = "hello world"; //字符串字面量的類型為&str
//兩者轉(zhuǎn)換
let s2:&str = &s;
let s3:&str = &s[..];
let s4:String = "hello,world".to_string()
字符串的底層的數(shù)據(jù)存儲格式實際上是[ u8 ]。因為字符串中每個字符所占的字節(jié)數(shù)不一致,所以沒有辦法使用整型索引(切片亦是如此,遇到含中文的字符串可能會崩潰)。
String是可變字符串,下面羅列下其添加、修改、刪除時用到的方法。
//追加
let mut s = String::from("Hello ");
s.push_str("rust");
s.push('!');
//插入
let mut s = String::from("Hello rust!");
s.insert(5, ',');//參數(shù)1為插入索引
s.insert_str(6, " I like");
//替換
//replace,該方法是返回一個新的字符串,而不是操作原來的字符串。
let string_replace = String::from("I like rust. Learning rust is my favorite!");
let new_string_replace = string_replace.replace("rust" /*old*/, "RUST" /*new*/);
//replacen,該方法是返回一個新的字符串,而不是操作原來的字符串。
let string_replace = "I like rust. Learning rust is my favorite!";
let new_string_replacen = string_replace.replacen("rust", "RUST", 1 /*替換個數(shù)*/);
//replace_range,該方法是直接操作原來的字符串,不會返回新的字符串
let mut string_replace_range = String::from("I like rust!");
string_replace_range.replace_range(7..8 /*replace range*/, "R" /*new*/);
//刪除
//pop,該方法是直接操作原來的字符串。但是存在返回值,其返回值是一個 Option 類型
let mut string_pop = String::from("rust pop 中文!");
let p1 = string_pop.pop();//p1 Some('!')
let p2 = string_pop.pop();//p2 Some('文'),此時string_pop為"rust pop 中"
//remove,該方法是直接操作原來的字符串,刪除并返回字符串中指定位置的字符,按照字節(jié)來處理字符串的
let mut string_remove = String::from("測試remove方法");
string_remove.remove(0);// 刪除第一個漢字
string_remove.remove(1);//代碼會發(fā)生錯誤 Error
string_remove.remove(3);// 直接刪除第二個漢字
//truncate,該方法是直接操作原來的字符串,刪除字符串中從指定位置開始到結(jié)尾的全部字符,按照字節(jié)來處理字符串的
let mut string_truncate = String::from("測試truncate");
string_truncate.truncate(3);
//clear,該方法是直接操作原來的字符串,清空字符串
let mut string_clear = String::from("string clear");
string_clear.clear();
//連接
//使用 + 或者 += 連接字符串,要求右邊的參數(shù)必須為字符串的切片引用(Slice)類型,+ 是返回一個新的字符串
let string_append = String::from("hello ");
let string_rust = String::from("rust");
let result = string_append + &string_rust;// &string_rust會自動解引用為&str;并且此時string_append 的所有權(quán)會被轉(zhuǎn)移到函數(shù)的參數(shù)1上,后續(xù)無法使用該變量
let mut result = result + "!"; // ' result + "!" '中的 'result' 是不可變的
result += "!!!";
//add函數(shù),上面的+號就相當(dāng)于調(diào)用了add函數(shù),參考c++函數(shù)符重載
fn add(self, s: &str) -> String
//format!
let s1 = "hello";
let s2 = String::from("rust");
let s = format!("{} {}!", s1, s2);
遍歷字符串,如下。
//以字符的形式
for c in "中國人".chars(){}
//以字節(jié)的形式
for b in "中國人".bytes(){}
切片
切片允許你引用集合中部分連續(xù)的元素序列,而不是引用整個集合。切片的使用方式:
let s = String::from("hello world");
let s1 = &s[0..5];//[開始索引..終止索引),右半開區(qū)間,見序列規(guī)則。使用切片必須帶&引用
//注意對字符串切片時語法要格外小心,切片的索引必須落在字符邊界的位置,即UTF-8字符邊界,如下中文在utf-8中占用三個字節(jié),下面代碼會崩潰
let s = "中國人";
let s1 = &s[0..2];//只取前兩個字節(jié),但是每個漢字占用三個字節(jié)
//**在rust中因為對程序員屏蔽了內(nèi)存層,在使用時尤其注意深復(fù)制和淺復(fù)制問題,切片是使用的原字符串,可以認(rèn)為是一種淺復(fù)制**
//當(dāng)字符串被切片后,字符串的生命周期應(yīng)該大于切片的聲明周期
let s = Stirng::from("hello");
let s1 = &s[0..2];
s.clear();
println!("s1 : {}", s1); //error
字符串的切片的類型為&str。切片不是字符串獨有的,其他集合類型也有,例如數(shù)組。
結(jié)構(gòu)體
結(jié)構(gòu)體的定義、賦值和訪問:
struct User {
active: bool,
username: String,
sign_in_count: u64,
}
//賦值時,采用key:value的形式
let mut user1 = User {
username: String::from("someusername123"),
active: true,
sign_in_count: 1,
};
//需要注意的是,必須要將結(jié)構(gòu)體實例聲明為可變的,才能修改其中的字段,Rust不支持將某個結(jié)構(gòu)體某個字段標(biāo)記為可變。
user1.username = String::from("someusername456");
//使用user1給user2賦值,其中user1的username字段所有權(quán)會轉(zhuǎn)移到user2中
let user2 = User{
active:false,
..user1
};
元組結(jié)構(gòu)體,結(jié)構(gòu)體必須要有名稱,但是結(jié)構(gòu)體的字段可以沒有名稱,這種結(jié)構(gòu)體長得很像元組,因此被稱為元組結(jié)構(gòu)體,如下:
struct Color(i32, i32, i32);
struct Point(i32, i32, i32);
let black = Color(0, 0, 0);
let origin = Point(0, 0, 0);
單元結(jié)構(gòu)體,沒有任何字段和屬性,如果你定義一個類型,但是不關(guān)心該類型的內(nèi)容, 只關(guān)心它的行為時,就可以使用單元結(jié)構(gòu)體,如下:
struct AlwaysEqual;
impl SomeTrait for AlwaysEqual{}
在處理結(jié)構(gòu)體所有權(quán)時需要注意,如果想在結(jié)構(gòu)體中使用一個引用,就必須加上生命周期,否則就會報錯。
枚舉類型
枚舉(enum)允許你通過列舉可能的成員來定義一個枚舉類型,例如撲克牌花色:
//與c枚舉最大的不同,每種枚舉類型都可以定義自己的數(shù)據(jù)類型。使用過程中只要牢記,不管樣子怎么變,變量都是兩大核心:類型和值
enum PokerCard {
Clubs(u8),
Spades(u8),
Diamonds(char),
Hearts(char),
}
//訪問枚舉值
let heart = PokerSuit::Hearts(10);
枚舉類型是一個類型,它會包含所有可能的枚舉成員(如上述牌花色); 而枚舉值是該類型中的具體某個成員的實例(如方塊花色10)。
rust中常用的枚舉:
Option枚舉用于處理空值 enum Option
Some(T),
None,
}
enum Result
{
Ok(T),
Err(E),
}
數(shù)組
Rust 中,最常用的數(shù)組有兩種,第一種是速度很快但是長度固定的 array,第二種是可動態(tài)增長的但是有性能損耗的 Vector。稱 array 為數(shù)組,Vector 為動態(tài)數(shù)組。
數(shù)組的使用:
//定義數(shù)組
let a = [1, 2, 3, 4, 5];
let a:[i32;5] = [1,2,3,4,5];
let a = [3;5]//五個三的數(shù)組
//訪問數(shù)組元素
//索引訪問(如果越界訪問會造成程序崩潰)
let first = a[0];
let second = a[1];
和字符串一樣,數(shù)組也可以使用切片(創(chuàng)建切片的代價非常小,因為切片只是底層數(shù)組的一個引用),如下:
let a: [i32; 5] = [1, 2, 3, 4, 5];
let slice: &[i32] = &a[1..3];//&[i32]為數(shù)組切片類型(切片可以認(rèn)為是數(shù)組的引用)
需要注意的是,[u8;3]和[u8;4]是兩種不同的類型,數(shù)組的長度也是類型的一部分。
元組
元組是由多種類型組合到一起形成的,因此它是復(fù)合類型,元組的長度是固定的,元組中元素的順序也是固定的。如下:
let tup: (i32, f64, u8) = (500, 6.4, 1);
let tup = (500, 6.4, 1);
//用模式匹配來解構(gòu)元組
let (x, y, z) = tup;
//用點來訪問元組,元組索引從0開始
let x = tup.0;//500
let y = tup.1;//6.4
元組有一個特別常用的場景,在函數(shù)的返回值可以使用一個元組返回多個值。
引用
引用的使用方式:
let x = 5;
let y : &int = &x; //引用默認(rèn)也是不可變的,不可變引用可以同時存在多個
//聲明可變引用(★重點:為了避免數(shù)據(jù)競爭,同一個作用域,只能同時存在一個可變引用,與不可變引用也不能同時存在;即同一作用域如果存在一個可變引用,就不能再存在其他引用)
let z = &mut x;
//y是一個引用類型,可以對y解引用
assert_eq!(5,*y);
//直接用y和5做比較是錯誤的,類型不匹配
if y == 5 {} //complier error
值得注意的是,引用的作用域和變量不同:引用的作用域從創(chuàng)建開始,一直持續(xù)到它最后一次使用的地方;變量的作用域持續(xù)到某個花括號結(jié)束。
函數(shù)
1.函數(shù)聲明
fn NAME (VAR:type)->type {}
//ep:
fn add (var1:i32, var2:i32)->i32
{
if var1 > var2 { return var1 } //return提前返回
var1+var2
}
注意:函數(shù)的每個參數(shù)都需要標(biāo)注類型。rust中函數(shù)即是表達式。
2.函數(shù)的返回值
如果沒有顯示強調(diào)函數(shù)的返回值,函數(shù)默認(rèn)返回空元組(),以下兩種情況都會返回():
函數(shù)沒有返回值函數(shù)通過’;'結(jié)尾
當(dāng)使用!作為函數(shù)返回類型的時候,表示該函數(shù)永不返回。
方法
Rust 的方法可以跟結(jié)構(gòu)體、枚舉、特征(Trait)一起使用。Rust 使用 impl 來定義方法,例如以下代碼:
struct Rect {
x:i64,
y:i64,
};
//給結(jié)構(gòu)體Circle實現(xiàn)方法
impl Rectangle{
// new是Circle的關(guān)聯(lián)函數(shù),這種方法往往用于初始化當(dāng)前結(jié)構(gòu)體的實例,使用Rect::new(x,y)的形式調(diào)用
fn new(x:i64, y:i64)->Circle{
Rect {x:x, y:y}
}
//&self其實是self: &Self的簡寫,在一個 impl塊內(nèi),Self指代被實現(xiàn)方法的結(jié)構(gòu)體類型,self 指代此類型的實例
fn area(&self) -> u32 {
self.x * self.y
}
}
}
需要注意的是,self 依然有所有權(quán)的概念:
self 表示 Rectangle 的所有權(quán)轉(zhuǎn)移到該方法中,這種形式用的較少&self 表示該方法對 Rectangle 的不可變借用&mut self 表示可變借用
定義在 impl 中且沒有 self 的函數(shù)被稱之為關(guān)聯(lián)函數(shù),:: 語法用于訪問關(guān)聯(lián)函數(shù)和模塊創(chuàng)建的命名空間。
泛型和特征
泛型
類似于c++語言的中的模板,主要目的是為程序員提供編程的便利,減少代碼的臃腫。其使用方式如下:
//使用泛型參數(shù),有一個先決條件,必需在使用前對其進行聲明,largest
fn largest
let mut largest = list[0];
for &item in list.iter() {
//這段代碼沒法編譯成功,因為不是所有類型都能使用>,所以需要用特征做限制,參考后續(xù)的特征
if item > largest {
largest = item;
}
}
largest
}
//結(jié)構(gòu)體使用泛型
struct Point
x:T,
y:E,
}
//枚舉中使用泛型
enum Option
Some(T),
None,
}
//方法中使用泛型
struct Point
x: T,
y: T,
}
impl
fn x(&self) -> &T {
&self.x
}
}
//針對具體的類型實現(xiàn)方法(參考c++模板具體化)
impl Point
fn distance_from_origin(&self) -> f32 {
(self.x.powi(2) + self.y.powi(2)).sqrt()
}
}
//const泛型
const N :usize; //表示const泛型N ,它基于的值類型是usize
fn display_array
println!("{:?}", arr);
}
fn main() {
let arr: [i32; 3] = [1, 2, 3];
display_array(arr);
let arr: [i32; 2] = [1, 2];
display_array(arr);
}
特征
特征(trait)定義了一組可以被共享的行為,只要實現(xiàn)了特征,你就能使用這組行為。特征跟接口的概念類似。其使用方式如下:
trait trait_name{
fn fn1()->();//特征只定義行為看起來是什么樣的,而不定義行為具體是怎么樣的。因此,只需要定義特征方法的簽名
}
//為類型實現(xiàn)具體的特征
impl trait_name for type{
fn fn1()->(){}
}
關(guān)于特征實現(xiàn)與定義的位置,有一條非常重要的原則(孤兒規(guī)則):如果你想要為類型 A 實現(xiàn)特征 T,那么 A 或者 T **至少有一個是在當(dāng)前作用域中定義的!**例如,無法為 String 類型實現(xiàn) Display 特征,他倆都是標(biāo)準(zhǔn)庫中定義的,和你沒有任何關(guān)系。
特征和接口最大的不同之處在于,特征可以為其方法定義默認(rèn)實現(xiàn)。
pub trait Summary {
fn summarize(&self) -> String {
String::from("(Read more...)")
}
}
特征的作用:做函數(shù)參數(shù)。
//你可以使用任何實現(xiàn)了 Summary 特征的類型作為該函數(shù)的參數(shù),同時在函數(shù)體內(nèi),還可以調(diào)用該特征的方法
fn notify(item:&impl Summary){
...
}
//特征做函數(shù)參數(shù)的另一種表示形式(和泛型很像,可以想象成約束性泛型),稱特征約束T:Summary
fn notiy
...
}
//多重約束,可以指定多個約束條件,例如除了讓參數(shù)實現(xiàn) Summary 特征外,還可以讓參數(shù)實現(xiàn) Display 特征以控制它的格式化輸出
fn notify(item:impl &(Summary + Display)){}
fn notify
//Where約束(語法糖)
fn notify
where T:Summary+Dispaly
{}
使用特征約束可以有條件的實現(xiàn)方法或者特征。
struct Pair
x: T,
y: T,
}
//只有 T 同時實現(xiàn)了 Display + PartialOrd 的 Pair
impl
fn cmp_display(&self)
{}
}
//為任何實現(xiàn)了 Display 特征的類型實現(xiàn)了 ToString 特征
impl
{}
函數(shù)中可以返回一個實現(xiàn)了某個特征的類型。
fn return_summary()->impl Summary{}//這種方式有一個特別大的限制,只能有一個具體的類型
通過derive派生特征。例如:
#[derive(Debug)]//一種特征派生語法,被 derive 標(biāo)記的對象會自動實現(xiàn)對應(yīng)的默認(rèn)特征代碼,繼承相應(yīng)的功能。
特征對象
特征對象是指實現(xiàn)了某個特征的實例,特征對象由dyn修飾,可以用使用引用或者Box
fn func(x:&dyn Summary){}
fn func(x:Box
//注意 dyn 不能單獨作為特征對象的定義,例如下面的代碼編譯器會報錯,原因是特征對象可以是任意實現(xiàn)了某個特征的類型,編譯器在編譯期不知道該類型的大小,不同的類型大小是不同的。
//而 &dyn 和 Box
fn func(x: dyn Summary) {}//報錯
使用特征約束和特征對象的區(qū)別:
struct Test
a:Vec
}
struct Test{
a:Vec
}
特征對象使用的是動態(tài)轉(zhuǎn)發(fā)(運行時才確定調(diào)用的是什么方法,關(guān)鍵字dyn修飾),而泛型使用的是靜態(tài)分發(fā)(編譯期完成處理)。
特征對象的限制(只有滿足如下要求時,特征才是安全的,才可以擁有特征對象):
方法的返回類型不能是 Self方法沒有任何泛型參數(shù)
關(guān)聯(lián)類型,是在特征定義的語句塊中,申明一個自定義類型,這樣就可以在特征的方法簽名中使用該類型:
trait Add
type Output;//關(guān)聯(lián)類型,在實現(xiàn)特征時使用type Output = ojbtype;為其賦值
fn add(self, rhs: RHS) -> Self::Output; //注意,這里和特征對象限制并不沖突,我需要知道的是Output的類型,而不是Self本身的類型
}
//如上所示,當(dāng)使用泛型類型參數(shù)時,可以為其指定一個默認(rèn)的具體類型
//泛型也可以實現(xiàn)關(guān)聯(lián)類型的效果,但是會使得程序更加復(fù)雜(實現(xiàn)特征時語法更加復(fù)雜)
trait Add
fn add(&self, right:T)->Option
}
特征定義中的特征約束,讓某個特征 A 能使用另一個特征 B 的功能(另一種形式的特征約束),這種情況下,不僅僅要為類型實現(xiàn)特征 A,還要為類型實現(xiàn)特征 B 才行,如下:
trait OutlinePrint: Display {//OutlinePrint: Display為特征中定義特征約束的語法
fn outPirnt(&self){}
}
完全限定語法:
復(fù)合類型
動態(tài)數(shù)組
動態(tài)數(shù)組類型用 Vec
創(chuàng)建和使用動態(tài)數(shù)組的方式,如下:
let v:Vec
//使用宏vec!創(chuàng)建數(shù)組,可以賦予初始化值
let mut v = vec![1,2,3];
//創(chuàng)建并指定容量
Vec::with_capacity(capacity);
//尾部添加元素
v.push(4);
//使用索引訪問數(shù)組
let var = &v[2];//訪問數(shù)據(jù)3
//使用get函數(shù)訪問數(shù)組
let var = v.get(2);//不同于索引的是,get返回的是Option<&T>類型
//同時借用多個數(shù)組問題(以下這段代碼無法編譯成功,因為同時存在可變引用和不可變引用)
let first = &v[0];
v.push(6);
println!("The first element is: {first}");
//迭代數(shù)組(使用mut標(biāo)志,迭代的同時可以修改數(shù)組值)
for i in &mut v{
...
}
映射
HashMap 也是 Rust 標(biāo)準(zhǔn)庫中提供的集合類型,類比C++中的map。主要做鍵值對映射存儲,提高查詢速率。
HashMap的創(chuàng)建和使用方式,如下:
//創(chuàng)建一個map
let mut map1 = HashMap::new();
//將key和value插入到map中
map1.insert("key", "value");//map1為HashMap<&str, &str>類型
//將元組數(shù)組轉(zhuǎn)為hashmap
let teams_list = vec![
("中國隊".to_string(), 100),
("美國隊".to_string(), 10),
];
let teams_map: HashMap<_,_> = teams_list.into_iter().collect();
//查詢map中的值
let score:Option<&i32> = teams_map.get("中國隊");
//查詢時的注意點:
//1.get 方法返回一個 Option<&i32> 類型:當(dāng)查詢不到時,會返回一個 None,查詢到時返回 Some(&i32)
//2.&i32 是對 HashMap 中值的借用,如果不使用借用,可能會發(fā)生所有權(quán)的轉(zhuǎn)移
//查詢值,若不存在則插入,若存在則不會插入;并返回value的值
let v = teams_map.entry("日本隊").or_insert(5);
//or_insert 返回了 &mut v 引用,因此可以通過該可變引用直接修改 map 中對應(yīng)的值
//使用 count 引用時,需要先進行解引用 *count,否則會出現(xiàn)類型不匹配
*v += 1;
//循環(huán)遍歷map
for(key, value) in &teams_list{
//key - value
}
HashMap 的所有權(quán)規(guī)則(下面2條是所有權(quán)核心規(guī)則)與其它 Rust 類型沒有區(qū)別:
若類型實現(xiàn) Copy 特征(基本類型都有這個特征),該類型會被復(fù)制進 HashMap,因此無所謂所有權(quán)。若沒實現(xiàn) Copy 特征,所有權(quán)將被轉(zhuǎn)移給 HashMap 中。
類型轉(zhuǎn)換
類型轉(zhuǎn)換必須是顯式的。Rust 永遠也不會偷偷的把16bit 整數(shù)轉(zhuǎn)換成 32bit 整數(shù)。
as轉(zhuǎn)換
fn main() {
let a = 3.1 as i8;
let b = 100_i8 as i32;
let c = 'a' as u8; // 將字符'a'轉(zhuǎn)換為整數(shù),97
if (a as i32) < b {}
println!("{},{},{}",a,b,c)
}
tryinto轉(zhuǎn)換(可以捕捉類型由大到小溢出錯誤)
fn main() {
let b: i16 = 1500;
let b_: u8 = match b.try_into() {
Ok(b1) => b1,
Err(e) => {
println!("{:?}", e.to_string());
0
}
};
}
注意:對于某種類型來說,例如i32,其i32、&i32、&mut i32、mut i32都表示不同的類型。
Rust 中常見的 DST(動態(tài)大?。?類型有: str、[T]、dyn Trait,它們都無法單獨被使用,必須要通過引用或者 Box 來間接使用 。
全局變量
編譯期初始化
靜態(tài)常量 //必須指明類型
const MAX_ID: usize = usize::MAX / 2;
fn main() {
println!("用戶ID允許的最大值是{}",MAX_ID);
}
靜態(tài)變量 static mut REQUEST_RECV: usize = 0;
fn main() {
unsafe { //Rust 要求必須使用unsafe語句塊才能訪問和修改static變量
REQUEST_RECV += 1;
assert_eq!(REQUEST_RECV, 1);
}
}
靜態(tài)變量和常量的區(qū)別
靜態(tài)變量不會被內(nèi)聯(lián),在整個程序中,靜態(tài)變量只有一個實例,所有的引用都會指向同一個地址。存儲在靜態(tài)變量中的值必須要實現(xiàn) Sync trait。
運行期初始化
靜態(tài)初始化有一個致命的問題:無法用函數(shù)進行靜態(tài)初始化,例如你如果想聲明一個全局的Mutex鎖:
static NAMES: Mutex
通過使用lazy_static包來解決這個問題,lazy_static是社區(qū)提供的非常強大的宏,用于懶初始化靜態(tài)變量,之前的靜態(tài)變量都是在編譯期初始化的,因此無法使用函數(shù)調(diào)用進行賦值,而lazy_static允許我們在運行期初始化靜態(tài)變量! use std::sync::Mutex;
use lazy_static::lazy_static;
lazy_static! {
//lazy_static宏,匹配的是static ref,所以定義的靜態(tài)變量都是不可變引用
static ref NAMES: Mutex
}
fn main() {
let mut v = NAMES.lock().unwrap();
v.push_str(", Myth");
println!("{}",v);
}
Box::leak,將一個變量從內(nèi)存中泄漏,然后將其變?yōu)椤痵tatic生命周期。
打印問題
常用的三個打印函數(shù)print!、println!、format!,如下:
print! 將格式化文本輸出到標(biāo)準(zhǔn)輸出,不帶換行符println! 同上,但是在行的末尾添加換行符format! 將格式化文本輸出到 String 字符串
let s = "hello";
println!("{}, world", s); //占位符{},會被后面的參數(shù)依次替換
let s1 = format!("{}, world", s);
print!("{}", s1);
print!("{}\n", "!");
Debug特征,對于數(shù)值、字符串、數(shù)組,可以直接使用 {:?}或{:#?}(更加優(yōu)雅)進行輸出。但是對于結(jié)構(gòu)體,需要派生Debug特征后,才能進行輸出。Display特征,實現(xiàn)了Display特征后,可以直接使用{ }進行輸出。
流程控制
條件選擇 if contion {}
else if contion {}
else {}
循環(huán) //for var in [&] [mut] list,rust for循環(huán)主要用作對各種數(shù)據(jù)集合的遍歷(注意,因為所有權(quán)問題,使用 for時我們往往使用集合的引用形式)。
for item in [&][mut] collection{}
//在Rust 中 _ 的含義是忽略該值或者類型的意思
for _ in 0..10 {}
//while循環(huán),根據(jù)循環(huán)條件來決定是否繼續(xù)循環(huán)
while condition {}
//loop循環(huán),簡單的無限循環(huán)
loop {}
//continue 同c語言
//break 區(qū)別c語言的地方是break可以帶返回值
break xxx;
模式匹配
match
在 Rust 中,模式匹配最常用的就是 match(類比c語言中的switch,不同的是match可以對模式匹配,配合枚舉類型使用,是match的核心所在,并且match可以有返回值) 和 if let。
match的使用,如下:
//語法
match target {
模式1 => {表達式1},
模式2| 模式3 => {表達式2},
_ =>{表達式3}, //其他
};
//個人理解,這里的模式匹配可以理解為類型匹配,通過對比是否符合枚舉類型來做匹配
//例子
enum Direction {
East,
West,
North{a:i16},
South(i16),
}
fn main() {
let dire = Direction::South;
match dire {
Direction::East => println!("East"),
Direction::North | Direction::West => {
println!("West or North");
},
_ => println!("West"),
};
let dirt_get_value = Direction::South(20);
match dirt_get_value{
Direction::South(val) => {
//匹配,并且獲取模式綁定的值。注意如果模式中使用的是結(jié)構(gòu)體匹配,那么獲取綁定值變量名需要和枚舉值中結(jié)構(gòu)體成員名相同,
//例如,枚舉類型中枚舉值為North{a:i16},那么模式為Direction::North{a}
println!("South and get value {}", val);
},
}
}
使用match時,需要注意以下幾個問題:
match 的匹配必須要窮舉出所有可能,因此這里用 _ 來代表未列出的所有可能性。match 的每一個分支都必須是一個表達式,且所有分支的表達式最終返回值的類型必須相同。X|Y,類似邏輯運算符 或,代表該分支可以匹配 X 也可以匹配 Y,只要滿足一個即可
if let
只有一個模式的值需要被處理時使用match會顯得很冗余,rust提供了if let的方式,如下:
if let mode = target {...}
模式
模式是 Rust 中的特殊語法,它用來匹配類型中的結(jié)構(gòu)和數(shù)據(jù),它往往和 match 表達式聯(lián)用,以實現(xiàn)強大的模式匹配能力。模式一般有以下的表達形式 :
字面值模式匹配
let x = 1;
match x {
1 => println!("one"),
2 => println!("two"),
_ => println!("anything"),
}
解構(gòu)的數(shù)組、枚舉、結(jié)構(gòu)體或者元組模式匹配
struct Point {
x: i32,
y: i32,
}
fn main() {
let p = Point { x: 0, y: 7 };
//結(jié)構(gòu)體解構(gòu)
let Point { x: a, y: b } = p;
assert_eq!(0, a);
assert_eq!(7, b);
//或者使用如下方式,變量名需要和結(jié)構(gòu)體字段一致
let Point { x, y } = p;
assert_eq!(0, x);
assert_eq!(7, y);
//match解構(gòu)
match p {
Point { x, y } => println!("On neither axis: ({}, {})", x, y),
//元組解構(gòu)
let ((feet, inches), Point {x, y}) = ((3, 10), Point { x: 3, y: -10 });
}
變量模式匹配
fn main() {
let x = Some(5);
match x {
Some(y) => println!("Matched, y = {:?}", y),
_ => println!("Default case"),
}
}
通配符模式匹配占位符模式匹配
//可以在一個模式內(nèi)部使用 _ 忽略部分值
let numbers = (2, 4, 8, 16, 32);
match numbers {
(first, _, third, _, fifth) => {
println!("Some numbers: {}, {}, {}", first, third, fifth)
},
}
//.. 模式會忽略模式中剩余的任何沒有顯式匹配的值部分
struct Point {
x: i32,
y: i32,
z: i32,
}
let origin = Point { x: 0, y: 0, z: 0 };
match origin {
Point { x, .. } => println!("x is {}", x),
}
模式匹配需要通過例子來幫助理解,有關(guān)模式的詳細介紹可以參考rust圣經(jīng)中的模式匹配章節(jié)。
解構(gòu)式賦值
在 Rust 1.59 版本后,可以在賦值語句的左值中使用元組、切片和結(jié)構(gòu)體模式。
struct Struct {
e: i32
}
fn main() {
let (a, b, c, d, e);
(a, b) = (1, 2); //第一次賦值(綁定),后面繼續(xù)對a、b賦值會報錯,因為其為非mut
// _ 代表匹配一個值,但是我們不關(guān)心具體的值是什么,因此沒有使用一個變量名而是使用了 _
[c, .., d, _] = [1, 2, 3, 4, 5];
Struct { e, .. } = Struct { e: 5 };
assert_eq!([1, 2, 1, 4, 5], [a, b, c, d, e]);
}
項目構(gòu)建
rust從高層次到低層次管理項目分別是:軟件包(packages)、單元包(crate)、模塊(module)
包(packages):Package 就是一個項目(項目包),因此它包含有獨立的 Cargo.toml 文件,以及因為功能性被組織在一起的一個或多個單元包。一個 Package 只能包含一個庫(library)類型的單元包,但是可以包含多個二進制可執(zhí)行類型的單元包。
單元包(crate):對于 Rust 而言,單元包是一個獨立的可編譯單元,它編譯后會生成一個可執(zhí)行文件或者一個庫。
模塊(Module):可以一個文件多個模塊,也可以一個文件一個模塊,模塊可以被認(rèn)為是真實項目中的代碼組織單元
注意,Package 是一個項目工程,而crate只是一個編譯單元;Package中包含一個或多個crate,且至少包含一個同名的crate。
記:
1.Package開始是頂級目錄,其他模塊中引用或者使用use引用時,使用絕對路徑時,從頂級目錄開始(以包名或’crate’開始)。
2.package中的src/main.rs 和 src/lib.rs 被稱為包根(crate root,單元包樹形結(jié)構(gòu)的根部)。
模塊
模塊使用mod關(guān)鍵字標(biāo)識,可以嵌套聲明,如下:
//pub 關(guān)鍵字可以控制模塊和模塊中指定項的可見性。
pub mod front_of_house {
pub mod hosting {
fn add_to_waitlist() {}
fn seat_at_table() {}
}
pub mod serving {
fn take_order() {}
fn serve_order() {}
fn take_payment() {}
}
}
在lib.rs中聲明如上三個模塊,則可形成如下的模塊樹形結(jié)構(gòu):
想要調(diào)用一個函數(shù),就需要知道它的路徑,在 Rust 中,這種路徑有兩種形式:
絕對路徑,從包根開始,路徑名以包名或者 crate 作為開頭相對路徑,從當(dāng)前模塊開始,以 self,super 或當(dāng)前模塊的標(biāo)識符作為開頭
每一層使用::分開,例如,crate::front_of_house::hosting::add_to_waitlist()。
super代表的是父模塊為開始的引用方式,非常類似于文件系統(tǒng)中的 .. 語法:…/a/a.txt。
self引用自身模塊中的項,相當(dāng)于.語法:./a.txt。
模塊需要注意的問題:
1.在rust中,子模塊可以訪問父模塊、父父模塊…的私有項,但是父模塊無法訪問子模塊的私有項。
2.同一層級的模塊可以相互訪問(例如,屬于同一個包根作用域下的兩個模塊可以相互訪問)。
3.模塊可以是同名的文件,或是同名的文件夾。
如果需要將文件夾作為一個模塊,我們需要進行顯示指定暴露哪些子模塊,有兩種方法::
在 子模塊目錄里創(chuàng)建一個 mod.rs,如果你使用的 rustc 版本 1.30 之前,這是唯一的方法。在 子模塊同級目錄里創(chuàng)建一個與模塊(目錄)同名的 rs 文件 front_of_house.rs,在新版本里,更建議使用這樣的命名方式來避免項目中存在大量同名的 mod.rs 文件。
而無論是上述哪個方式創(chuàng)建的文件,其內(nèi)容都是一樣的,在文件中定義需要暴露的子模塊。
pub mod son_mod_name; //類似于c中的include
將其他模塊引入到當(dāng)前程序中:
//通過mod引入,告訴編譯器去加載和編譯指定的模塊文件,并將其作為當(dāng)前模塊的子模塊,需要使用完整的路徑來訪問這些項。
mod front_of_house;
crate::front_of_house::hosting::add_to_waitlist();
//通過use引入模塊,可以引入模塊中具體的項
use crate::front_of_house::hosting;
hosting::add_to_waitlist();
//使用as來為引入的項取別名
use crate::front_of_house::hosting as othername;
othername::add_to_waitlist();
引入項再導(dǎo)出:引入項再導(dǎo)出的主要目的是在一個模塊中重新導(dǎo)出來自其他模塊的項,以便在使用該模塊的代碼中可以直接訪問這些項,而無需通過中間模塊的路徑來訪問。使用的關(guān)鍵字是
‘pub use’。
如何引入第三方依賴:
1.修改 Cargo.toml 文件,在 [dependencies] 區(qū)域添加一行:rand = "0.8.3"。。
2.如果用的是 VSCode 和 rust-analyzer 插件,該插件會自動拉取該庫,等它完成后,再進行下一步(VSCode 左下角有提示)。
3.通過using在代碼中使用第三方依賴。
限制可見性語法:
pub 意味著可見性無任何限制pub(crate) 表示在當(dāng)前包可見pub(self) 在當(dāng)前模塊可見pub(super) 在父模塊可見pub(in
cargo
Rust的項目主要分為兩種類型:bin和lib
//創(chuàng)建項目
cargo new project_name --bin
cargo new lib_name --lib
cargo.toml和cargo.lock是cargo的核心文件。
創(chuàng)建項目后,會生成一個用作項目配置的Cargo.toml文件。 Cargo.lock 文件是 cargo 工具根據(jù)同一項目的 toml 文件生成的項目依賴詳細清單,一般不用修改它。
在cargo.toml中,主要通過各種依賴段落來描述該項目的各種依賴項:
基于 Rust 官方倉庫 crates.io,通過版本說明來描述基于項目源代碼的 git 倉庫地址,通過 URL 來描述基于本地項目的絕對路徑或者相對路徑,通過類 Unix 模式的路徑來描述
//cargo.toml中描述依賴項
[dependencies]
rand = "0.3"
hammer = { version = "0.5.0"}
color = { git = "https://github.com/bjz/color-rs" }
geometry = { path = "crates/geometry" }
//編譯和運行項目
cargo bulid [--release]
cargo run [--release]
//快速檢查代碼的正確性
cargo check
高級用法
函數(shù)式編程
閉包
閉包是一種匿名函數(shù),它可以賦值給變量也可以作為參數(shù)傳遞給其它函數(shù),不同于函數(shù)的是,它允許捕獲調(diào)用者作用域中的值。并且在rust中每一個閉包實例都有獨屬于自己的類型,即使于兩個簽名一模一樣的閉包,它們的類型也是不同的。
閉包的聲明形式(閉包無需標(biāo)注參數(shù)和返回值的類型,可以自行推導(dǎo)):
|param1, param2,...|-> type {
語句1;
返回表達式
}
//函數(shù)
fn add_one_v1 (x: u32) -> u32 { x + 1 }
//閉包的幾種寫法
let add_one_v2 = |x: u32| -> u32 { x + 1 };
let add_one_v3 = |x| { x + 1 };
let add_one_v4 = |x| x + 1 ;
//move關(guān)鍵字強制閉包取得捕獲變量的所有權(quán)(閉包捕獲變量時,優(yōu)先使用copy,無法使用copy時,才會用所有權(quán)轉(zhuǎn)移)
let add_one_v3 = move|x| { x + 1 };
三種Fn特征,閉包捕獲變量有三種途徑,恰好對應(yīng)函數(shù)參數(shù)的三種傳入方式:轉(zhuǎn)移所有權(quán)、可變借用、不可變借用,因此相應(yīng)的 Fn 特征也有三種:
FnOnce,該類型的閉包會拿走被捕獲變量的所有權(quán)。FnMut,它以可變借用的方式捕獲了環(huán)境中的值。Fn 特征,它以不可變借用的方式捕獲環(huán)境中的值。
注意,一個閉包實現(xiàn)了哪種 Fn 特征取決于該閉包如何使用被捕獲的變量,而不是取決于閉包如何捕獲它們。
迭代器
IntoIterator特征
在rust中,為某個集合類型實現(xiàn)了IntoIterator特征后,就可以通過for語法糖自動把實現(xiàn)了該特征的集合類型轉(zhuǎn)換為迭代器。
let arr = [1, 2, 3]; //數(shù)組實現(xiàn)了IntoIterator特征
for v in arr { //自動轉(zhuǎn)換為迭代器進行訪問
println!("{}",v);
}
//IntoIterator特征原型
pub trait IntoIterator {
type Item;
type IntoIter: Iterator
fn into_iter(self) -> Self::IntoIter; //通過該方法,實現(xiàn)了該特征的集合類型可以返回對應(yīng)的迭代器
}
into_iter、iter和iter_mut方法,它們都可以顯式的把集合對象轉(zhuǎn)換成迭代器,例如:
let arr = [1, 2, 3];
for v in arr.into_iter() {
println!("{}", v);
}
這三者的區(qū)別是:
into_iter 會奪走所有權(quán)。iter 是借用,實現(xiàn)的迭代器調(diào)用next返回的類型是Some(&T)。iter_mut 是可變借用,實現(xiàn)的迭代器調(diào)用next返回的類型是Some(&mut T)。
Iterator特征
pub trait Iterator {
type Item;
fn next(&mut self) -> Option
//省略其余有默認(rèn)實現(xiàn)的方法...
}
在rust中,迭代器是惰性的,如果不使用它,將不會發(fā)生任何事:
let v1 = vec![1, 2, 3];
let v1_iter = v1.iter();
//在for循環(huán)之前,只是簡單的創(chuàng)建了一個迭代器 v1_iter,此時不會發(fā)生任何迭代行為
//惰性初始化的方式確保了創(chuàng)建迭代器不會有任何額外的性能損耗,其中的元素也不會被消耗
for val in v1_iter {
println!("{}", val);
}
消費者適配器
只要迭代器上的某個方法A在其內(nèi)部調(diào)用了next方法,那么A就被稱為消費性適配器:因為next方法會消耗掉迭代器上的元素,所以方法A的調(diào)用也會消耗掉迭代器上的元素。例如下面的sum函數(shù):
fn main() {
let v1 = vec![1, 2, 3];
let v1_iter = v1.iter();
let total: i32 = v1_iter.sum();
assert_eq!(total, 6);
}
迭代器適配器
消費者適配器是消費掉迭代器,然后返回一個值。而迭代器適配器,是返回一個新的迭代器,這是實現(xiàn)鏈?zhǔn)椒椒ㄕ{(diào)用的關(guān)鍵。與消費者適配器不同,迭代器適配器是惰性的,因此需要一個消費者適配器來收尾,最終將迭代器轉(zhuǎn)換成一個具體的值:
let v1: Vec
let v2: Vec<_> = v1.iter().map(|x| x + 1).collect();//map方法是一個迭代者適配器,它是惰性的,不產(chǎn)生任何行為,因此還需要一個消費者適配器collect進行收尾
assert_eq!(v2, vec![2, 3, 4]);
智能指針
Box
Box
相比其它語言,Rust 堆上對象還有一個特殊之處,它們都擁有一個所有者,因此受所有權(quán)規(guī)則的限制:當(dāng)賦值時,發(fā)生的是所有權(quán)的轉(zhuǎn)移(只需淺拷貝棧上的引用或智能指針即可),例如以下代碼:
fn main() {
let b = foo("world");
println!("{}", b);
}
fn foo(x: &str) -> String {
let a = "Hello, ".to_string() + x;
a //a的所有權(quán)返回給b
}
Box使用場景:
使用 Box
let a = Box::new(3);
println!("a = {}", a); // a = 3
// 下面一行代碼將報錯
// let b = a + 1; // cannot add `{integer}` to `Box<{integer}>`
//正確的寫法是 let b = *a + 1;
}
避免棧上數(shù)據(jù)的拷貝。 fn main() {
// 在棧上創(chuàng)建一個長度為1000的數(shù)組
let arr = [0;1000];
// 將arr所有權(quán)轉(zhuǎn)移arr1,由于 `arr` 分配在棧上,因此這里實際上是直接重新深拷貝了一份數(shù)據(jù)
let arr1 = arr;
// arr 和 arr1 都擁有各自的棧上數(shù)組,因此不會報錯
println!("{:?}", arr.len());
println!("{:?}", arr1.len());
// 在堆上創(chuàng)建一個長度為1000的數(shù)組,然后使用一個智能指針指向它
let arr = Box::new([0;1000]);
// 將堆上數(shù)組的所有權(quán)轉(zhuǎn)移給 arr1,由于數(shù)據(jù)在堆上,因此僅僅拷貝了智能指針的結(jié)構(gòu)體,底層數(shù)據(jù)并沒有被拷貝
// 所有權(quán)順利轉(zhuǎn)移給 arr1,arr 不再擁有所有權(quán)
let arr1 = arr;
println!("{:?}", arr1.len());
// 由于 arr 不再擁有底層數(shù)組的所有權(quán),因此下面代碼將報錯
// println!("{:?}", arr.len());
}
將動態(tài)大小類型變?yōu)?Sized 固定大小類型。 enum List {
Cons(i32, Box), //如果這里直接使用List會報錯,因為無法知道具體的大小
Nil,
}
特征對象。 trait Draw {
fn draw(&self);
}
impl Draw for Button {
fn draw(&self) {
println!("這是屏幕上第{}號按鈕", self.id)
}
}
struct Select {
id: u32,
}
impl Draw for Select {
fn draw(&self) {
println!("這個選擇框賊難用{}", self.id)
}
}
fn main() {
let elems: Vec
for e in elems {
e.draw()
}
}
Box::leak Box 中還提供了一個非常有用的關(guān)聯(lián)函數(shù):Box::leak,它可以消費掉 Box 并且強制目標(biāo)值從內(nèi)存中泄漏。例如,將一個String類型字符串的生命周期提升為’static。
Deref解引用
Deref特征可以讓智能指針像引用那樣工作,這樣就可以寫出同時支持智能指針和引用的代碼,例如*T(參考運算符重載)。還可以連續(xù)的實現(xiàn)如Box
Deref解引用規(guī)則:
一個類型為T的對象foo,如果 T: Deref
三種Deref的轉(zhuǎn)換:
當(dāng)T: Deref
Drop釋放資源
在rust中當(dāng)一個變量超過作用域范圍后,就會通過Drop特征來回收資源。
Drop的一些注意點:
Drop特征中的drop方法借用了目標(biāo)的可變引用,而不是拿走了所有權(quán)。pub trait Drop {
fn drop(&mut self);
}
結(jié)構(gòu)體中每個字段都有自己的Drop。
Drop的順序:
變量級別,按照逆序的方式。結(jié)構(gòu)體內(nèi)部,按照順序的方式。
注意,無法為一個類型同時實現(xiàn) Copy 和 Drop 特征。因為實現(xiàn)了 Copy 的特征會被編譯器隱式的復(fù)制,導(dǎo)致非常難以預(yù)測析構(gòu)函數(shù)執(zhí)行的時間和頻率。因此這些實現(xiàn)了 Copy 的類型無法擁有析構(gòu)函數(shù)。
Rc與Arc引用計數(shù)
rust通過引用計數(shù)的方式,允許一個數(shù)據(jù)資源在同一時刻擁有多個所有者。這種實現(xiàn)機制就是 Rc 和 Arc,前者適用于單線程,后者適用于多線程(原子化實現(xiàn)的引用計數(shù),因此是線程安全的)。這兩者都是只讀的,如果想要實現(xiàn)內(nèi)部數(shù)據(jù)可修改,必須配合內(nèi)部可變性 RefCell 或者互斥鎖 Mutex 來一起使用。
當(dāng)我們希望在堆上分配一個對象供程序的多個部分使用且無法確定哪個部分最后一個結(jié)束時,就可以使用 Rc 成為數(shù)據(jù)值的所有者,例如:
use std::rc::Rc;
fn main() {
let a = Rc::new(String::from("hello, world"));
let b = Rc::clone(&a);//僅僅復(fù)制了智能指針并增加了引用計數(shù),并沒有克隆底層數(shù)據(jù)
assert_eq!(2, Rc::strong_count(&a));
assert_eq!(Rc::strong_count(&a), Rc::strong_count(&b))
}
Rc
Weak弱引用
可訪問,但沒有所有權(quán),不增加引用計數(shù),因此不會影響被引用值的釋放回收。可由 Rc
Cell與RefCell
Rust 提供了 Cell 和 RefCell 用于內(nèi)部可變性,簡而言之,可以在擁有不可變引用的同時修改目標(biāo)數(shù)據(jù),對于正常的代碼實現(xiàn)來說,這個是不可能做到的(要么一個可變借用,要么多個不可變借用)。
Cell 和 RefCell 在功能上沒有區(qū)別,區(qū)別在于 Cell
use std::cell::Cell;
fn main() {
let c = Cell::new("asdf"); //"asdf"是&str類型,實現(xiàn)了copy特征,如果這里是String就會報錯
let one = c.get(); //get取值
c.set("qwer");//set設(shè)置值
let two = c.get();
println!("{},{}", one, two);
}
RefCell可以用來解決可變引用和不可變引用共存的問題,但是沒有從根本上解決這種問題,只是將報錯從編譯期間推遲到了運行時,并從編譯錯誤變成了panic異常。 RefCell主要用于你確信代碼是正確的,而編譯器卻發(fā)生了誤判時。
Cell和ReCell比較:
與 Cell 用于可 Copy 的值不同,RefCell 用于可變引用RefCell 只是將借用規(guī)則從編譯期推遲到程序運行期,并不能繞過這個規(guī)則RefCell 適用于編譯期誤報或者一個引用被在多處代碼使用、修改以至于難于管理借用關(guān)系時使用 RefCell 時,違背借用規(guī)則會導(dǎo)致運行期的 panic
多線程
rust中使用多線程
使用 thread::spawn 可以創(chuàng)建線程:
use std::thread;
fn main() {
let v = vec![1, 2, 3];
let handle = thread::spawn(move|| { //線程中無法直接借用外部環(huán)境中的變量值,但可以使用move關(guān)鍵字將v的所有權(quán)轉(zhuǎn)移到線程中去
println!("Here's a vector: {:?}", v);
});
handle.join().unwrap();//等待子線程結(jié)束
}
線程屏障,使用Barrier讓多個線程都執(zhí)行到某個點后,才繼續(xù)一起往后執(zhí)行:
use std::sync::{Arc, Barrier};
use std::thread;
fn main() {
let mut handles = Vec::with_capacity(6);
let barrier = Arc::new(Barrier::new(6));
for _ in 0..6 {
let b = barrier.clone();
handles.push(thread::spawn(move|| {
println!("before wait");
b.wait();//6個線程都打印完before wait后才會一起往后執(zhí)行
println!("after wait");
}));
}
for handle in handles {
handle.join().unwrap();
}
}
線程局部變量:thread_local! 是一個宏(macro),用于創(chuàng)建線程局部(thread-local)變量。線程局部變量是一種特殊類型的變量,每個線程都有其自己的副本,線程之間的變量互相獨立。
線程中只能被調(diào)用一次的函數(shù):std::sync::Once中的call_once方法。
線程消息傳遞
消息通道,標(biāo)準(zhǔn)庫提供了通道std::sync::mpsc,該通道支持多個發(fā)送者,但是只支持唯一的接收者:
use std::sync::mpsc;
use std::thread;
fn main() {
// 創(chuàng)建一個消息通道, 返回一個元組:(發(fā)送者,接收者)
let (tx, rx) = mpsc::channel();
// 創(chuàng)建線程,并發(fā)送消息
thread::spawn(move || {
// 發(fā)送一個數(shù)字1, send方法返回Result
tx.send(1).unwrap();
// 下面代碼將報錯,因為編譯器自動推導(dǎo)出通道傳遞的值是i32類型,那么Option
// tx.send(Some(1)).unwrap()
});
// 在主線程中接收子線程發(fā)送的消息并輸出
println!("receive {}", rx.recv().unwrap());
}
使用通道來傳輸數(shù)據(jù),一樣要遵循 Rust 的所有權(quán)規(guī)則:
若值的類型實現(xiàn)了Copy特征,則直接復(fù)制一份該值,然后傳輸過去,例如之前的i32類型。若值沒有實現(xiàn)Copy,則它的所有權(quán)會被轉(zhuǎn)移給接收端,在發(fā)送端繼續(xù)使用該值將報錯。
mpsc::channel表示異步通道,發(fā)送數(shù)據(jù)時不會造成阻塞(緩存大小受內(nèi)存影響);
mpsc::sync_channel(num)表示同步通道,通過num設(shè)置緩存大小,發(fā)送方發(fā)送的數(shù)據(jù)超過設(shè)置的緩存值時會阻塞等待接收方消費數(shù)據(jù)。
線程同步
互斥鎖:
use std::sync::{Arc, Mutex};
use std::thread;
//展示mutex如何使用
fn main() {
// 使用Mutex結(jié)構(gòu)體的關(guān)聯(lián)函數(shù)創(chuàng)建新的互斥鎖實例
let counter = Arc::new(Mutex::new(0));
let mut handles = vec![];
for _ in 0..10 {
let counter = Arc::clone(&counter);
let handle = thread::spawn(move || {
// 獲取鎖,并返回被鎖數(shù)據(jù)的引用(deref特征)
// lock返回的是Result
let mut num = counter.lock().unwrap();
*num += 1;
// 鎖自動被drop
});
handles.push(handle);
}
for handle in handles {
handle.join().unwrap();
}
println!("Result: {}", *counter.lock().unwrap()); //10
}
內(nèi)部可變性總結(jié):Rc
條件變量:
use std::sync::{Arc,Mutex,Condvar};
use std::thread::{spawn,sleep};
use std::time::Duration;
fn main() {
let flag = Arc::new(Mutex::new(false));
let cond = Arc::new(Condvar::new());
let cflag = flag.clone();
let ccond = cond.clone();
let hdl = spawn(move || {
let mut lock = cflag.lock().unwrap();
let mut counter = 0;
while counter < 3 {
while !*lock {
// wait方法會接收一個MutexGuard<'a, T>,且它會自動地暫時釋放這個鎖,使其他線程可以拿到鎖并進行數(shù)據(jù)更新。
// 同時當(dāng)前線程在此處會被阻塞,直到被其他地方notify后,它會將原本的MutexGuard<'a, T>還給我們,即重新獲取到了鎖,同時喚醒了此線程。
lock = ccond.wait(lock).unwrap();
}
*lock = false;
counter += 1;
println!("inner counter: {}", counter);
}
});
let mut counter = 0;
loop {
sleep(Duration::from_millis(1000));
*flag.lock().unwrap() = true;
counter += 1;
if counter > 3 {
break;
}
println!("outside counter: {}", counter);
cond.notify_one();
}
hdl.join().unwrap();
println!("{:?}", flag);
}
信號量:
use tokio::sync::Semaphore;
use std::sync::Arc;
#[tokio::main]
async fn main() {
// 創(chuàng)建一個容量為 2 的信號量
let semaphore = Arc::new(Semaphore::new(2));
// 創(chuàng)建多個任務(wù)來獲取和釋放信號量
let tasks = (0..5).map(|i| {
let semaphore = semaphore.clone();
tokio::spawn(async move {
// 獲取信號量許可
let permit = semaphore.acquire().await.unwrap();
println!("Task {} acquired permit", i);
// 模擬任務(wù)執(zhí)行
tokio::time::sleep(tokio::time::Duration::from_secs(2)).await;
println!("Task {} releasing permit", i);
// 釋放信號量許可
drop(permit);
})
});
// 等待所有任務(wù)完成
for task in tasks {
task.await.unwrap();
}
}
Atomic原子類型
use std::sync::atomic::{AtomicBool, Ordering};
fn main() {
let flag = AtomicBool::new(true);
// 在多個線程中讀取和修改 AtomicBool 值
let thread1 = std::thread::spawn(move || {
println!("Thread 1: Flag = {}", flag.load(Ordering::Relaxed));
flag.store(false, Ordering::Relaxed);
});
let thread2 = std::thread::spawn(move || {
flag.store(false, Ordering::Relaxed);
println!("Thread 2: Flag = {}", flag.load(Ordering::Relaxed));
});
thread1.join().unwrap();
thread2.join().unwrap();
}
Send和Sync特征
Send和Sync是Rust安全并發(fā)的重中之重,但是它們只是標(biāo)記特征(marker trait,該特征未定義任何行為,僅告訴編譯器實現(xiàn)了該特征后可以在多線程中安全使用,但真正的安全需要編碼者自己把控), 其具體作用是:
實現(xiàn)Send的類型可以在線程間安全的傳遞其所有權(quán)。實現(xiàn)Sync的類型可以在線程間安全的共享(通過引用)。
注意:手動實現(xiàn) Send 和 Sync 是不安全的,通常并不需要手動實現(xiàn) Send 和 Sync trait,實現(xiàn)者需要使用unsafe小心維護并發(fā)安全保證。
Unsafe Rust
unsafe存在的主要原因是因為 Rust 的靜態(tài)檢查太強了。當(dāng)遇到一些編譯檢查是很難繞過的時候,最常用的方法之一就是使用unsafe和pin。使用 unsafe 非常簡單,只需要將對應(yīng)的代碼塊標(biāo)記下即可:
fn main() {
let mut num = 5;
let r1 = &num as *const i32;
unsafe {
println!("r1 is: {}", *r1);
}
}
unsafe 能賦予我們 5 種超能力,這些能力在安全的 Rust 代碼中是無法獲取的:
解引用裸指針,就如上例所示。調(diào)用一個 unsafe 或外部的函數(shù)。訪問或修改一個可變的靜態(tài)變量。實現(xiàn)一個 unsafe 特征。訪問 union 中的字段。
裸指針
裸指針(raw pointer,又稱原生指針,參考c指針) 在功能上跟引用類似,同時它也需要顯式地注明可變性。但是又和引用有所不同,裸指針的聲明: *const T 和 *mut T,它們分別代表了不可變和可變。
裸指針的創(chuàng)建和使用:
fn main() {
//基于引用創(chuàng)建裸指針
let a = 1;
let b: *const i32 = &a as *const i32;
let c: *const i32 = &a; //隱式轉(zhuǎn)換
unsafe {
println!("{}", *c);//*號解引用
}
//基于內(nèi)存地址創(chuàng)建裸指針
let address = 0x012345usize;
let r = address as *const i32; //不安全
let string = "Hello World!";
let pointer = string.as_ptr() as usize;
let length = string.len();
let (p,len) = from_utf8_unchecked(from_raw_parts(pointer as *const u8, length))
//基于智能指針創(chuàng)建裸指針
let a: Box
let b: *const i32 = &*a;
let c: *const i32 = Box::into_raw(a);//或者使用 into_raw 來創(chuàng)建
}
unsafe函數(shù):
unsafe fn dangerous() {}
fn main() {
unsafe {
dangerous();
}
}
實現(xiàn)unsafe特征:
pub unsafe trait Send {}//send特征
unsafe impl Send for XXX {} //為xxx實現(xiàn)Send特征
宏編程
在 Rust 中宏分為兩大類:聲明式宏( declarative macros ) macro_rules! 和下面三種過程宏( procedural macros ):
#[derive],在之前多次見到的派生宏,可以為目標(biāo)結(jié)構(gòu)體或枚舉派生指定的代碼,例如Debug特征。類屬性宏(Attribute-like macro),用于為目標(biāo)添加自定義的屬性。類函數(shù)宏(Function-like macro),看上去就像是函數(shù)調(diào)用。
聲明式宏
聲明式宏(macro_rules!)允許我們寫出類似 match 的代碼。match 表達式是一個控制結(jié)構(gòu),其接收一個表達式,然后將表達式的結(jié)果與多個模式進行匹配,一旦匹配了某個模式,則該模式相關(guān)聯(lián)的代碼將被執(zhí)行。而宏也是將一個值跟對應(yīng)的模式進行匹配,且該模式會與特定的代碼相關(guān)聯(lián)。但是與 match 不同的是,宏里的值是一段 Rust 源代碼(字面量),模式用于跟這段源代碼的結(jié)構(gòu)相比較,一旦匹配,傳入宏的那段源代碼將被模式關(guān)聯(lián)的代碼所替換,最終實現(xiàn)宏展開。
以動態(tài)數(shù)組Vector為例,一個簡化的實現(xiàn) vec!:
#[macro_export]
macro_rules! vec {
( $( $x:expr ),* ) => { //vec只有一個模式( $( $x:expr ),* )
{
let mut temp_vec = Vec::new();
$(
temp_vec.push($x);
)*
temp_vec
}
};
}
過程宏
從形式上來看,過程宏跟函數(shù)較為相像,但過程宏是使用源代碼作為輸入?yún)?shù),基于代碼進行一系列操作后,再輸出一段全新的代碼。注意,過程宏中的 derive 宏輸出的代碼并不會替換之前的代碼,這一點與聲明宏有很大的不同。
定義一個derive過程宏 //有一個特征 HelloMacro,使用過程宏來統(tǒng)一實現(xiàn)該特征,這樣只需要對類型進行標(biāo)記即可實現(xiàn)該特征:#[derive(HelloMacro)]
pub trait HelloMacro {
fn hello_macro();
}
//在特定的文件中定義過程宏
extern crate proc_macro;
use proc_macro::TokenStream;
use quote::quote;
use syn;
use syn::DeriveInput;
#[proc_macro_derive(HelloMacro)]
pub fn hello_macro_derive(input: TokenStream) -> TokenStream {
// 基于 input 構(gòu)建 AST 語法樹
let ast:DeriveInput = syn::parse(input).unwrap();
// 構(gòu)建特征實現(xiàn)代碼
let name = &ast.ident;
let gen = quote! {
impl HelloMacro for #name {
fn hello_macro() {
println!("Hello, Macro! My name is {}!", stringify!(#name));
}
}
};
gen.into()
}
//在項目中使用過程宏
use hello_macro::HelloMacro;
use hello_macro_derive::HelloMacro;
#[derive(HelloMacro)]
struct Sunfei;
#[derive(HelloMacro)]
struct Sunface;
fn main() {
Sunfei::hello_macro();
Sunface::hello_macro();
}
類屬性宏 類屬性過程宏跟 derive 宏類似,但是前者允許我們定義自己的屬性。除此之外,derive 只能用于結(jié)構(gòu)體和枚舉,而類屬性宏可以用于其它類型項,例如函數(shù)。 //假設(shè)我們在開發(fā)一個 web 框架,當(dāng)用戶通過 HTTP GET 請求訪問 / 根路徑時,使用 index 函數(shù)為其提供服務(wù):
#[route(GET, "/")]
fn index() {}
//這里的 #[route] 屬性就是一個過程宏,它的定義函數(shù)大概如下
#[proc_macro_attribute]
pub fn route(attr: TokenStream, item: TokenStream) -> TokenStream {}
//與derive宏不同,類屬性宏的定義函數(shù)有兩個參數(shù):
//1.第一個參數(shù)時用于說明屬性包含的內(nèi)容:Get, "/" 部分
//2.第二個是屬性所標(biāo)注的類型項,在這里是 fn index() {...},注意,函數(shù)體也被包含其中
類函數(shù)宏 類函數(shù)宏可以讓我們定義像函數(shù)那樣調(diào)用的宏,從這個角度來看,它跟聲明宏 macro_rules 較為類似。區(qū)別在于,macro_rules 的定義形式與 match 匹配非常相像,而類函數(shù)宏的定義形式則類似于之前講過的兩種過程宏: //定義
#[proc_macro]
pub fn sql(input: TokenStream) -> TokenStream {}
//調(diào)用
let sql = sql!(SELECT * FROM posts WHERE id=1);
//為何我們不使用聲明宏 macro_rules 來定義呢?原因是這里需要對 SQL 語句進行解析并檢查其正確性,這個復(fù)雜的過程是 macro_rules 難以對付的,而過程宏相比起來就會靈活的多。
異步編程
Rust 選擇的異步編程模型是async。它通過語言特性+標(biāo)準(zhǔn)庫+三方庫實現(xiàn),優(yōu)點是支持高并發(fā)、以及處理異步IO。與其他語言相比,rust的async有如下特點:
Future 在 Rust 中是惰性的,只有在被輪詢(poll)時才會運行, 因此丟棄一個Future會阻止它未來再被運行,可以將Future理解為一個在未來某個時間點被調(diào)度執(zhí)行的任務(wù)。Async 在 Rust 中使用開銷是零。Rust 沒有內(nèi)置異步調(diào)用所必需的運行時,需要借助Rust 社區(qū)生態(tài)提供的運行時實現(xiàn),例如tokio。
async/await
async/.await是 Rust 內(nèi)置的語言特性,可以讓我們用同步的方式去編寫異步的代碼。
通過 async 標(biāo)記的語法塊會被轉(zhuǎn)換成實現(xiàn)了Future特征的狀態(tài)機。 與同步調(diào)用阻塞當(dāng)前線程不同,當(dāng)Future執(zhí)行并遇到阻塞時,它會讓出當(dāng)前線程的控制權(quán),這樣其它的Future就可以在該線程中運行,這種方式完全不會導(dǎo)致當(dāng)前線程的阻塞。
使用 async fn 語法來創(chuàng)建一個異步函數(shù):
async fn do_something() {
println!("go go go !");
}
fn main() {
let future = do_something();//注意:異步函數(shù)的返回值是一個 Future,若直接調(diào)用該函數(shù),不會輸出任何結(jié)果,因為 Future 還未被執(zhí)行
block_on(future); //block_on會阻塞當(dāng)前線程直到指定的Future執(zhí)行完成
do_something().await;//或者是使用.await使Future執(zhí)行
}
block_on和.await的區(qū)別:block_on會阻塞當(dāng)前線程,而.await并不會阻塞當(dāng)前的線程。在async fn函數(shù)中使用.await可以等待當(dāng)前Future完成,在等待的過程中,該線程還可以繼續(xù)執(zhí)行其它Future,即await可以保證在當(dāng)前函數(shù)中的同步,但是和線程中其他Future是異步執(zhí)行。
use futures::executor::block_on;
struct Song {
}
async fn learn_song() -> Song {
Song {
}
}
async fn sing_song(song: Song) {
}
async fn dance() {
}
async fn learn_and_sing() {
// 這里使用.await來等待學(xué)歌的完成,但是并不會阻塞當(dāng)前線程,該線程在學(xué)歌的任務(wù)過程中,完全可以去執(zhí)行跳舞的任務(wù)
let song = learn_song().await;
// 唱歌必須要在學(xué)歌之后,await保證了當(dāng)前函數(shù)內(nèi)的同步
sing_song(song).await;
}
async fn async_main() {
let f1 = learn_and_sing();
let f2 = dance();
//join!可以并發(fā)的處理和等待多個Future,若learn_and_sing Future被阻塞,那dance Future可以拿過線程的所有權(quán)繼續(xù)執(zhí)行。若dance也變成阻塞狀態(tài),那learn_and_sing又可以再次拿回線程所有權(quán),繼續(xù)執(zhí)行。
// 若兩個都被阻塞,那么async main會變成阻塞狀態(tài),然后讓出線程所有權(quán),并將其交給main函數(shù)中的block_on執(zhí)行器
futures::join!(f1, f2);
}
fn main() {
block_on(async_main());
}
Futrue
Futrue,可以理解為未來某個時間點會執(zhí)行的任務(wù)。 Future 特征是 Rust 異步編程的核心,畢竟異步函數(shù)是異步編程的核心,而 Future 恰恰是異步函數(shù)的返回值和被執(zhí)行的關(guān)鍵。
一個簡化版的Future特征:
trait SimpleFuture {
type Output;
//Future需要被執(zhí)行器poll(輪詢)后才能運行,通過調(diào)用該方法,可以推進 Future 的進一步執(zhí)行,直到被完成為止
//Future 并不能保證在一次 poll 中就被執(zhí)行完
fn poll(&mut self, wake: fn()) -> Poll
}
//若在當(dāng)前 poll 中, Future 可以被完成,則會返回 Poll::Ready(result) ,反之則返回 Poll::Pending, 并且安排一個 wake 函數(shù):當(dāng)未來 Future 準(zhǔn)備好進一步執(zhí)行時, //該函數(shù)會被調(diào)用,然后管理該 Future 的執(zhí)行器會再次調(diào)用 poll 方法,此時 Future 就可以繼續(xù)執(zhí)行了。
enum Poll
Ready(T),
Pending,
}
當(dāng)使用關(guān)鍵字async修飾函數(shù)時,函數(shù)會返回一個Futrue。該函數(shù)就成為了一個異步函數(shù)(有特征的狀態(tài)機)。值得注意的是,同步函數(shù)中不能調(diào)用異步函數(shù),如果調(diào)用了異步函數(shù),那么同步函數(shù)就成了異步函數(shù)了。
理解:加了async相當(dāng)于把函數(shù)封裝成一個Futrue任務(wù),執(zhí)行器可以將這些Futrue放到一個任務(wù)隊列中,輪詢獲取隊列中的任務(wù),依次調(diào)用任務(wù)的poll函數(shù)。如果本次調(diào)用結(jié)束,則此task運行完成;如果本次調(diào)用沒有結(jié)束,則為task注冊wake函數(shù),待task下次可以啟動時讓其自己調(diào)用wake將自己放進任務(wù)隊列中,供執(zhí)行器再次執(zhí)行。
Pin
在 Rust 中,所有的類型可以分為兩類:
類型的值可以在內(nèi)存中安全地被移動,例如數(shù)值、字符串、布爾值、結(jié)構(gòu)體、枚舉,幾乎所有類型都可以落入到此范疇內(nèi)。自引用類型(值在內(nèi)存中移動不安全):struct SelfRef {
value: String,
pointer_to_value: *mut String, //指向value的指針
}
自引用類型容易遇到一個問題,若上述value使用新的字符串賦值,而pointer_to_value依然認(rèn)為指向之前的字符串,一個重大 bug 就出現(xiàn)了。Pin可以防止這種情況出現(xiàn),它的作用就是可以防止一個類型在內(nèi)存中被移動(可以理解為當(dāng)類型的值不可在內(nèi)存移動時,通過某種方式安全獲取它的所有權(quán)和可變引用(如下Pin聲明,通過一個指針做到這點),并且獲得的值無法在不安全的函數(shù)中使用,例如,std::mem::swap)。
Pin是一個結(jié)構(gòu)體類型:
pub struct Pin
{
pointer: P,
}
與Pin相對的是特征UnPin,絕大多數(shù)類型都不在意是否被移動,它們都自動實現(xiàn)了Unpin特征。Unpin是一種標(biāo)記特征( marker trait ),該特征未定義任何行為,一旦類型定義了Unpin特征就表示其可以在內(nèi)存中安全移動(實例可以在內(nèi)存中進行移動,而不會觸發(fā) Rust 的默認(rèn)行為,即在移動值時需要顯式地調(diào)用 std::mem::drop 來釋放舊值)。
使用Pin的總結(jié):
若 T: Unpin ( Rust 類型的默認(rèn)實現(xiàn)),那么 Pin
將 !Unpin 值固定到棧上需要使用 unsafe。將 !Unpin 值固定到堆上無需 unsafe ,可以通過 Box::pin 來簡單的實現(xiàn)。 當(dāng)固定類型 T: !Unpin 時,你需要保證數(shù)據(jù)從被固定到被 drop 這段時期內(nèi),其內(nèi)存不會變得非法或者被重用。
Pin的詳細介紹
同時運行多個Future
join!宏,它允許我們同時等待多個不同 Future 的完成,且可以并發(fā)地運行這些 Future。 use futures::join;
async fn enjoy_book_and_music() -> (Book, Music) {
let book_fut = enjoy_book();
let music_fut = enjoy_music();
join!(book_fut, music_fut) //join! 會返回一個元組,里面的值是對應(yīng)的 Future 執(zhí)行結(jié)束后輸出的值。
}
try_join!,某一個 Future 報錯后就立即停止所有 Future 的執(zhí)行,特別是當(dāng)Future返回Result時。 use futures::try_join;
async fn get_book() -> Result
async fn get_music() -> Result
async fn get_book_and_music() -> Result<(Book, Music), String> {
let book_fut = get_book();
let music_fut = get_music();
try_join!(book_fut, music_fut)//傳給 try_join! 的所有 Future 都必須擁有相同的錯誤類型
}
select!宏,同時等待多個Future,且任何一個Future結(jié)束后,都可以立即被處理。相比之下,join! 只有等所有 Future 結(jié)束后,才能集中處理結(jié)果。 use futures::{
future::FutureExt, // for `.fuse()`
pin_mut,
select,
};
async fn task_one() { /* ... */ }
async fn task_two() { /* ... */ }
async fn race_tasks() {
let t1 = task_one().fuse();//讓Future實現(xiàn)FusedFuture特征(當(dāng)Future一旦完成后,那select就不能再對其進行輪詢使用),是使用select必須的
let t2 = task_two().fuse();
pin_mut!(t1, t2);//為Future實現(xiàn)Unpin特征,是使用select必須的
loop //select一般會配合loop使用,否則其中一個任務(wù)完成,函數(shù)就結(jié)束了且不會等待另一個任務(wù)的完成
{
select! {
() = t1 => println!("任務(wù)1完成"),
() = t2 => println!("任務(wù)2完成"),
complete => break, //當(dāng)所有的 Future完成后才會被執(zhí)行
default => panic!(), //若沒有任何Future處于Ready狀態(tài),則該分支會被立即執(zhí)行(和complete類似)
}
}
}
其他
柚子快報激活碼778899分享:開發(fā)語言 后端 rust基礎(chǔ)
相關(guān)文章
本文內(nèi)容根據(jù)網(wǎng)絡(luò)資料整理,出于傳遞更多信息之目的,不代表金鑰匙跨境贊同其觀點和立場。
轉(zhuǎn)載請注明,如有侵權(quán),聯(lián)系刪除。