![bookcover|200](https://img-blog.csdnimg.cn/img_convert/c0c6b4248a79635f5bf63261305fccbc.jpeg)
《Rust权威指南》
本书由 Rust 核心开发团队编写而成,由浅入深地探讨了 Rust 语言的方方面面。从学习函数、选择数据结构及绑定变量入手,逐步介绍所有权、trait、生命周期、安全保证等高级概念,模式匹配、错误处理、包管理、函数式特性、并发机制等实用工具,以及两个完整的项目开发实战案例。 作为开源的系统级编程语言,Rust 可以帮助你编写出更为快速且更为可靠的软件,在给予开发者底层控制能力的同时,通过深思熟虑的工程设计避免了传统语言带来的诸多麻烦。 本书被视为 Rust 开发工作的必读书目,适合所有希望评估、入门、提高和研究Rust语言的软件开发人员阅读。
[!tip]- 作者简介
Steve Klabnik,Rust文档团队负责人,Rust核心开发者之一,Rust布道者及高产的开源贡献者,此前致力于Ruby等项目的开发。
Carol Nichols,Rust核心团队成员,i32、LLC联合构建者,Rust Belt Rust会议组织者。
毛靖凯,游戏设计师,一直专注于游戏领域研发,曾负责设计和维护了多个商业游戏的基础框架。业余时间活跃于Rust开源社区,并尝试使用Rust来解决游戏领域中的诸多问题。
唐刚,资深开发者,Rustcc社区创始人和维护者之一。目前就职于Cdot Network。使用Rust从事区块链共识协议的开发工作。
沙渺,嵌入式开发者,国内Rust语言社区和Raspberry Pi(树莓派)开发社区早期参与者。负责维护多个RISC-V架构硬件平台的基础函数库。
🗒️我的笔记
Rust简介
主要是看视频 https://www.bilibili.com/video/BV1hp4y1k7SV?p=2&spm_id_from=pageDriver
Rust 特别擅长的领域:
- 高性能 Web Service
- WebAssembly
- 命令行工具
- 网络编程
- 嵌入式设备
- 系统编程
Rust优点:
install
https://www.rust-lang.org/
安装 in mac
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
更新:
rustup update
卸载Rust
rustup self uninstall
验证安装:
rustc --version
查看本地文档:
rustup doc
hello world
rustc main.rs
编译
Rust的缩进是4个空格而不是tab
println! 是个宏。函数是没有!的。
rustc 只适合简单的Rust程序。
hello Cargo
复杂的程序得用Cargo。
Cargo是Rust的构建系统和包管理工具。
创建项目 cargo new xxx
cargo build
编译构建项目。会生成target
目录,可执行文件就在里面。
cargo run
会编译 并运行项目。
cargo check
检查项目,比build快很多。
为发布构建,得加参数--release
cargo build --release
编译时会进行优化,编译的更慢,生成的执行文件运行的更快。
猜数游戏
use std::io;
fn main() {
println!("猜数!");
println!("测试一个数");
let mut guess = String::new();
io::stdin().read_line(&mut guess).expect("无法读取行");
println!("你猜测的数是:{}", guess);
}
rust中库不叫lib, 而是叫crate.
为了生成随机数,得引用一个叫rand的crate。
直接在Cargo.toml
里添加即可。
[dependencies]
rand = "0.3.14"
版本依赖会存到Cargo.lock
中,更新版本时也会更新Cargo.lock
trait 类似于其他语言的接口。
match guess.cmp(&secret_number) {
Ordering::Less=>println!("Too small!"),
Ordering::Greater => println!("Too big!"),
Ordering::Equal => println!("You win"),
}
match 类似switch
loop 引入无限循环。break 退出循环。
如果要处理异常,而不是直接panic, 就需要去掉expect
, 改成match
自己匹配了。
完整的代码:
use std::cmp::Ordering;
use std::io;
use rand::Rng;
fn main() {
println!("猜数!");
let secret_number = rand::thread_rng().gen_range(1,101);
loop {
println!("测试一个数");
let mut guess = String::new();
io::stdin().read_line(&mut guess).expect("无法读取行");
println!("你猜测的数是:{}", guess);
let guess:u32 = match guess.trim().parse() {
Ok(num) => num,
Err(_) => continue,
};
match guess.cmp(&secret_number) {
Ordering::Less=>println!("Too small!"),
Ordering::Greater => println!("Too big!"),
Ordering::Equal => {println!("You win");break;},
}
}
}
通用的编程概念
变量与可变性。
默认let
声明的是 不可变的。可以指定变量类型,也可自动推导。
要指明可变需要在变量前加mut
修饰符。
- 常量与不可变的变量不一样
- 得用
const
关键词声明,不能用mut
修饰符。 - 需要标注类型
- 常量可以在任何作用域内进行声明,包括全局作用域
- 常量只能绑定到常量表达式,无法绑定到函数的调用结果或运行才能计算的。(必须编译期间确定值)
- 在程序运行期间,常量在其作用域内一直有效
- 命名规范,全大写,用
_
分隔。 如 MAX_POINT
Shadowing(隐藏)
可以声明同名变量 覆盖他。类型可以不一样。一般是类型转换时这么做。
数据类型
标量类型:一个标量类型代表一个单个的值
- 整数类型 i8,i16,i32,i64,i128,u8,u16,u32,u64,u128. arch和架构相关。开发模式会检查溢出。发布模式不会检查。
- 浮点类型 f32,f64(默认类型)
- 布尔类型(bool) true/false
- 字符类型(char) 4字节大小。
复合类型: 将多个值放在一个类型里。
Rust提供两种: 元组(长度固定,元素可以是多种类型)、数组(长度固定,元素类型统一)。
Tuple示例
fn main() {
let tup: (i32, f64, u8) = (500, 6.4, 1);
let (x,y,z) = tup;
println!("{},{},{}", x,y,z);
println!("{},{},{}", tup.0,tup.1,tup.2);
}
数组示例(实际用Vector更多)
数组是分配在栈上的连续内存。
索引越界时 编译会通过,运行时会panic。
申明方式有3种:
- 字面量
let a = [1, 2, 3, 4, 5];
- 指明类型和长度
let a: [i32; 5] = [1, 2, 3, 4, 5];
- 重复的内容
let a = [3; 5];
// [3,3,3,3,3]
函数
关键词是fn
, 命名规范为snake case。 入口为main
.
参数:
- parameters, 形参,定义时的参数
- arguments, 实参。实际传进来的值。
函数签名必须声明类型。
函数体中的语句与表达式
- 函数体由一系列语句组成,可选的由一个表达式结束
- Rust是一个基于表达式的语言
- 语句是执行一些动作的指令
- 表达式会计算产生一个值
- 函数的定义也是语句
- 语句不返回值,所以不可以使用let将一个语句赋值给变量。
函数的返回值:
- 需要声明(-> 后面),不能命名
- 返回值是最有一个表达式的值
- 如果想提前返回,使用return , 并指定一个值
注意,表达式不要加’;'号,否则变成了语句。
分支语句
if
每种情况都叫arm
, 必须是bool
类型。 按顺序执行。
不用三元运算符。 let number = if condistion { 5 } else {6 };
这种写法就可以了。
match
类似于其他语言的switch, 处理多分支的。
loop
反复运行。无限循环。 loop
快里最后一个表达式可以返回值。
while
条件循环。
for
循环遍历集合。(由于其安全、简介。是用的最多的)
fn main() {
let a = [10, 20, 30, 40, 50];
for element in a {
println!("the value is: {}", element);
}
}
Range
协助循环。
fn main() {
for number in (1..4).rev() {
println!("{}!", number);
}
println!("LIFTOFF!!!");
}
所有权
什么是所有权
Rust的核心机制。
其他语言要么是手动内存管理,要么是gc 自动管理。
Rust采用了第三种方式
- 内存是通过一个所有权系统来管理的,其中包含一组编译器在编译时检查的规则。
- 当程序运行时,所有权特性不会减慢程序的运行速度。
栈内存 vs 堆内存
所有存储在Stack上的数据必须拥有已知的固定的大小。
- 编译时大小未知的数据或运行时大小 可能发生变化的数据 必须存放在heap上。
使用栈内存 性能更高。因为不需要分配内存( 查找可用空间 需要时间)。访问也更快。
所有权解决的问题:
- 跟踪代码的哪些部分正在使用heap的哪些数据。
- 最小化heap上的重复数据量
- 清理heap上未使用的数据以避免空间不足。
所有权规则
- 每个值都有一个变量,这个变量是该值的所有者
- 每个值同时只能有一个所有者
- 当所有者超出作用域时,该值将被删除。
为什么String类型的值可以修改,而字符串字面值却不能修改。
因为他们处理内存的方式不同。
字符串字面值,在编译时就知道它的内容了,其文本内容直接被硬编码到最终的可执行文件里
String类型,为了支持可变性,需要在heap上分配内存来保存编译时未知的文本内容:
- 操作系统必须在运行时来申请内存。
- 当用完String之后,需要使用某种方式将内存返回给操作系统。(在Rust中,对于某个值来说,当拥有它的变量走出作用域范围时,内存会立即自动的交还给操作系统)
drop
函数。离开作用域范围时自动调用。
变量与数据交互的方式:
- 移动(move)
- 多个变量可以与同一个数据使用一种独特的方式来交互。
let x=5;let y=x;
let s1 = String::from("hello");
let s2 = s1;// 这里 就是移动。。所有权从s1移动到了s2
println!("{}, world!", s1); // 编译通不过。s1被废弃掉了。否则会出现两次释放内存的问题。
![[Pasted image 20220530235851.png]]
2. 克隆(clone)
let s1 = String::from("hello");
let s2 = s1.clone();
println!("s1 = {}, s2 = {}", s1, s2);
![[Pasted image 20220531000207.png]]
Stack上的数据:复制
由于Stack上复制数据没啥性能消耗。所以不存在heap上的废弃问题。
Copy
trait, 可以用于像整数这样完全存放在stack上面的类型(就是大小很明显的值)
如果一个类型实现了Copy
这个trait, 那么旧的变量在赋值后仍然可用。
如果一个类型或者该类型的一部分实现了Drop
trait, 那么Rust不允许它再去实现Copy
trait了
一些拥有Copy trait的类型
- 任何简单标量的组合类型都可以是Copy的
- 任何需要分配内存或某种资源的都不是Copy的
- 一些拥有Copy trait的类型
- 所有的整数类型,如u32
- bool
- float32, float64
- char
- Tuple, 如果其所有内容都是Copy的
所有权和函数
- 在语义上,将值传递给函数和把值赋值给变量是类似的:
- 返回值与作用域
- 一个变量的所有权总是遵循同样的模式:
- 把一个值赋给其他变量时就会发生移动
- 当一个包含heap数据的 变量离开作用域时,它的值就会被drop函数清理,除非数据的所有权移动到另一个变量上了
引用与借用
&
表示引用: 允许你引用某些值 而 不取得其所有权
![[Pasted image 20220602223830.png]]
如图。s就是s1的引用。
fn main() {
let s1 = String::from("hello");
let len = calculate_length(&s1);
println!("The length of '{}' is {}.", s1, len);
}
fn calculate_length(s: &String) -> usize {
s.len()
}
我们把引用作为函数参数这个行为叫做借用。
不能修改借用的东西
和变量一样,引用默认是不可变的。
加上mut
可以把引用改成可变引用,这样就可以修改了。
不过在特定的作用域内,可变引用只能有一个。
以下三种行为会发生数据竞争:
- 两个或多个指针同事访问同一个数据。
- 至少有一个指针用于写入数据。
- 没有使用任何机制来同步对数据的访问。
可以通过创建新的作用域,来允许非同时的创建多个可变引用。
另一个限制:
不可以同时拥有一个可变引用和一个不变的引用
[!quote]- 悬空引用
悬空指针:一个指针引用了内存中的某个地址,而这块内存可能已经释放并分配给其他人使用了。
在Rust里,编译器可保证引用永远都不是悬空引用;
[!quote]- 引用的规则
在任何给定的时刻,只能满足下列条件之一;
一个可变的引用
任意数量的不可变的引用。
引用必须一致有效
切片
Rust 的另外一种不持有所有权的数据类型: 切片
[!quote]- 字符串切片
字符串切片就是指向字符串中一部分内容的引用。
示例: &a[1..5]
注意:
- 字符串切片的范围索引必须发生在有效的UTF-8字符边界内。
- 如果尝试从一个多字节的字符串中创建字符串切片,程序会报错并退出。
fn main() {
let mut s = String::from("hello world");
let word = first_word(&s);
s.clear();
println!("the first word is: {}", word);
}
fn first_word(s: &str) -> &str {
let bytes = s.as_bytes();
for (i, &item) in bytes.iter().enumerate() {
if item == b' ' {
return &s[0..i];
}
}
&s[..]
}
使用structs结构化相关数据
定义和实例化结构体
例子
struct User {
active: bool,
username: String,
email: String,
sign_in_count: u64,
}
fn main() {
let mut user1 = User {
email: String::from("someone@example.com"),
username: String::from("someusername123"),
active: true,
sign_in_count: 1,
};
user1.email = String::from("anotheremail@example.com");
}
注意: 一旦struct的实例是可变的,那么实例中所有的字段都是可变的
[!quote]- Tuple struct
可定义类似tupe的struct, 叫做tuple struct
整体有名字,但是里面的元素没有名字。
struct Color(i32, i32, i32);
struct Point(i32, i32, i32);
fn main() {
let black = Color(0, 0, 0);
let origin = Point(0, 0, 0);
}
[!quote]- Unit-Like Struct(没任何字段)
适用于需要在某个类型上实现某个trait, 但是在里面又没有想要存储的数据。
struct数据的所有权
只要struct实例是有效的,那么里面的字段数据也是有效的。
struct里也可以存放引用,需要生命周期
生命周期保证只要struct实例是有效的,那么里面的引用也是有效的。
格式化:
std::fmt::Display {}
std::fmt::Debug
{:?} 单行打印
{:#?} 美化打印
#[derive(Debug)]
struct方法
#[derive(Debug)]
struct Rectangle {
width: u32,
height: u32,
}
impl Rectangle {
fn area(&self) -> u32 {
self.width * self.height
}
}
fn main() {
let rect1 = Rectangle {
width: 30,
height: 50,
};
println!(
"The area of the rectangle is {} square pixels.",
rect1.area()
);
}
方法与函数的不同之处:
- 方法是在struct(或enum、trait对象)的上下文中定义
- 第一个参数是self, 表示方法被调用的struct实例
[!quote]- 关联函数
可以再impl块里定义不把self作为第一个参数的函数,他们叫关联函数(不是方法)
示例:
#[derive(Debug)]
struct Rectangle {
width: u32,
height: u32,
}
impl Rectangle {
fn square(size: u32) -> Rectangle {
Rectangle {
width: size,
height: size,
}
}
}
fn main() {
let sq = Rectangle::square(3);
}
枚举与模式匹配
定义枚举
enum IpAddrKind {
V4,
V6,
}
fn main() {
let four = IpAddrKind::V4;
let six = IpAddrKind::V6;
route(IpAddrKind::V4);
route(IpAddrKind::V6);
}
fn route(ip_kind: IpAddrKind) {}
将数据附加到枚举的变体中
enum IpAddrKind {
V4(String),
V6(String),
}
优点:
不需要额外使用struct
每个变体可以拥有不同的类型以及关联的数据量。
enum IpAddrKind {
V4(u8,u8,u8,u8),
V6(String),
}
[!quote]- Option枚举
定义于标准库中
在Prelude(预导入模块)中
描述了: 某个值可能存在(某种类型) 或不存在的情况
在Rust中没有Null
这个概念,而是用Option表达的。
enum Option<T> {
None,
Some(T),
}
[!tip]- Option<T>比Null好在哪?
Option<T>和T是不同的类型,不可以把Option<T>直接当做T
若想使用Option<T>
中的T, 必须将她转化为T
match控制流
[!quote]- 强大的控制流运算符-match
允许一个只与一系列模式进行匹配,并执行匹配的模式对应的代码
模式可以是字面值、变量名、通配符…
enum Coin {
Penny,
Nickel,
Dime,
Quarter,
}
fn value_in_cents(coin: Coin) -> u8 {
match coin {
Coin::Penny => 1,
Coin::Nickel => 5,
Coin::Dime => 10,
Coin::Quarter => 25,
}
}
fn main() {}
[!quote]- 绑定值的模式
匹配的分支可以绑定到被匹配对象的部分值。
因此,可以从enum变体中提取值。
匹配Option(T)
#[derive(Debug)]
enum UsState {
Alabama,
Alaska,
}
enum Coin {
Penny,
Nickel,
Dime,
Quarter(UsState),
}
fn value_in_cents(coin: Coin) -> u8 {
match coin {
Coin::Penny => 1,
Coin::Nickel => 5,
Coin::Dime => 10,
Coin::Quarter(state) => {
println!("State quarter from {:?}!", state);
25
}
}
}
fn main() {
value_in_cents(Coin::Quarter(UsState::Alaska));
}
[!tip]- match匹配必须穷举所有可能
用_ 表示没列举的可能性。
if let 控制流
用于替代 match
只关心一种匹配的情况。
fn main() {
let config_max = Some(3u8);
match config_max {
Some(max) => println!("The maximum is configured to be {}", max),
_ => (),
}
}
改成if let
fn main() {
let config_max = Some(3u8);
if let Some(max) = config_max {
println!("The maximum is configured to be {}", max);
}
}
代码组织
代码组织主要包括:
- 哪些细节可以暴露,哪些细节是私有的
- 作用域内哪些名称有效
[!quote]- 模块系统
- Package(包): Cargo的特性,让你构建、测试、共享crate
- Crate(单元包): 一个模块树,它可以产生一个library或可执行文件
- Module(模块)、use: 让你控制代码的组织、作用域、私有路径
- Path(路径): 为struct、function或module等项命名的方式
Package 和 Crate
- Crate的类型
- Crate Root:
- 是源代码文件
- Rust编译器从这开始,组成你的Crate的根Module
- 一个Packge:
- 包含一个Cargo.toml, 它描述了如何构建这些Crates
- 只能包含0-1个library crate
- 可以包含任意数量的binary crate
- 必须至少包含一个crate(library或binary)
Cargo的惯例
- src/main.ts:
- binary crate的crate root
- crate 名与package名相同
- src/lib.ts:
- package包含一个library crate
- library crate的crate root
- crate 名与package名相同
- 一个Package可以同时包含src/main.rs 和 src/lib.rs
- 一个binary crate, 一个libary crate
- 名称与package名相同
- 一个Package可以有多个binary crate:
- 文件放在src/bin
- 每个文件可以是单独的binary crate
[!quote]- Crate的作用
将相关的功能组合到一个作用域内,便于在项目间进行共享-防止冲突
定义module来控制作用域和私有性
- Module
- 在一个crate内,将代码进行分组
- 增加代码可读性,易于复用
- 控制项目(item)的私有性。public、private
- 建立module:
- mod 关键字
- 可嵌套
- 可包含其它项(struct、enum、常量、trait、函数等)的定义
路径 Path
- 为了在Rust的模块中找到某个条目,需要使用路径
- 路径的两种形式
- 绝对路径: 从crate root开始,使用crate名或字面值crate
- 相对路径:从当前模块开始,使用self,super或当前模块的标识符
- 路径至少由一个标识符组成,标识符之间用
::
。
私有边界
- 模块不仅可以组织代码,还可以定义私有边界。
- 如果想把函数或struct等设为私有,可以将它放到某个模块中。
- Rust中所有条目默认都是私有的
- 父级模块无法访问子模块中的私有条目
- 子模块里可以使用所有祖先模块中的条目
[!quote]- pub
加上pub就变成公有了。
super
引用上级
use关键词
可以使用use
关键字将路径导入导作用域内
可以使用相对路径/绝对路径
use的习惯用法:
- 函数:引入到父级
- struct, enum, 其他: 指定完整路径
- 同名条目: 指定到父级
可用 as
取别名。
use
引入的模块默认是私有的。外部模块无法访问。
加上pub
之后 外部模块也就可以访问了。
使用外部包
- 在Cargo.toml里添加依赖的package
- Cargo会从https://crates.io/ 下载包及其依赖项
- use引入
标准库也被当做外部包。由于内置了。不需要修改配置文件。但使用的时候还得用use
更多参考 https://download.csdn.net/download/goodparty/86824159?spm=1001.2014.3001.5503
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)