Rust 編程語言入門教程
第一章: Rust簡(jiǎn)介
為什么要用Rust
Rust是一種令人興奮的新編程語言, 它可以讓每一個(gè)人編寫可靠且高效的軟件.
它可以用來替換C/C++, Rust和他們具有同樣的性能, 但是很多常見的bug在編譯時(shí)就可以被消滅.
Rust是一種通用的編程語言, 但是它更善于以下場(chǎng)景:
需要運(yùn)行時(shí)的速度
需要內(nèi)存安全
更好的利用多處理器
與其他語言比較
C/C++ 性能非常好, 但類型系統(tǒng)和內(nèi)存都不太安全.
JAVA/C#, 擁有GC, 能保證內(nèi)存安全, 也有很多優(yōu)秀特性, 但是性能不行
RUST:
安全
無需GC
易于維護(hù), 調(diào)試, 代碼安全高效
Rust特別擅長(zhǎng)的領(lǐng)域
高性能webservice
webassembly
命令行工具
網(wǎng)絡(luò)編程
嵌入式設(shè)備
系統(tǒng)編程
Rust與Firefox
Rust最初是Mozilla公司的一個(gè)研究性項(xiàng)目, firefox是Rust產(chǎn)品應(yīng)用的一個(gè)重要例子.
Mozilla 一直以來都在用rust創(chuàng)建一個(gè)名為servo的實(shí)驗(yàn)性瀏覽器引擎, 其中的所以內(nèi)容都是并行執(zhí)行的.
目前servo的部分功能已經(jīng)被集成到firefox里面了
firefox原來的量子版就包含了servo的css渲染引擎
- rust使得firefox在這方便得到了巨大的性能改進(jìn)
Rust的用戶和案例
- google: 新操作系統(tǒng)Fuschia, 其中Rust代碼量大約30%
- Amazon: 基于Linux開發(fā)的直接可以在裸機(jī), 虛擬機(jī)上運(yùn)行容器的操作系統(tǒng).
- System76: 純Rust開發(fā)了下一代安全操作系統(tǒng)Redox
- 螞蟻金服: 庫操作系統(tǒng)Occlum
- 斯坦福和密歇根大學(xué): 嵌入式實(shí)時(shí)操作系統(tǒng), 應(yīng)用于google的加密產(chǎn)品.
- 微軟: 正在使用Rust重寫windows系統(tǒng)中的一些低級(jí)組件.
- 微軟: winRT/Rust項(xiàng)目
- Dropbox, yelp, Coursera, LINE, Cloudflare, Atlassian, npm, Ceph, 百度, 華為, Sentry, Deno
Rust的優(yōu)點(diǎn)
- 性能
- 安全性
- 無所畏懼的并發(fā)
Rust的缺點(diǎn)
- 難學(xué)
注意
Rust有很多獨(dú)有的概念, 它們和現(xiàn)在大多主流語言都不同.
- 所以學(xué)習(xí)Rust必須從基礎(chǔ)概念一步一步學(xué), 否則會(huì)懵.
參考教材:
The Rust programming language
Rust權(quán)威指南
安裝Rust
安裝rust
- 官網(wǎng): https://www.rust-lang.org
- Linux or Mac:
curl https://sh.rustup.rs -sSf| sh - windows: 按官網(wǎng)指示操作
- windows subsystem for Linux:
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
更新Rust
rustup update
卸載Rust
rustup self uninstall
驗(yàn)證安裝是否成功
rustc --version
- 結(jié)果格式: rustc x.y. z (abcabcabc yyyy-mm-dd)
- 會(huì)顯示最新穩(wěn)定版的: 版本號(hào), commit hash, commit 日期
第二章:第一個(gè)rust程序
hello world
fn main() {
println!("hello world");
}
編譯:
rustc main.rs
編譯完成后將會(huì)輸出 可執(zhí)行文件main
運(yùn)行程序使用
./main
Rust 程序解析
- 定義函數(shù) fn main() {}
沒有參數(shù), 沒有返回值 - main 函數(shù)很特別 : 他是每一個(gè)Rust 可執(zhí)行程序 最先運(yùn)行的代碼
- 打印文本: println!("hello, world!")
- rust 的縮進(jìn)是4個(gè)空格而不是tab
- println! 是一個(gè)Rust macro(宏)
- 如果是函數(shù)的話就沒有!
- "hello world" 是字符串, 它是println!的參數(shù)
- 這行代碼以;結(jié)尾
編譯和運(yùn)行是單獨(dú)的兩步
- 運(yùn)行rust程序之前需要先編譯, 命令為: rustc 源文件名
rustc main.rs
- 編譯成功后, 會(huì)生成一個(gè)二進(jìn)制文件
- 在wendow上還會(huì)生成一個(gè).pdb文件, 里面包含調(diào)試信息
- Rust是 ahead-of-time 預(yù)編譯的語言
- 可以先編譯程序, 然后把可執(zhí)行文件交給別人運(yùn)行(無需安裝Rust)
- rustc只適合簡(jiǎn)單的Rust程序
- 當(dāng)文件比較多, 項(xiàng)目比較多是需要使用Cargo
hello cargo
cargo 介紹
- Cargo 是Rust的構(gòu)建系統(tǒng)和包管理工具
- 構(gòu)建代碼非洲, 下載依賴的庫装处, 構(gòu)建這些庫
- 安裝Rust的時(shí)候會(huì)安裝Cargo
- cargo --version
$cargo --version cargo 1.55.0 (32da73ab1 2021-08-23)
使用Cargo創(chuàng)建項(xiàng)目
創(chuàng)建項(xiàng)目: cargo new hello_cargo
-
創(chuàng)建完成后項(xiàng)目的目錄結(jié)構(gòu)
$tree -a . hello_cargo ├── Cargo.toml ├── .git │ ├── config │ ├── description │ ├── HEAD ................... ├── .gitignore └── src └── main.rs
目錄結(jié)構(gòu)說明:
Cargo.toml:- TOML(Tom's obvious, Minimal Language)格式妖混,是Cargo的配置格式
[package] name = "hello_cargo" version = "0.1.0" edition = "2018" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies]
-
[package], 是一個(gè)區(qū)域標(biāo)題, 表示下方內(nèi)容是用來配置包(package)的.
- name: 項(xiàng)目名
- version: 項(xiàng)目版本
- authors: 項(xiàng)目作者
[dependencies], 另一個(gè)區(qū)域的開始萌庆,它會(huì)列出項(xiàng)目的依賴項(xiàng).
-
在Rust里面,代碼的包稱作crate(板條箱;簍子;)
src/main.js
- cargo生成的main.rs在src目錄下
- 而Cargo.toml在項(xiàng)目頂層下
- 源代碼都應(yīng)該在src目錄下
- 頂層目錄可以放置:README, 許可信息猜惋, 配置文件和其它與程序源代碼無關(guān)的文件
- 如果創(chuàng)建項(xiàng)目時(shí)沒有使用cargo, 也可以把項(xiàng)目轉(zhuǎn)化為使用cargo:
- 把源代碼文件移動(dòng)到src下
- 創(chuàng)建Cargo.toml并填寫相應(yīng)的配置
構(gòu)建Cargo項(xiàng)目
- cargo build
創(chuàng)建可執(zhí)行文件:target/debug/hello_cargo (linux/macos)或者 target\debug\hello_cargo.exe (windows) - 第一次運(yùn)行cargo build會(huì)在頂層目錄生成cargo.lock文件
- 該文件負(fù)責(zé)追蹤項(xiàng)目依賴的精確版本
- 不需要手動(dòng)修改該文件
- 構(gòu)建和運(yùn)行cargo項(xiàng)目
- cargo run
- cargo run, 編譯代碼+執(zhí)行結(jié)果
- 如果之前編譯成功過, 并且源代碼沒有改變音诫, 那么就會(huì)直接運(yùn)行二進(jìn)制文件
- cargo check
- cargo check, 檢查代碼惨奕, 確保能通過編譯雪位, 但是不產(chǎn)生任何可執(zhí)行文件
- cargo buid -release
- 編譯時(shí)會(huì)進(jìn)行優(yōu)化
- 代碼會(huì)運(yùn)行的更快竭钝, 但是編譯時(shí)間更長(zhǎng)
- 會(huì)在target/release 而不是target/debug生成可執(zhí)行文件
- 兩種配置, 一種是開發(fā)用的雹洗, 一種是發(fā)布用的
- 編譯時(shí)會(huì)進(jìn)行優(yōu)化
猜數(shù)字游戲
聲明變量
main.rs
println!("猜數(shù)香罐!");
println!("猜測(cè)一個(gè)數(shù)!");
//let聲明一個(gè)變量
//mut 聲明變量為可變變量时肿,
// 默認(rèn)情況下變量是不可變的庇茫, 除非顯示使用mut指明變量為可變變量
// = 賦值操作
//注意申明時(shí)沒有指定變量類型, 變量類型是根據(jù)賦初始值時(shí)進(jìn)行推導(dǎo)的
//String是由rust 的標(biāo)準(zhǔn)庫所提供的類型螃成,內(nèi)部是使用utf-8編碼
//:: 符號(hào)表明new是String類型的關(guān)聯(lián)函數(shù)旦签,關(guān)聯(lián)函數(shù)相當(dāng)與其他語言中的靜態(tài)方法
let mut guess = String::new();
//io是rust標(biāo)準(zhǔn)庫中的一個(gè)包名
//stdin()方法會(huì)返回一個(gè)Stdin對(duì)象, 標(biāo)準(zhǔn)輸入對(duì)象
//read_line 是標(biāo)準(zhǔn)輸入對(duì)象的一個(gè)方法,調(diào)用該方法時(shí)寸宏,需要提供一個(gè)可變字符串變量宁炫,用于接收用戶輸入
//& 取地址符號(hào),表示傳遞引用氮凝, 表示這個(gè)參數(shù)是一個(gè)引用reference羔巢,通過引用我們就可以在不同地方,訪問程序的統(tǒng)一塊內(nèi)存區(qū)域
//&mut表示這個(gè)引用是可變的, 如果不加mut竿秆, 表明這個(gè)引用也是不可變的
//read_line函數(shù)返回的是一個(gè)i0:Result<usize>對(duì)象启摄,expect是result對(duì)象的一個(gè)方法
// result是一個(gè)枚舉類型, 其有兩種類型的返回結(jié)果幽钢, 一種是err, 一種ok
// 如果返回的result為err, 該expect就會(huì)將錯(cuò)誤信息輸出到終端
//如果返回結(jié)果是ok類型歉备, expect就會(huì)提取出result中附加的值并將這個(gè)值作為結(jié)果返回給用戶
//如果不調(diào)用expect方法, 編譯時(shí)將會(huì)收到搅吁, rusult未被使用的警告
io::stdin().read_line(&mut guess).expect("無法讀取行");
//{} 是一個(gè)占位符威创,輸出時(shí)將會(huì)替換成相對(duì)應(yīng)的變量的值
println!("你猜測(cè)的數(shù)時(shí){}", guess);
引入依賴
rust中的依賴被稱為crate
crate分為兩種, 一種是二進(jìn)制格式的可執(zhí)行文件谎懦, 一種是源文件肚豺, 這種crate被稱為library craterust的crate倉庫為crates.io, 可以訪問該網(wǎng)站獲得相應(yīng)的crate
cargo 引入依賴的方式
#Cargo.toml
[package]
name = "hello_cargo"
version = "0.1.0"
edition = "2018"
[dependencies]
rand = "^0.7.0"
在dependencies 區(qū)域添加依賴的crate名稱和版本如上所示.
- 示例代碼
use std::io;
use rand::Rng; //trait
use std::cmp::Ordering;
fn main() {
println!("猜數(shù)!");
let secret_num = rand::thread_rng().gen_range(1, 101);
//rust循環(huán)
loop {
println!("猜測(cè)一個(gè)數(shù)界拦!");
let mut guess = String::new();
io::stdin().read_line(&mut guess).expect("無法讀取行");
println!("你猜測(cè)的數(shù)是: {}", guess);
//shadow
let guess:u32 = match guess.trim().parse() {
Ok(num) => num,
Err(ex) => {
//rust異常處理
println!("解析錯(cuò)誤 {} {}", guess, ex);
continue;
}
};
// rust條件運(yùn)算
match guess.cmp(&secret_num) {
Ordering::Less => println!("Too small!"), //arm
Ordering::Greater => println!("Too big!"),
Ordering::Equal => {
println!("You win!");
break;
}
}
}
}
第三章 通用的編程概念
變量和可變性
聲明變量使用let 關(guān)鍵字
-
默認(rèn)情況下吸申,變量是不可變的(immutable)
例如let x =5; // x 為不可變變量 println!("the value of x is {}", x); x = 6; // 注意: 這里會(huì)有編譯錯(cuò)誤 println!("the value of x is {}", x);
-
聲明變量時(shí), 在變量前面加上mut, 就可以使變量可變.
let mut x =5; println!("the value of x is {}", x); x = 6; println!("the value of x is {}", x);
變量和常量
- 常量(constant), 常量在綁定值以后也是不可變的享甸, 但是它與不可變的變量有很多區(qū)別:
- 不可以使用mut, 常量永遠(yuǎn)都是不可變的
- 聲明常量使用const關(guān)鍵字截碴, 它的類型必須被標(biāo)注
- 常量可以在任何作用域內(nèi)進(jìn)行聲明, 包括全局作用域
- 常量只可以綁定到常量表達(dá)式蛉威, 無法綁定到函數(shù)的調(diào)用結(jié)果或只能在運(yùn)行時(shí)才能計(jì)算出的值
- 在程序運(yùn)行期間日丹, 常量在其聲明的作用域內(nèi)一直有效
- 命名規(guī)范: Rust里常量使用全大寫字母, 每個(gè)單詞之間用下劃線分開蚯嫌, 例如:
const MAX_POINTS: u32 = 100_000;
- 常量(constant), 常量在綁定值以后也是不可變的享甸, 但是它與不可變的變量有很多區(qū)別:
shadowing (隱藏)
- 可以使用相同的名字聲明新的變量哲虾, 新的變量就會(huì)shadow(隱藏)之前聲明的同名變量
- 在后續(xù)代碼中這個(gè)變量名代表的就是新的變量
- shadow和把變量標(biāo)記為mut是不一樣的
例如fn main() { //定義不可變變量x let x = 5; println!("the value of x is {}", x); // x = 6 // 如果這里給x賦值會(huì)報(bào)錯(cuò) // 但是如果我們聲明一個(gè)同名的新的變量,就可以編譯通過 // 我們甚至可以改變x的數(shù)據(jù)類型, 甚至可以定義新的同名但是不同的可變性的變量 //新的同名變量 let x = 6; println!("the value of x is {}", x); //不同可變性的同名變量 let mut x = "my love"; println!("the value of x is {}", x); x = "hello kitty"; println!("the value of x is {}", x); }
Rust數(shù)據(jù)類型
標(biāo)量和復(fù)合類型
-
Rust 是靜態(tài)編譯語言择示, 在編譯時(shí)必須知道所有變量的類型
- 基于使用的值束凑, 編譯器通常能夠推斷出它的具體類型
- 但如果可能的類型比較多(例如把String 轉(zhuǎn)為整數(shù)的parse方法),就必須添加類型的標(biāo)注栅盲,否則編會(huì)報(bào)錯(cuò).
例如
let guess: u32 = "42".parse().expect("not a number") println!("{}", guess)
標(biāo)量類型
- 一個(gè)標(biāo)量類型代表一個(gè)單個(gè)的值
- Rust有四個(gè)主要的標(biāo)量類型:
- 整數(shù)類型
- 浮點(diǎn)類型
- 布爾類型
- 字符類型
整數(shù)類型
- 整數(shù)類型分為無符合整數(shù)類型汪诉,
- 無符合整數(shù)類型以u開頭
- 有符號(hào)整數(shù)類型以i開頭-
Rust 的整數(shù)類型列表如圖:
每種長(zhǎng)度都有對(duì)應(yīng)的有符號(hào)型和無符號(hào)型.
有符號(hào)范圍
-(2的n-1次方-1) 到(2的n-1次方-1)-
無符號(hào)范圍
- 0 到2的n次方 -1
- isize 和 usize類型- isize和usize類型的位數(shù)由程序運(yùn)行的計(jì)算機(jī)的架構(gòu)所決定
- 如果是64位的計(jì)算機(jī),那就是64位的
- 如果是32位的計(jì)算機(jī)谈秫,那就是32位的
- 使用isize或者usize的場(chǎng)景是對(duì)某種集合進(jìn)行索引操作
length signed unsigned 8-bit i8 u8 16-bit i16 u16 32-bit i32 u32 64-bit i64 i64 128-bit i128 u128 arch isize usize
-
整數(shù)的字面值
- 除了byte類型外扒寄, 所有的數(shù)值字面值都允許使用類型后綴
例如: 57u8: 值為57 類型為u8- 整數(shù)的默認(rèn)類型就是i32:
- 總體來說速度很快, 即使在64位系統(tǒng)中
| NumberLiteral | Example |
|:----|:----|
| Decimal | Oxff |
| Hex | Oo77 |
| Binary | Ob1111_0000 |
| Byte (u8 only) | b'A' |
- 總體來說速度很快, 即使在64位系統(tǒng)中
- 整數(shù)的默認(rèn)類型就是i32:
- 整數(shù)溢出
例如:u8的范圍是0-255, 如果你把一個(gè)u8變量的值設(shè)為256, 那么:
- 調(diào)試模式下編譯:rust會(huì)檢查整數(shù)溢出拟烫, 如果發(fā)生溢出该编, 程序運(yùn)行時(shí)就會(huì)panic
- 在發(fā)布模式下(--release)編譯:rust不會(huì)檢查可能導(dǎo)致panic的整數(shù)溢出
- 在這種模式下如果發(fā)生溢出:rust會(huì)執(zhí)行環(huán)繞操作
256變成0, 257變成1....
- 但是不會(huì)導(dǎo)致panic
浮點(diǎn)類型
- rust 有兩種基礎(chǔ)的浮點(diǎn)類型,也就是含有小數(shù)部分的類型
- f32, 32位, 單精度
- f64, 64位构灸,雙精度
- rust的浮點(diǎn)類型使用了IEEE-754標(biāo)準(zhǔn)來表述
- f64是默認(rèn)類型上渴,因?yàn)樵诂F(xiàn)代cpu上f64和f32的數(shù)度差不多岸梨,而且精度更高
- 例子
let x = 2.0 //默認(rèn)為f64 let y: f32 = 3.0; //f32
數(shù)值操作
```rust
let sum = 5+10;
let difference = 95.5-4.3; //f64
let product = 4*30;
let quotient = 56.7/32.2
let reminder = 54%5
```
布爾類型
字符類型
rust語言中char類型被用來描述語言中最基礎(chǔ)的單個(gè)字符.
字符類型的字面值使用單引號(hào)
占用4字節(jié)大小
是Unicode 標(biāo)量值, 可以表示比ASCII多得多的字符內(nèi)容:拼音稠氮, 中日文曹阔, 零長(zhǎng)度空白字符,emoji表情等.
- 其范圍為
- U+0000到U+D7FF
- U+E000到U+10FFFF
- 但是unicode中并沒有字符的概念隔披, 所以自覺上認(rèn)為的字符也許與Rust中的概念并不相符
復(fù)合類型
- 復(fù)合類型可以將多個(gè)值放在一個(gè)類型里
- Rust提供了兩種基礎(chǔ)的復(fù)合類型: 元組(Tuple), 數(shù)組
Tuple
- Tuple可以將多個(gè)類型的多個(gè)值放在一個(gè)類型里
- Tuple的長(zhǎng)度是固定的: 一旦聲明就無法改變
創(chuàng)建tuple
- 在小括號(hào)里赃份, 將值用逗號(hào)分開
- Tuple中的每個(gè)位置都對(duì)應(yīng)一個(gè)類型,tuple中各元素的類型不必相同
- 實(shí)例
let tup: (i32, f64, u8) = (500, 6.4, 1) println!("{},{},{}", tup.0, tup.1, tup.2)
獲取tuple的元素值
- 可以使用模式匹配來解構(gòu)(destructure)一個(gè)Tuple來獲取元素的值
- 例子
let tup: (i32, f64, u8) = (500, 6.4, 1); let (x, y, z) = tup; //這里使用模式匹配解構(gòu)tup的值 println!("{},{},{}", x, y, z)
訪問tuple的元素
- 在tuple變量使用點(diǎn)標(biāo)記法奢米,后接元素的索引號(hào)
- 實(shí)例
let tup: (i32, f64, u8) = (500, 6.4, 1)
println!("{},{},{}", tup.0, tup.1, tup.2)
數(shù)組
- 數(shù)組也可以將多個(gè)值放在一個(gè)類型里
- 數(shù)組中每個(gè)元素的類型必須相同
- 數(shù)組的長(zhǎng)度也是固定的
聲明一個(gè)數(shù)組
- 在中括號(hào)里抓韩, 各值用逗號(hào)分開
- 例子
fn main() { let a = [1, 2, 3, 4, 5] }
數(shù)組的用處
如果想讓你的數(shù)據(jù)存放在stack上, 而不是heap上鬓长,或者想保證有固定數(shù)量的元素谒拴, 這時(shí)使用數(shù)組更有好處.
-
數(shù)組沒有Vector靈活(以后再講)
- Vector和數(shù)組類似,它是由標(biāo)準(zhǔn)庫提供的
- Vector的長(zhǎng)度是可以改變的
- 如果你不確定應(yīng)該使用數(shù)組還是vector, 那么估計(jì)你應(yīng)該用vector.
-
例子
fn main() { let months = ["January", "Fabruary", ...... "December" ] }
數(shù)組的類型
- 數(shù)組的類型以這種形式來表示: [類型; 長(zhǎng)度]
- 例如: let a: [i32; 5] = [1, 2, 3, 4, 5];
- 另外一種聲明數(shù)組的方法
如果數(shù)組的沒一個(gè)元素值都相同涉波, 那么可以在:
在中括號(hào)里指定初始值
然后是一個(gè);
最后是數(shù)組的長(zhǎng)度
例如: let a = [3; 5]; 它相當(dāng)于:let a = [3, 3, 3, 3, 3]
- 數(shù)組的類型以這種形式來表示: [類型; 長(zhǎng)度]
訪問數(shù)組的元素
數(shù)組是stack上分配的單個(gè)塊的內(nèi)存
可以使用索引來訪問數(shù)組的元素
-
例子
let first = months[0]; let second = months[1];
-
如果訪問的索引超出了數(shù)組的范圍英上, 那么
- 編譯會(huì)通過
- 運(yùn)行會(huì)報(bào)錯(cuò)(runtime時(shí)會(huì)panic)
- Rust不會(huì)允許其繼續(xù)訪問相應(yīng)地址的內(nèi)存
說明:
在簡(jiǎn)單的情況下,編譯會(huì)報(bào)錯(cuò)啤覆,但是在復(fù)雜情況下苍日,編譯不會(huì)報(bào)錯(cuò)
例如
- Rust不會(huì)允許其繼續(xù)訪問相應(yīng)地址的內(nèi)存
let index = 15; let month = months[index]; //此時(shí)編譯會(huì)報(bào)錯(cuò)
let index = [15, 1, 2 , 3]; let month = months[index[0]]; //此時(shí)編譯不會(huì)報(bào)錯(cuò), 運(yùn)行時(shí)會(huì)發(fā)生panic
3.4 函數(shù)
- 聲明函數(shù)使用fn關(guān)鍵字
- 依照慣例窗声,針對(duì)函數(shù)和變量名相恃,rust使用snake case命名規(guī)范:
- 所有的字母都是小寫的, 單詞之間使用下劃線分開
- 例子
fn main() { println!("hello world"); another_function(); } fn another_function() { println!("another function") }
函數(shù)的參數(shù)
- parameters, arguments
- 例子
fn main() { println!("hello world"); another_function(5); } fn another_function(x: i32) { println!("the value of x is {}", x) }
函數(shù)中的語句(statement)和表達(dá)式(expression)
- 函數(shù)體由一系列語句組成笨觅, 可選的由一個(gè)表達(dá)式結(jié)束
- Rust是一個(gè)基于表達(dá)式的語言
- 語句是執(zhí)行一些動(dòng)作的指令
- 表達(dá)式會(huì)計(jì)算產(chǎn)生一個(gè)值
- 函數(shù)的定義也是語句
- 語句不返回值拦耐,所以不可以使用let將一個(gè)語句-賦給一個(gè)變量
例子
fn main() {
let x = 5;
let y = {
let x = 1;
//x+3; //注意這里有個(gè)分號(hào),它為語句而不是表達(dá)式屋摇, 但是這個(gè)語句有些特殊 它的值等于一個(gè)空的tuple 即()
x+3 //這里沒有分號(hào)揩魂, 它是一個(gè)表達(dá)式幽邓, 表達(dá)式的值為5 它是整個(gè)block的返回值炮温, 該值將會(huì)被賦給變量y
}
}
函數(shù)的返回值
- 在->符號(hào)后邊聲明函數(shù)返回值的類型, 但是不可以為返回值命名
- 在Rust里面牵舵, 返回值就是函數(shù)體里面最后一個(gè)表達(dá)式的值
- 若想提前返回柒啤, 需使用return 關(guān)鍵字, 并指定一個(gè)值
- 大多數(shù)函數(shù)都是默認(rèn)使用最后一個(gè)表達(dá)式為返回值
例子:
- 大多數(shù)函數(shù)都是默認(rèn)使用最后一個(gè)表達(dá)式為返回值
fn five() -> i32 {
5
}
fn main() {
let x = five();
println("the value of x is: {}", x);
}
注釋
單行注釋
多行注釋
文檔注釋
控制流
if 表達(dá)式
- if 表達(dá)式允許你根據(jù)條件來執(zhí)行不同的代碼分支
- 這個(gè)條件必須是bool類型
- if 表達(dá)式中畸颅, 與條件相關(guān)聯(lián)的代碼塊就叫做分支(arm)
- 可選的担巩, 在后面可以加上一個(gè)else表達(dá)式
- 但是如果使用了多余一個(gè)else if, 那么最好使用match來重構(gòu)代碼
- 例子
fn main() { let number = 3; if number < 5 { println!("condition was false"); } else { println!("condition was false"); } }
- 例子:else if
fn main() {
let number = 3;
if number % 4 == 0 {
println!("number is divisaible by 4");
} else if number % 3 == 0 {
println!("number is divisaible by 3");
} else {
println!("number is not divisaible by 3 or 4");
}
}
在let語句中使用if
- 因?yàn)閕f是一個(gè)表達(dá)式, 所以可以將它放在let語句中等號(hào)的右邊(例子)
fn main() {
let condition = ture;
let muber = if condition {5} else {6};
println!("The value of number is {}", number);
}
Rust的循環(huán)
- Rust提供了3中循環(huán):loop, while 和 for.
loop 循環(huán)
loop關(guān)鍵字告訴Rust反復(fù)地執(zhí)行一塊代碼没炒,直到你喊停位置
可以在loop循環(huán)中使用break關(guān)鍵字來告訴程序何時(shí)停止循環(huán)
例子
fn main() {
let mut counter = 0;
let resut = loop {
counter += 1;
if counter == 10 {
break counter * 2;
}
}
println!("The result is: {}", result);
}
while條件循環(huán)
另外一種常見的循環(huán)模式是每次執(zhí)行循環(huán)體之前都判斷一次條件.
while條件循環(huán)就是為這種模式而生的
-
例子
fn main() { let mut number = 3; while number != 0 { println!("{}!", number); number = number -1; } println!("LIFTOFF!!!"); }
for 循環(huán)遍歷集合
可以使用while 或 loop 來遍歷機(jī)會(huì)涛癌, 但是易出錯(cuò)且低效.
使用for循環(huán)更簡(jiǎn)潔緊湊, 它可以針對(duì)集合中的每一個(gè)元素來執(zhí)行一些代碼
-
例子
fn main() { let a = [10, 20, 30, 40, 50] for element in a.iter() { println!("the value is: {}", element); } }
由于for循環(huán)的安全,簡(jiǎn)潔性拳话,所以它在Rust里用的最多
Range
- 標(biāo)準(zhǔn)庫提供
- 指定一個(gè)開始數(shù)字和一個(gè)結(jié)束數(shù)字先匪,Range可以生成它們之間的數(shù)字(不包含介紹)
- rev方法可以反轉(zhuǎn)range
- 例子
fn main() { for number in (1..4).rev() { println!("{}!", number); } println!("LIFTOFF!"); }
第四章: 所有權(quán)
4.1 什么是所有權(quán)
- Rust的核心特性就是所有權(quán)
- 所有程序在運(yùn)行時(shí)都必須管理它們使用計(jì)算機(jī)內(nèi)存的方式
- 有些語言有垃圾收集機(jī)制,在程序運(yùn)行時(shí)弃衍, 它們不斷地尋找不再使用的內(nèi)存
- 在其他語言中呀非,程序員必須顯示地分配和釋放內(nèi)存
- Rust采用了第三種方式
- 內(nèi)存是通過一個(gè)所有權(quán)系統(tǒng)來管理的,其中包含一組編譯器在編譯時(shí)檢查的規(guī)則.
- 當(dāng)程序運(yùn)行時(shí)镜盯,所有權(quán)特性不會(huì)減慢程序運(yùn)行速度
Stack vs Heap
- Stack按值的接收順序來存儲(chǔ)岸裙,按相反的順序?qū)⑺鼈円瞥?后進(jìn)先出, LIFO)
- 添加數(shù)據(jù)叫做壓入棧
- 移除數(shù)據(jù)叫做彈出棧
- 所有存儲(chǔ)在stack上的數(shù)據(jù)必須擁有已知的固定的大小
- 編譯時(shí)大小未知的數(shù)據(jù)或運(yùn)行時(shí)大小可能變化的數(shù)據(jù)必須存放在heap上
- Heap內(nèi)存組織性差一些
- 當(dāng)你把數(shù)據(jù)放入heap時(shí), 你會(huì)請(qǐng)求一定數(shù)量的空間
- 操作系統(tǒng)在heap里找到一塊足夠大的空間速缆,把它標(biāo)記為在用降允,并返回一個(gè)指針,也就是這個(gè)空間的地址
- 這個(gè)過程叫做在heap上進(jìn)行分配艺糜, 有時(shí)僅僅稱為分配
- 把值壓到stack上不叫分配
- 因?yàn)橹羔樖且阎潭ù笮〉哪飧猓】梢园阎羔槾娣旁趕tack上.
但如果想要實(shí)際數(shù)據(jù),你必須使用指針來定位 - 把數(shù)據(jù)壓倒stack上要比在heap上分配快得多:
- 因?yàn)椴僮飨到y(tǒng)不需要尋找用來存儲(chǔ)新數(shù)據(jù)的空間倦踢,那個(gè)位置永遠(yuǎn)在stack的頂端 - 在heap上分配空間需要做更多的工作:
- 操作系統(tǒng)首先需要找到一個(gè)足夠大的空間來存放數(shù)據(jù)送滞,然后要做好記錄方便下次分配
- 訪問heap中的數(shù)據(jù)要比訪問stack中的數(shù)據(jù)慢,因?yàn)樾枰ㄟ^指針才能找到heap中的數(shù)據(jù)
- 對(duì)于現(xiàn)代的處理器來說辱挥, 由于緩存的緣故犁嗅,如果指令在內(nèi)存中跳轉(zhuǎn)的次數(shù)越少,那么速度就越快.
- 如果數(shù)據(jù)存放的距離比較近晤碘,那么處理器的處理速度就會(huì)更快一些(stack上)
- 如果數(shù)據(jù)之間的距離比較遠(yuǎn)褂微,那么處理速度就會(huì)慢一些(heap上)
- 在heap上分配大量的空間也是需要時(shí)間的
- 當(dāng)你的代碼調(diào)用函數(shù)時(shí),值被傳入到函數(shù)(也包括指向heap的指針).函數(shù)本地的變量被壓到stack上园爷,當(dāng)函數(shù)結(jié)束后宠蚂,這些值會(huì)從stack上彈出.
所有權(quán)存在的原因
- 所有權(quán)解決的問題
- 跟蹤代碼的哪些部分正在使用heap的哪些數(shù)據(jù)
- 最小化heap上的重復(fù)數(shù)據(jù)量
- 清理heap上未使用的數(shù)據(jù)以避免空間不足
- 一旦你懂得了所有權(quán),那么就不需要經(jīng)常去想stack或heap了
- 但是知道管理heap數(shù)據(jù)是所有權(quán)存在的原因童社,這有助于解釋它為什么會(huì)這樣工作.
所有權(quán)求厕,內(nèi)存與分配
所有權(quán)規(guī)則
- 每個(gè)值都有一個(gè)變量,這個(gè)變量是該值的所有者
- 每個(gè)值同時(shí)只能有一個(gè)所有者
- 當(dāng)所有者超出作用域的時(shí)候扰楼,該值將被刪除.
變量作用域
scope就是程序中一個(gè)項(xiàng)目的有效范圍
-
例子
fn main() { //s 不可以 let s = "hello"; // s可用 //可以對(duì)s進(jìn)行相關(guān)操作 } //s 作用域到此結(jié)束呀癣,s不再可用
string 類型
- String 比那些基礎(chǔ)標(biāo)量數(shù)據(jù)類型更復(fù)雜
- 字符串字面值: 程序里手寫的那些字符串值.它們是不可變的
- Rust還有第二種字符串類型: String
- 在heap上分配,能夠存儲(chǔ)在編譯時(shí)未知數(shù)量的文本
創(chuàng)建String類型的值
- 可以使用from函數(shù)從字符串字面值創(chuàng)建出String類型
let s = String::from("hello");// :: 表示from是String類型下的函數(shù)
這類字符串是可以被修改的
fn main() { let mut s = String::from("Hello"); s.push_str(", world"); pringln!(s); }
-
字符串字面值弦赖,在編譯時(shí)就知道它的內(nèi)容项栏, 其文本內(nèi)容直接被編碼到最終的可執(zhí)行文件里
- 速度快,高效蹬竖,得益于其不可變性
-
String類型沼沈,為了支持可變性流酬, 需要在heap上分配內(nèi)存來保存編譯時(shí)未知的文本內(nèi)容:
- 操作系統(tǒng)必須在運(yùn)行時(shí)來請(qǐng)求內(nèi)存
- 這步通過調(diào)用String::from來實(shí)現(xiàn)
- 當(dāng)用完String之后,需要使用某種方式將內(nèi)存返回給操作系統(tǒng)
- 這步列另,在擁有GC的語言中康吵,GC會(huì)跟蹤并清理不再使用的內(nèi)存
- 沒有GC的語言中,就需要我們?nèi)プR(shí)別內(nèi)存何時(shí)不再使用访递,并調(diào)用代碼將它返回
- 如果忘了晦嵌,那就浪費(fèi)內(nèi)存.
- 如果提前做了,變量就會(huì)非法
- 如果做了兩次拷姿,也是bug. 必須一次分配對(duì)應(yīng)一次釋放
- rust采用了不同的方式: 對(duì)于某個(gè)值來說惭载,當(dāng)擁有它的變量走出作用范圍時(shí),內(nèi)存會(huì)立即自動(dòng)的交換給操作系統(tǒng).
- drop函數(shù)
- 操作系統(tǒng)必須在運(yùn)行時(shí)來請(qǐng)求內(nèi)存
變量和數(shù)據(jù)交互的方式: 移動(dòng) move
-
多個(gè)變量可以與同一個(gè)數(shù)據(jù)使用一種獨(dú)特的方式來交互
let x = 5; let y = x;
整數(shù)是已知且固定大小的簡(jiǎn)單的值响巢, 這兩個(gè)5被壓到了stack中
let s1 = String::from("hello"); let s2 = s1;
一個(gè)String 由3部分組成:
- 一個(gè)指向存放字符串內(nèi)容的內(nèi)存的指針
- 一個(gè)長(zhǎng)度
- 一個(gè)容量
上面這些東西被放在stack上.
存放字符串內(nèi)容的部分在heap上
長(zhǎng)度len, 就是存放字符串內(nèi)容所需的字節(jié)數(shù)
當(dāng)把s1 賦值給S2, String 的數(shù)據(jù)被復(fù)制了一份
在stck上復(fù)制了一份指針描滔, 長(zhǎng)度, 容量
并沒有復(fù)雜指針?biāo)赶虻膆eap上的數(shù)據(jù)
當(dāng)變量離開作用域時(shí)踪古,Rust會(huì)自動(dòng)調(diào)用Drop函數(shù)含长,并將變量使用的heap內(nèi)存釋放.
當(dāng)S1, S2離開作用域時(shí), 它們都會(huì)嘗試釋放相同的內(nèi)存二次釋放(double free) bug
為了保證內(nèi)存安全:Rust沒有嘗試復(fù)制被分配的內(nèi)存
-
Rust讓s1失效
當(dāng)s1離開作用域的時(shí)候伏穆, rust不需要釋放任何東西
-
試試看當(dāng)s2創(chuàng)建后拘泞,再使用s1是什么效果
fn main() { let s1 = String::from("hello"); let s2 = s1; println!("{}", s1); //這里會(huì)有編譯錯(cuò)誤,這里s1已經(jīng)失效了 }
- 淺拷貝(shallow copy)
- 深拷貝(deep copy)
- 你也許會(huì)將復(fù)制枕扫, 指針陪腌, 長(zhǎng)度, 容量視為淺拷貝烟瞧, 但是由于Rust讓s1失效了诗鸭, 所以我們用一個(gè)新的術(shù)語:移動(dòng)move
- 隱藏了一個(gè)設(shè)計(jì)原則,Rust不會(huì)自動(dòng)創(chuàng)建數(shù)據(jù)的深拷貝
- 就運(yùn)行時(shí)性能而言参滴, 任何自動(dòng)賦值的操作都是廉價(jià)的
變量和數(shù)據(jù)交互的方式: 克隆(Clone)
如果真想對(duì)heap上面的String 數(shù)據(jù)進(jìn)行深拷貝强岸, 而不僅僅是stack上的數(shù)據(jù),可以使用clone方法
fn main() {
let s1 = String::from("hello");
let s2 = s1.clone();
println!("{}", s1);
}
Stack上的數(shù)據(jù): 復(fù)制
- Copy trait, 可以勇于想整數(shù)這樣完全放在stack上面的類型
如果一個(gè)類型實(shí)現(xiàn)了Copy這個(gè)trait,那么舊的變量在賦值后仍然可用 - 如果一個(gè)類型或者該類型的一部分實(shí)現(xiàn)了Drop trait,那么Rust不允許讓它再去實(shí)現(xiàn)Copy trait了
一些擁有copy trait的類型
- 任何簡(jiǎn)單標(biāo)量的組合類型都可以是copy的
- 任何需要分配內(nèi)存或某種資源的都不是copy的
- 一些擁有Copy trait的類型
- 所有的整數(shù)類型, 例如u32- bool
- char
- 所有的浮點(diǎn)類型, 例如f64
- tuple 元組砾赔, 如果其所有的字段都是Copy的
(i32, i32) 是
(i32, String) 不是
所有權(quán)與函數(shù)
- 在語義上蝌箍, 將值傳遞給函數(shù)和把值賦給變量是類似的:
- 將值傳遞給函數(shù)將發(fā)生移動(dòng)或復(fù)制
返回值與作用域
- 函數(shù)在返回值的過程中同樣也會(huì)發(fā)生所有權(quán)的轉(zhuǎn)移
fn main() {
let s1 = gives_ownership();
let s2 = String::from("hello");
let s3 = take_and_gives_back(s2);
}
fn gives_ownership() -> String {
let some_string = String::from("hello");
some_string
}
fn takes_and_gives_back(a_string: String) -> String {
a_string
}
- 一個(gè)變量的所有權(quán)總是遵循同樣的模式:
- 把一個(gè)值賦給其它變量時(shí)就會(huì)發(fā)生移動(dòng)
- 當(dāng)一個(gè)包含heap數(shù)據(jù)的變量離開作用域時(shí),它的值就會(huì)被Drop函數(shù)清理过蹂,除非數(shù)據(jù)的所有權(quán)移動(dòng)到另一個(gè)變量上了
如何讓函數(shù)使用某個(gè)值十绑,但是不獲得其所有權(quán)聚至?
fn main() {
let s1 = String::from("hello");
let(s2, len)= calculate_length(s1);
println!("the length of '{}' is {}", s2, len);
}
fn calculate_length(s: String) -> (String, usize) {
let length = s.len();
(s酷勺,length)
}
引用和借用
- 一下例子中參數(shù)的類型是&String而不是String
- &符號(hào)就表示引用:允許你引用某些值而不取得所有權(quán)
fn main() {
let s1 = String::from("hello");
let(s2, len)= calculate_length(s1);
println!("the length of '{}' is {}", s2, len);
}
fn calculate_length(s: &String) -> usize {
s.len()
}
我們把引用作為函數(shù)參數(shù)這個(gè)行為叫做借用
是否可以修改借用的東西?不行
和變量一樣扳躬,引用默認(rèn)也是不可變的
可變引用
可變引用有一個(gè)重要的限制: 在特定作用域內(nèi)脆诉,對(duì)某一塊數(shù)據(jù)甚亭, 只能有一個(gè)可變引用
- 這樣做的好處是可在編譯時(shí)防止數(shù)據(jù)競(jìng)爭(zhēng)
以下三種行為下會(huì)發(fā)生數(shù)據(jù)競(jìng)爭(zhēng): - 兩個(gè)或多個(gè)指針同時(shí)訪問同一個(gè)數(shù)據(jù)
- 至少有一個(gè)指針用于寫入數(shù)據(jù)
- 沒有使用任何機(jī)制來同步對(duì)數(shù)據(jù)的訪問
- 可以通過創(chuàng)建新的作用域, 來允許非同時(shí)的創(chuàng)建多個(gè)可變引用(例子)
- 不可以同時(shí)擁有一個(gè)可變引用和一個(gè)不變的引用
fn main() {
let mut s1 = String::from("hello");
let len = calculate_length(&mut s1);
println!("Then length of '{}' is {}.", s1, len);
}
fn calculate_length(s: &mut String) -> usize {
s.push_str(", world")
s.len()
}
懸空引用Dangling References
- 懸空指針(Dangling Pointer): 一個(gè)指針引用了內(nèi)存中的某個(gè)地址击胜, 而這塊內(nèi)存可能已經(jīng)釋放并分配給其它人使用了.
- 在Rust里亏狰, 編譯器可保證引用永遠(yuǎn)都不是懸空引用:
- 如果你引用了某些數(shù)據(jù), 編譯器將保證在引用離開作用域之前數(shù)據(jù)不會(huì)離開作用域
fn main() { let r = dangle(); } fn dangle() -> &String { // 這里編譯器會(huì)報(bào)錯(cuò)偶摔,因?yàn)閟出了此作用域?qū)?huì)被是放暇唾,而返回值是一個(gè)指向已經(jīng)被釋放區(qū)域的指針,這會(huì)導(dǎo)致問題辰斋,而rust在編譯期就杜絕了這種錯(cuò)誤. let s = String::from("hello"); &s }