rust基础
This is my study note of rust.
Rust是一种系统级编程语言
- 零成本抽象
1
rust几乎没有运行时的抽象,它的抽象过程在编译时就已经执行完成了。
- 所有权系统
1
2
3
4
5所有权系统使得编译器在编译时进行内存管理,而无需运行的垃圾回收(GC)。
什么叫做垃圾回收机制呢(garbage collection)?就是使用动态内存分配时,程序员往往会不小心忘记
释放内存或者多次释放相同的内存,这时候就会导致程序运行错误,垃圾回收机制就是自动检测不再使用的
内存对象将他们释放,主要是检测对象间的引用关系,当一个对象不再被引用时,垃圾回收器会在适当的时候
回收他们的内存。 - 零成本的异常处理
1
rust使用Result和Option类型来处理错误和可选值,这些类型在编译过程中进行检查,不需要额外的运行开销。
- 内敛汇编
1
开发者可以直接在rust中嵌入汇编代码,以获得最佳的性能和对底层硬件的直接访问。
- 零开销的并发
1
提供内置的并发原语,避免常见的并发问题,例如数据竞争和死锁。
什么是cargo
是Rust编程语言的官方构建系统和包管理器。主要提供以下功能:
- 项目初始化
1
使用cargo new可以创建一个新的rust项目,包括基础结构和配置文件。
- 依赖管理
1
在Cargo.toml中指定所需的依赖项以及版本要求,随后Cargo就会自动下载、编译和构建这些依赖项。
- 构建和测试
1
21.Cargo build 使用Cargo build可以构建Rust项目
2.Cargo test 使用Cargo test可以运行项目中的测试,分为单元测试和集成测试 - 运行和调试
1
21.Cargo run 用于运行Rust项目
2.Cargo check 用于快速检查代码中的错误和警告 - 发布和分发
1
Cargo publish用于向Rust项目发布到crates.io,这是Rust社区的官方包仓库。其他开发者就能用Cargo install命令来安装和使用你的包。
基础语法
编译
1 | rustc main.rs |
导入包
1 | 在Cargo.toml中加入[dependenies] 随后加所需的包,如下: |
更新依赖
1 | Cargo update |
语法
use
1
use std::io // 预导入,例子表示导入std::io下的所有内容
println!
1
2
3
4
5println!("hello world");
println!("{}",data); // {}用于打印data
#[derive(Debug)]
println!("{:#?}",结构体变量名); // 打印结构体变量各个参数变量申明
1
2
3
4let 变量名:类型; // 申明不可变变量
let mut 变量名:类型; // 声明变量可变
const 变量名:类型=数字; // 申明常量在其作用域内一直有效
// 可以使用相同的名字声明新的变量,新的变量会隐藏之前声明的同名变量。io::stdin
1
io::stdin().readline(&mut 变量) // 从命令行读取参数并逐行输入到变量中,&表示引用
expect
1
io::stdin().readline(&mut 变量).expect(""); // 若是函数返回err则打印,否则不打印
String
1
2
3
4
5
6
7
8
9String::new(); // 创建String对象
String::from(“”); // 从""中创建String类型
let mut s2 = s1; // 此时s1会被废弃,s1失效,这样就不会导致二次释放
let mut s2 = s1.clone(); // 此时s1和s2均有效
编译时可以确定大小的stack数据可以使用复制操作,如下:
let num = 10;
let num1 = num; // num1和num都有效
注意,由于String类型实现的是move而Stack类型实现的是copy,所以当String做为传入参数时,
除非使用引用,否则原变量会被废弃,stack类型变量当作函数传入参数时,原变量不会被废弃。match
1
2
3
4
5
6
7
8
9
10
11match 变量
{
case1 => 操作1,
case => 操作2,
}
// 类似于case,用变量去匹配case执行相应操作
if let
if let Some(3) = v
{
// 匹配一种情况,match的语法糖
}循环
1
loop{操作} // 可用break跳出循环,continue执行下次循环
整数字面值
1
2
3
4
5Decimal 98_222
Hex 0xff
Octal 0o44
Binary 0b1111_0000
Byte b'A'复合类型
1
2
3
4
5
6
7
8
9
10
111.tuple
let data:(i32,u32, u8) = (10,0,0);
// 访问变量使用data.0、data.1、data.2
let (x,y,z) = data;
// 此时x为data.0, y为data.1,z为data.2
2.数组
let data[i32, num] = [];
// 数组以[类型, 长度]形式表示
let data[3, 5];
// 此时data相当于[3,3,3,3,3]
数组是在Stack上分配的单个块的内存,如果使用索引来访问,超出范围时编译会通过(也可能报错),运行会报错。函数
1
2
3
4
5// 必须指明传入参数的类型
fn 函数名(传参:类型)->返回类型
{
操作
}所有权
1
2
3
4
5
6
7内存通过管理权系统进行管理,其中包含一组编译器在编译时检查规则,在运行时无开销。
编译时大小未知的数据必须放在heap上,已知大小的数据放在Stack上。
所有权规则:
1.每个值都有一个变量,该变量是该值的所有者
2.每个值同时只能有一个所有者
3.所有者超出作用域时该值删除引用
1
2
3
4
5
6
71.只能有一个可变引用,防止数据竞争
2.允许非同时创建多个可变引用
3.不可以拥有一个可变引用和一个不可变引用
4.可以多个不可变引用
悬空引用的概念:
一个指针引用了内存的某个地址,而这个地址已经被释放或者被其他人使用了。切片
1
2
3
4str类型
let name = String::from("Lilei");
let data = name[..] // 此时data为"Lilei"
let data = name[..2] // 此时data为"Lil"结构体
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40// 需要对struct所有字段赋值,一旦有一个实例可变,那所有字段可变
struct User
{
username:String,
active:bool,
}
impl User // 定义User结构体内部函数
{
// 定义关联函数
fn new(s:&String, a:bool)
{
Struct User
{
username:s,
active:a,
};
}
fn solve(&self)->bool
{
self.active
}
}
let data = User
{
username:String::from("xiaoming"),
active:false,
};
// 想基于data实例创建新的实例时,若active参数不变,则:
let data2 = User
{
username:String::from("xiaohong");
..data
}
tuple struct:
Struct Color(i32, i32, i32);
let data = Color(0,0,0);枚举
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20enum IPAddrkind
{
v4(u8, u8, u8, u8),
v6(String),
}
// 为枚举定义方法
impl IPAddrkind
{
fn call(&self)
{
}
}
Option<T>枚举
enum Option<T>
{
Some(T),
None,
}Vector
1
2
3
4
51.创建Vector
let v:Vec<i32> = Vec::new();
let v = vec![1,2,3];
2.插入vector
v.push(1);HashMap
1
2
3
4
5
6
7
8
9
10
11
12
13
14let mut scores:<i32, i32> = HashMap:new() // 创建空HashMap
insert() // 添加数据
collect()创建HashMap:
let mut data:HashMap<_,_> = teams.iter().zip(scores,iter()).collect();
get()函数进行参数获取:
let name = 1;
let score = data.get(&name);
遍历HashMap:
for (k, v) in &data
{
}
entry方法,检查指定的k是否对应一个v,经常和or_insert()一起使用
data.entry(1).or_insert(2);
代码组织
1 | -package(包):Cargo的特性,让你构建、测试、共享crate |
错误处理
1 | 1.可恢复的作用 |
泛型
1 | 1.编写的代码不是最终的代码,而是一种模板,里面有一些“占位符” |
- trait
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
391.类似虚函数,只申明,不实现,实现放在具体的结构中去实现,当然也可以定义,做为默认实现
2.trait只能在本地crate里定义,无法为外部类型实现trait(孤儿规则)
pub trait summary
{
fn summarize(&self) -> String;
}
impl summary for struct名
{
fn summarize(&self) -> String
{
}
}
3.trait可以做为传参限制,即:
pub fn notify(item: impl summary + Display)
{
...
}
// 上述代码代表实现了summary和Display这个trait的item,也可写成如下形式:
pub fn notify<T:summary + Display>(item: T)
{
...
}
4.where语句
pub fn notify<T, U>(item: T, item1:U)
where
T:summary + Display,
U:Clone,
{
...
}
4.覆盖实现
impl<T:summary> ToString for T
{
...
}
// 上述代码代表为所有实现了summary的结构体都实现ToString方法 - 生命周期
1
2
3
4
5
6
7
8
9
10
11fn longest<'a>(x:'a &str, y:'a &str) -> &str
{
...
// 描述多个引用的生命周期之间的关系,不影响生命周期
}
生命周期的引用:
规则一:每个引用类型的参数都有自己的生命周期
规则二:如果只有一个输入生命周期参数,那么该生命周期会被赋给所有的输出生命周期
规则三:如果有多个输入生命周期参数,其中一个是&self或者&mut self,那么self的
生命周期会被赋给所有的输出生命周期参数
'static生命周期:整个程序的持续时间 - 测试
1
2
3
4
5
6
7
8
9
10
11在函数上加#[test]将函数变为测试函数
Cargo test
想要在成功的测试中看到打印的内容: + --show-output
根据函数名进行测试: + 测试的函数名(测试多个函数可以取他们名称的公共部分)
忽略某些测试:代码中加入#[ignore]
assert!宏检查测试结果:
true:测试通过
false:调用panic!,测试失败
#[should_panic(expected = "")] // 恐慌时通过,否则报错,使用expected去匹配
标准错误:
eprintln! - 闭包
1
2
3
4
5
6
7
8
9
10
11
12
13
14
151.是匿名函数
2.保存为变量、做为参数
3.可在一个地方创建闭包,然后在另一个上下文中调用闭包来完成运算
4.从其定义的作用域捕获值
let x = 5;
let expensive_closure = |num:i32|
{
num = x;
};
// 可以从定义的作用域把x的值直接进行使用,会产生内存开销
闭包获取所在环境值的方式:
1.取得所有权:FnOnce
2.可变借用:FnMut
3.不可变借用:Fn
(实现Fn的闭包也实现了FnMut,实现FnMut的闭包也实现了FnOnce) - 迭代器
1
2
3
4
5
6
7
8
9
10
11
12
13迭代器负责:
1.遍历每个项
2.确定序列何时完成
Rust迭代器是懒惰的:
除非调用消费迭代器的方法,否则迭代器本身没有任何效果
方法:
1.iter方法:在不可变引用上创建迭代器
2.into_iter方法:创建迭代器会获得所有权
Iterator trait有些带默认实现的方法:
其中有些方法会调用next方法,实现Iterator trait时必须实现next方法的原因之一
调用next的方法叫做"消耗性适配器",会把迭代器耗尽
.map方法,创建新的迭代器 参数
.filter方法,过滤符合条件的迭代器,创建新的迭代器存储
Cargo操作
- 通过release profile来自定义构建
1
2
3
4
5
6
7
8
9
10
11
12
13-预定义的
-可自定义:使用不同的配置,对编译代码有更多的控制
-每个profile的配置独立于其他的profile
主要两个profile:
dev profile:适用于开发,Cargo build
release profile:适用于发布,Cargo build -release
想要自定义 xxxx profile的配置:
在Cargo.toml里添加[profile.xxxx]区域
[profile.dev]
opt-level = 0
[profile.release]
opt-release = 3 - 发布库
1
2
3
4
5
6
7
8
9发布到crates.io
可以使用pub use来导出内部私有结构,不同的对外公共结构不需要重新组织内部代码结构
发布操作:
1.在crates.io上创建账号同时获得API token
2.运行命令: cargo login [你的API token]
// 通知cargo,你的API token存储在本地~/.cargo/credentials
3.cargo publish
// 需要添加元数据:①name、description(一两句话即可,会出现在crate搜索的结果里)、license、version、author
4.cargo yank从Crates.io撤回版本 cargo yank --vers 1.0.1 - 通过workspaces组织大工程
1
2
3
4共享同一个Cargo.lock和输出文件夹的包
依赖关系:
[dependenies]
依赖包名 = {path = "依赖包路径"} - 安装库
1
2cargo install
// 安装的二进制存放在更目录的bin文件夹下或者$HOME/.cargo/bin - 自定义命令拓展cargo
1
如果$PATH中的某个二进制时cargo-something,你可以像子命令一样运行:cargo something
智能指针
- Box
1
2编译阶段强制代码遵守借用规则
let data = Box::new(5); - Cons List
1
2每个成员由两个元素组成:1.当前项的值 2.下一个元素
let list = Cons(1, Cons(1, Cons(3,Nil))); - Deref Trait 自定义解引用运算符*
1
2
3
4
5
6
7
8
9
10自定义的智能指针需要解引用的话需要实现Deref的defef
impl<T> Deref for MyBox<T>
{
type Target = T;
fn defef(&self) -> &T
{
&self.0
}
} - Drop Trait
1
drop(c);
- Rc
1
2
3
4Rc::clone(&a); // 增加引用计数
Rc::strong_count(&a); // 获得引用计数
防止循环引用:
Weak<T>; - RefCell
1
2
3
4-运行时检查借用规则
borrow方法:返回智能指针Re<T>
-borrow_mut方法
返回智能指针RefMut<T>
无畏并发
- 多线程可导致的问题
1
2竞争状态,以不一致的顺序访问数据或资源
死锁:两个线程彼此等待对方使用完所持有的资源 - thread::spawn函数可以创建新线程
1
2参数:一个闭包
move||{} // move声明后,允许使用其他线程的数据 - Channel
1
2
3
4使用mpsc::channel函数来创建Channel
let(tx, rx) = mpsc::channel();
tx.send(参数).unwrap();
rx.recv().unwrap();
本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 风声向寂!
评论
ValineDisqus