變量
聲明變量關(guān)鍵字:let
變量值分為兩種類型:
- 可變的(
mut
) - 不可變
變量類型:
- 布爾型 -
bool
表示 true 或 false - 無符號整型-
u8
u32
u64
u128
表示正整數(shù) - 有符號整型 -
i8
i32
i64
i128
表示正負整數(shù) - 指針大小的整數(shù) -
usize
isize
表示內(nèi)存中內(nèi)容的索引和大小 - 浮點數(shù) -
f32
f64
- 元組(tuple) -
(value, value, ...)
用于在棧上傳遞固定序列的值 - 數(shù)組 - 在編譯時已知的具有固定長度的相同元素的集合
- 切片(slice) - 在運行時已知長度的相同元素的集合
-
str
(string slice) - 在運行時已知長度的文本
可以通過將類型附加到數(shù)字的末尾來明確指定數(shù)字類型(如 13u32
和 2u8
)母剥。
使用as
進行類型轉(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;
當元組只有一個值的時候锈拨,需要寫成(x, )敛腌,這是為了和括號中的其他值進行區(qū)分祭饭。
表達式
let x = (let y = 6); //error
let
沒有右值語義瓮床,Rust中不允許這么用盹舞,可以這么寫:
let y = {
let x = 3;
x
};
函數(shù)
函數(shù)
多個返回值
函數(shù)可以通過元組來返回多個值。
元組元素可以通過他們的索引來獲取纤垂。
支持各種形式的解構(gòu)矾策,允許我們以符合人類工程學的方式提取數(shù)據(jù)結(jié)構(gòu)的子片段磷账。
返回空
如果沒有為函數(shù)指定返回類型峭沦,它將返回一個空的元組,也稱為單元逃糟。
一個空的元組用 ()
表示吼鱼。
函數(shù)指針
函數(shù)指針實現(xiàn)了所有三個閉包 trait(Fn、FnMut 和 FnOnce)绰咽,所以總是可以在調(diào)用期望閉包的函數(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ù)中的應用琐谤,常常與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);
}
閉包默認會按引用捕獲變量(在此例中為 i
)玩敏。如果將此閉包返回斗忌,則引用也會跟著返回。而 i 會被銷毀旺聚,所以引用變?yōu)閼掖怪羔樦簟R虼艘由蟤ove關(guān)鍵字,移動所有權(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)境,它們對應函數(shù)的三種獲取參數(shù)的方式碱璃,分別是獲取所有權(quán)弄痹、可變借用、不可變借用嵌器。這三種捕獲值的方式被編碼為如下三個Fn trait:
(1)FnOnce消費從周圍作用域捕獲的變量界酒,閉包周圍的作用域被稱為其環(huán)境。為了消費捕獲到的變量嘴秸,閉包必須獲取其所有權(quán)并在定義閉包時將其移進閉包毁欣。其名稱的Once部分代表了閉包不能多次獲取相同變量的所有權(quán)庇谆。
(2)FnMut獲取可變的借用值,所以可以改變其環(huán)境凭疮。
(3)Fn從其環(huán)境獲取不可變的借用值饭耳。
當創(chuàng)建一個閉包時,rust會根據(jù)其如何使用環(huán)境中的變量來推斷我們希望如何引用環(huán)境执解。由于所有閉包都可以被調(diào)用至少一次寞肖,因此所有閉包都實現(xiàn)了FnOnce。沒有移動被捕獲變量的所有權(quán)到閉包的閉包也實現(xiàn)了FnMut衰腌,而不需要對捕獲的變量進行可變訪問的閉包實現(xiàn)了Fn新蟆。
自動推導
閉包會為每個參數(shù)和返回類型推導一個具體類型,但是不能推導兩次右蕊。如下錯誤:
let example_closure = |x| x;
let s = example_closure(String::from("hello"));
let n = example_closure(5); //報錯琼稻,嘗試推導兩次,變成了不同的類型
與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
后的判斷表達式不需要括號萝风。
從塊表達式返回值
if let
let number = if condition {
5
} else {
6
};
if
嘀掸、else
返回值的類型必須是相同的。當所有if
规惰,else if
塊無法匹配時睬塌,調(diào)用任何一個else
塊,如果無else
歇万,則返回()
揩晴。因此,此代碼中的if
表達式的else
塊(雖然沒有顯式寫出)返回值為()
堕花,與if
塊中的i32
類型不一致文狱,報E0308
錯誤。舉例如下:
fn r(n: i32) -> i32 {
if n > 0 {
0
}
1
}
match let
let result = match food {
"hotdog" => "is hotdog",
// 注意缘挽,當它只是一個返回表達式時瞄崇,大括號是可選的
_ => "is not hotdog",
};
loop
let result = loop {
counter += 1;
if counter == 10 {
// loop 可以被中斷以返回一個值。
break counter * 2;
}
};
//result等于20壕曼。
避免使用while true {...}
苏研,使用loop
。Rust使用LLVM腮郊,而LLVM沒有表達無限循環(huán)的方式摹蘑,因此在某些時候會出錯。如下:
let x;
while true { x = 1; break; }
println!("{}", x);
編譯器報錯轧飞,"use of possibly uninitialised variable"衅鹿。
match
match
是窮盡的撒踪,意為所有可能的值都必須被考慮到。
match x {
0 => {
println!("found zero");
}
// 我們可以匹配多個值
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);
}
// 這是默認匹配大渤,如果沒有處理所有情況制妄,則必須存在該匹配
_ => {
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)體
// 這仍然是一個在棧上的結(jié)構(gòu)體
let loc = Location(42, 32);
單元結(jié)構(gòu)體
struct Marker;
其中,元組結(jié)構(gòu)體只有一個字段時烫幕,稱之為New Type
模式俺抽。
方法(封裝特性)
與函數(shù)(function)不同,方法(method)是與特定數(shù)據(jù)類型關(guān)聯(lián)的函數(shù)较曼。
靜態(tài)方法 — 屬于某個類型磷斧,調(diào)用時使用 ::
運算符。
實例方法 — 屬于某個類型的實例诗芜,調(diào)用時使用 .
運算符瞳抓。
struct SeaCreature {
noise: String,
}
impl SeaCreature {
fn get_sound(&self) -> &str {
&self.noise
}
}
fn main() {
let creature = SeaCreature {
// 靜態(tài)方法
noise: String::from("blub"),
};
// 實例方法
println!("{}", creature.get_sound());
}
枚舉
enum Species {
Crab,
Octopus,
Fish,
Clam
}
match ferris.species {
Species::Crab => println!("{} is a crab",ferris.name),
_ => xxx,
}
enum
的元素可以有一個或多個數(shù)據(jù)類型埃疫,從而使其表現(xiàn)得像 C 語言中的聯(lián)合伏恐。
當使用 match
對一個 enum
進行模式匹配時,可以將變量名稱綁定到每個數(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) => {
...
}
}
泛型
// 一個部分定義的結(jié)構(gòu)體類型
struct BagOfHolding<T> {
item: T,
}
fn main() {
// 注意:通過使用泛型翠桦,我們創(chuàng)建了編譯時創(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 有個強大的操作符 ?
來與之配合销凑。
option
用法
Option主要有以下一些用法:
- 初始化值;
- 作為在整個輸入范圍內(nèi)沒有定義的函數(shù)的返回值仅炊;
- 作為返回值斗幼,用
None
表示出現(xiàn)的簡單錯誤; - 作為結(jié)構(gòu)體的可選字段抚垄;
- 作為結(jié)構(gòu)體中可借出或者是可載入的字段蜕窿;
- 作為函數(shù)的可選參數(shù);
- 代表空指針呆馁;
- 用作復雜情況的返回值桐经。
值復制方法
let x = 123u8;
let y: Option<&u8> = Some(&x);
let z = y.copied();
copied將引用轉(zhuǎn)換為值。
Vectors
Vec
有一個形如 iter()
的方法可以為一個 vector 創(chuàng)建迭代器浙滤,這允許我們可以輕松地將 vector 用到 for
循環(huán)中去阴挣。
fn main() {
// 我們可以顯式確定類型
let mut i32_vec = Vec::<i32>::new(); // turbofish <3
i32_vec.push(1);
// 自動檢測類型
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)存細節(jié):
-
Vec
是一個結(jié)構(gòu)體纺腊,但是內(nèi)部其實保存了在堆上固定長度數(shù)據(jù)的引用畔咧。 - 一個 vector 開始有默認大小容量茎芭,當更多的元素被添加進來后,它會重新在堆上分配一個新的并具有更大容量的定長列表誓沸。(類似 C++ 的 vector)
迭代器
迭代器實現(xiàn)了Iterator
(trait
)骗爆,定義于標準庫中。該trait
定義如下:
trait Iterator {
type Item;
fn next(mut self) -> Option<Self::Item>;
//省略其它內(nèi)容
}
如果希望迭代可變引用蔽介,可以使用iter_mut
摘投。
切片
Slice是對一個數(shù)組的引用片段,代表一個指向數(shù)組起始位置的指針和數(shù)組長度虹蓄。
Slice是一種新的類型犀呼,而不是簡單的一個引用而已。
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!";
聲明了一個 &str
類型律胀;
let hello_world: &'static str = "Hello, world!";
hello_world 與 字符串常量一樣宋光。
utf8
// &a[3..7]表示螃蟹
let a = "hi ??";
// chars[3]表示螃蟹,chars[i]表示一個字符炭菌,每個char占4字節(jié)
let chars = "hi ??".chars().collect::<Vec<char>>();
String
String不能用index訪問的原因:
- 避免根據(jù)UTF-8編碼得到的長度與期望的不一致罪佳;
- 根據(jù)index訪問需要從頭遍歷(在Rust中需要判斷有效字符數(shù)量),所以訪問的時間復雜度不為
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);//會將之前Blue對應的值覆蓋掉
ss.entry(String::from("Yellow")).or_insert(20); // 沒有實體時插入
// 根據(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)存回收機制 |
---|---|
其他語言 | GC會跟蹤聲明的變量克握,當它不再被使用時蕾管,自動清除。如果沒有GC菩暗,程序員負責在恰當?shù)臅r候釋放這段申請的內(nèi)存掰曾。 |
Rust | 使用RAII(“資源獲取即初始化”),在變量invalid 時停团,調(diào)用drop 回收旷坦。 |
Move,Copy客蹋,Clone
Move
let s1 = String::from("hello");
let s2 = s1;
s1
移動到了s2
塞蹭,不僅僅是shallow copy
,s1
還被置為invalid
了讶坯。棧上的s1對象和s2對象進行按位淺拷貝番电,堆上數(shù)據(jù)不變。
將所有者作為參數(shù)傳遞給函數(shù)時,其所有權(quán)將移交至該函數(shù)的參數(shù)漱办。 在一次移動后这刷,原函數(shù)中的變量將無法再被使用。在移動期間娩井,所有者的堆棧值將會被復制到函數(shù)調(diào)用的參數(shù)堆棧中暇屋。
Rust不會自動創(chuàng)建“深拷貝”,需要自己用Clone()
洞辣。但是咐刨,如果實現(xiàn)了Copy
的trait
,那么值會被復制入棧扬霜。
Copy
實現(xiàn)Copy的類型在堆上沒有資源定鸟,值完全處于棧上。淺拷貝后著瓶,源與目標對象都可以訪問联予,是獨立的數(shù)據(jù)。為了#[derive(Copy, Clone)]
工作材原,成員也必須實現(xiàn)Copy
沸久。
在派生語句中的Clone是需要的,因為Copy的定義類似這樣:pub trait Copy:Clone {}余蟹,即要實現(xiàn)Copy需要先實現(xiàn)Clone
Copy與Drop不能同時存在卷胯。
Drop
變量在離開作用范圍時,編譯器會自動銷毀變量客叉,如果變量類型有Drop
trait诵竭,就先調(diào)用Drop::drop
方法话告,做資源清理兼搏,一般會回收heap內(nèi)存等資源,然后再收回變量所占用的stack內(nèi)存沙郭。如果變量沒有Drop
trait佛呻,那就只收回stack內(nèi)存。
如果類型實現(xiàn)了Copy
trait病线,在copy語義中并不會調(diào)用Clone::clone
方法吓著,不會做deep copy,那就會出現(xiàn)兩個變量同時擁有一個資源(比如說是heap內(nèi)存等)送挑,在這兩個變量離開作用范圍時绑莺,會分別調(diào)用Drop::drop
方法釋放資源,這就會出現(xiàn)double free錯誤惕耕。
Clone
幫助實現(xiàn)“深拷貝”纺裁。
釋放
釋放是分級進行的。刪除一個結(jié)構(gòu)體時,結(jié)構(gòu)體本身會先被釋放欺缘,緊接著才分別釋放相應的子結(jié)構(gòu)體并以此類推栋豫。
內(nèi)存細節(jié):
- Rust 通過自動釋放內(nèi)存來幫助確保減少內(nèi)存泄漏。
- 每個內(nèi)存資源僅會被釋放一次谚殊。
引用
引用默認也是不可變的丧鸯。可變引用才可以修改被引用的值嫩絮。已經(jīng)被引用的變量丛肢,其所有權(quán)不可以被移動。
可變引用(&mut)
- 可變引用只能出現(xiàn)一次剿干,避免數(shù)據(jù)競爭摔踱。
- 已有不可變引用,可變引用就不能再出現(xiàn)怨愤。
解引用
使用 &mut
引用時, 你可以通過 *
操作符來修改其指向的值派敷。 你也可以使用 *
操作符來對所擁有的值進行拷貝(前提是該值可以被拷貝)。
操作符".
"可以自動解引用:
let f = Foo { value: 42 };
let ref_ref_ref_f = &&&f;
println!("{}", ref_ref_ref_f.value);
解引用多態(tài)與可變性交互
解引用多態(tài)有如下三種情況:
- 當 T: Deref<Target=U> 時從 &T 到 &U撰洗。
- 當 T: DerefMut<Target=U> 時從 &mut T 到 &mut U篮愉。
- 當 T: Deref<Target=U> 時從 &mut T 到 &U。(注意:此處反之是不可能的)
生命周期
生命周期的主要目標是避免懸垂引用差导,大部分時候是可以隱含并且被推斷的试躏。
顯式生命周期
盡管 Rust 不總是在代碼中將它展示出來,但編譯器會理解每一個變量的生命周期并進行驗證以確保一個引用不會有長于其所有者的存在時間设褐。 同時颠蕴,函數(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 則擁有另一個不相關(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)變量的范圍也可以被限制在一個函數(shù)內(nèi)
static mut SECRET: &'static str = "swordfish";
// 字符串字面值擁有 'static 生命周期
let msg: &'static str = "Hello World!";
隱式生命周期
三條規(guī)則確定不需要生命周期注解:
- 第一條規(guī)則是:每一個是引用的參數(shù)都有它自己的生命周期參數(shù)寡键。
- 第二條規(guī)則是:如果只有一個輸入生命周期參數(shù),那么它被賦予所有輸出生命周期參數(shù):
fn foo<'a>(x: &'a i32) -> &'a i32
雪隧。 - 第三條規(guī)則是:在struct的impl語句中西轩,如果方法有多個輸入生命周期參數(shù),不過其中之一因為方法的緣故為
&self
或&mut self
脑沿,那么self
的生命周期被賦給所有輸出生命周期參數(shù)藕畔。第三條規(guī)則使得方法更容易讀寫,因為只需更少的符號庄拇。
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對應的類型為結(jié)構(gòu)體Wrapper仍秤,而Wrapper是有生命周期的限制的,此時編譯器不知道如何判斷可很,因此報錯诗力。
結(jié)構(gòu)體生命周期
如果結(jié)構(gòu)體成員含有引用類型,則需要顯式指定生命周期我抠。如下:
struct StuA<'a> {
name: &'a str,
}
相應的苇本,在方法中,也需要聲明結(jié)構(gòu)體的生命周期:
impl<'b> StuA<'b> {
// 隱式生命周期第二條規(guī)則
fn do_something(&self) -> i32 {
3
}
fn do_something2(&self, s: &str) -> &str{
// 隱式生命周期第三條規(guī)則
//相當于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)于繼承和面向接口編程的思想贱鄙。
注釋
文檔注釋
內(nèi)部支持Markdown標記劝贸,也支持對文檔中的示例代碼進行測試,可以用rustdoc生成HTML文檔逗宁。
- /// :生成庫文檔映九,用于函數(shù)或結(jié)構(gòu)體的說明;
- //! :生成庫文檔瞎颗,用于說明整個模塊的功能件甥;
println!宏
-
println!("{}", 2)
,nothing表示Display哼拔; -
println!("{:?}", 2)
引有,?表示Debug管挟; - o代表八進制轿曙,x/X表示十六進制,b表示二進制僻孝;
- p代表指針;
- e/E表示指數(shù)守谓;
內(nèi)存布局
Rust 程序有 3 個存放數(shù)據(jù)的內(nèi)存區(qū)域:
- 數(shù)據(jù)內(nèi)存 - 對于固定大小和靜態(tài)(即在整個程序聲明周期中都存在)的數(shù)據(jù)穿铆。 例如 “Hello World”字面值常量,該文本的字節(jié)只能讀取斋荞,因此它們位于該區(qū)域中荞雏。 編譯器對這類數(shù)據(jù)做了很多優(yōu)化,由于位置已知且固定,因此通常認為編譯器使用起來非撤镉牛快悦陋。
- 棧內(nèi)存 - 對于在函數(shù)中聲明為變量的數(shù)據(jù)。 在函數(shù)調(diào)用期間筑辨,內(nèi)存的位置不會改變俺驶,因為編譯器可以優(yōu)化代碼,所以棧數(shù)據(jù)使用起來非彻髟快暮现。
- 堆內(nèi)存 - 對于在程序運行時創(chuàng)建的數(shù)據(jù)。 此區(qū)域中的數(shù)據(jù)可以添加楚昭、移動栖袋、刪除、調(diào)整大小等抚太。由于它的動態(tài)特性塘幅,通常認為它使用起來比較慢, 但是它允許更多創(chuàng)造性的內(nèi)存使用尿贫。當數(shù)據(jù)添加到該區(qū)域時晌块,我們稱其為分配。 從本區(qū)域中刪除 數(shù)據(jù)后帅霜,我們將其稱為釋放匆背。
結(jié)構(gòu)體內(nèi)存對齊
對齊規(guī)則:
- 每種類型都有一個數(shù)據(jù)對齊屬性。在X86平臺上u64和f64都是按照32位對齊的身冀。
- 一種類型的大小是它對齊屬性的整數(shù)倍钝尸,這保證了這種類型的值在數(shù)組中的偏移量都是其類型尺寸的整數(shù)倍,可以按照偏移量進行索引搂根。需要注意的是珍促,動態(tài)尺寸類型的大小和對齊可能無法靜態(tài)獲取。
- 結(jié)構(gòu)體的對齊屬性等于它所有成員的對齊屬性中最大的那個剩愧。Rust會在必要的位置填充空白數(shù)據(jù)猪叙,以保證每一個成員都正確地對齊,同時整個類型的尺寸是對齊屬性的整數(shù)倍仁卷。
- 不保證數(shù)據(jù)填充和成員順序穴翩,編譯器可能進行優(yōu)化。
struct A {
a: u8,
b: u32,
c: u16
}
按照前3條規(guī)則锦积,A的大小應該為12字節(jié)芒帕,而實際上編譯后可能只有8字節(jié)。
指針
原生指針:
-
*const T
- 指針常量丰介。 -
*mut T
- 可變指針背蟆。
取得指針所指地址內(nèi)的數(shù)據(jù)鉴分,需要在unsafe{...}
中,因為不能保證該原生指針指向有效數(shù)據(jù)带膀。
智能指針
智能指針通常使用結(jié)構(gòu)體實現(xiàn)志珍。智能指針區(qū)別于常規(guī)結(jié)構(gòu)體的顯著特征在于其實現(xiàn)了Deref和Drop trait。
- Deref trait允許智能指針結(jié)構(gòu)體實例表現(xiàn)的像引用一樣垛叨,這樣就可以編寫既用于引用伦糯,又用于智能指針的代碼。
- Drop trait允許我們自定義當智能指針離開作用域時執(zhí)行的代碼点额。
Box
Box
將數(shù)據(jù)從棧上移動到堆舔株,棧上存放指向堆數(shù)據(jù)的指針。
struct Ocean {
animals: Vec<Box<dyn NoiseMaker>>,
}
let ocean = Ocean {
animals: vec![Box::new(ferris), Box::new(sarah)],
};
適用于:
- 當有一個在編譯時未知大小的類型还棱,而又需要在確切大小的上下文中使用這個類型值的時候载慈;(舉例子:在一個list環(huán)境下,存放數(shù)據(jù)珍手,但是每個元素的大小在編譯時又不確定)办铡;
- 當有大量數(shù)據(jù)并希望在確保數(shù)據(jù)不被拷貝的情況下轉(zhuǎn)移所有權(quán)的時候;
- 當希望擁有一個值并只關(guān)心它的類型是否實現(xiàn)了特定trait而不是其具體類型時琳要。
Rc
引用計數(shù)指針寡具,將數(shù)據(jù)從棧上移動到堆。允許其他Rc
指針不可變引用同一個數(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
一個智能指針容器】文唬可變與不可變引用都可以厦坛,引用規(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,
}
可以擁有一個表面上不可變的List杜秸,但是通過RefCell<T>
中提供內(nèi)部可變性方法來在需要時修改數(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) })
}
特點:
(1)弱引用通過Rc::downgrade
傳遞Rc實例的引用润绎,調(diào)用Rc::downgrade
會得到Weak<T>
類型的智能指針撬碟,同時將weak_count加1(不是將strong_count加1)。
(2)區(qū)別在于 weak_count
無需計數(shù)為 0 就能使 Rc 實例被清理莉撇。只要strong_count
為0就可以了呢蛤。
(3)可以通過Rc::upgrade
方法返回Option<Rc<T>>
對象。
Mutex
智能指針容器稼钩,可變與不可變引用都可以顾稀。可以用來編排多核CPU線程任務坝撑。
內(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)鍵字來標記模塊膨蛮、類型、函數(shù)和方法是公有的季研,默認情況下一切都是私有的敞葛。
繼承
Rust不支持繼承。但是Rust可以通過trait進行行為共享与涡。
trait對象
1惹谐、trait對象動態(tài)分發(fā)
(1)對泛型類型使用trait bound編譯器進行的方式是單態(tài)化處理,單態(tài)化的代碼進行的是靜態(tài)分發(fā)(就是說編譯器在編譯的時候就知道調(diào)用了什么方法)驼卖。
(2)使用 trait 對象時氨肌,Rust 必須使用動態(tài)分發(fā)。編譯器無法知曉所有可能用于 trait 對象代碼的類型酌畜,所以它也不知道應該調(diào)用哪個類型的哪個方法實現(xiàn)怎囚。為此,Rust 在運行時使用 trait 對象中的指針來知曉需要調(diào)用哪個方法桥胞。
2恳守、trait對象要求對象安全
只有 對象安全(object safe)的 trait 才可以組成 trait 對象。trait的方法滿足以下兩條要求才是對象安全的:
- 返回值類型不為 Self(例如
Clone
不能作為對象安全的trait對象) - 方法沒有任何泛型類型參數(shù)
高級特性
類型別名
類型別名的主要用途是減少重復埠戳。
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 有一個叫做 !
的特殊類型井誉。在類型理論術(shù)語中,它被稱為 empty type整胃,因為它沒有值颗圣。我們更傾向于稱之為 never type。在函數(shù)不返回的時候充當返回值屁使。
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 的值是 !在岂。
//當 Rust 要計算 guess 的類型時,它查看這兩個分支蛮寂。
//前者是 u32 值蔽午,而后者是 ! 值。
//因為 ! 并沒有一個值酬蹋,Rust 決定 guess 的類型是 u32
};
println!("You guessed: {}", guess);
}
說明:never type 可以強轉(zhuǎn)為任何其他類型及老。允許 match 的分支以 continue 結(jié)束是因為 continue 并不真正返回一個值抽莱;相反它把控制權(quán)交回上層循環(huán),所以在 Err 的情況骄恶,事實上并未對 guess 賦值食铐。
動態(tài)類型
動態(tài)大小類型(dynamically sized types),有時被稱為 “DST” 或 “unsized types”僧鲁,這些類型允許我們處理只有在運行時才知道大小的類型虐呻。
str
// 錯誤代碼
// 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 則是 兩個 值:str 的地址和其長度。這樣寞秃,&str 就有了一個在編譯時可以知道的大姓宓稹:它是 usize 長度的兩倍。也就是說春寿,無論字符串是多大朗涩,&str的大小我們總是知道的。
因此堂淡,引出動態(tài)大小類型的黃金規(guī)則:必須將動態(tài)大小類型的值置于某種指針之后馋缅。如:Box 或 Rc、&str等绢淀。
trait
每一個 trait 都是一個可以通過 trait 名稱來引用的動態(tài)大小類型萤悴。為了將 trait 用于 trait 對象,必須將他們放入指針之后皆的,比如 &Trait 或 Box(Rc 也可以)覆履。
Sized trait
為了處理 DST,Rust 用Sized trait 來決定一個類型的大小是否在編譯時可知费薄。這個 trait 自動為編譯器在編譯時就知道大小的類型實現(xiàn)硝全。