rust 教程:https://course.rs/advance/intro.html
安装 rust 环境:
1 | curl --proto '=https' --tlsv1.2 https://sh.rustup.rs -sSf | sh |
使用 vscode,安装插件,语法补全等等:rust-analyzer
;好看的 vscode 主题:One Dark Pro
1 | rust 字符类型:使用单引号 |
内存安全之所有权和借用
如何从内存中申请空间来存放程序的运行内容,如何在不需要的时候释放这些空间,是编程语言设计的难点。有三种方式:
- 垃圾回收机制(GC):在程序运行时不断寻找不再使用的内存,比如:Java、Go
- 手动管理内存的分配与释放,比如 C++
- 通过所有权来管理内存,编译器在编译时会根据一系列规则进行检查,比如 rust
关于所有权的规则:
- rust 中每一个值都被一个变量所拥有,该变量被称为值的所有者
- 一个值同时只能被一个变量所拥有,或者说一个值只能拥有一个所有者
- 当所有者(变量)离开作用域范围时,这个值将被丢弃
举一个例子,String 类型是一个复杂类型,由存储在栈中的堆指针、字符串长度、字符串容量共同组成。假设有 String 类型的对象 s1 和 s2。当 s1 赋给 s2 后,rust 认为 s1 不再有效,因此也无须在 s1 离开作用域后 drop 任何东西,这就是把所有权从 s1 转移给了 s2,s1 在被赋予 s2 后就马上失效了。如下:
1 | let s1 = String::from("hello"); |
如果需要深拷贝,可以使用 clone
的方法:
1 | let s1 = String::from("hello"); |
在 rust 中一般看到的浅拷贝,如下:
1 | let x = 5 |
这种浅拷贝一般是在栈上的拷贝,这种不会有所有权的问题。因为像整形这样的基本类型在编译时是已知大小的,会被存储在栈上,所以拷贝其实际的值是快速的。这意味着没有理由在创建变量 y 后使 x 无效。
不可变引用。使用引用传递参数:
1 | fn calculate_len(s: &String) -> usize { |
&
符号即是引用,允许我们使用值,但是没有获取到所有权。如上,使用 &s1
语法,我们创建了一个指向 s1 的引用,但是并不拥有它。因为并不拥有这个值,当引用离开作用域后,其指向的值也不会被丢弃。还有引用指向的值默认是不可变的。
可见引用。
1 | fn change_mut(some_string: &mut String) { |
如上,可以先声明 s 是可变类型,然后创建一个可变的引用 &mut s
和接受可变引用参数 some_thing: &mut String
的函数。
不过可变引用有一个很大的限制,同一作用域,特定数据只能有一个可变引用。
1 | let mut s = String::from("hello"); |
数据竞争会导致未定义的行为,这种行为很可能超出我们的预期,难以在运行时追踪,并且难以诊断和修复。而 Rust 避免了这种情况的发生,因为它甚至不会编译存在数据竞争的代码。
悬垂引用(Dangling References),意思是指针指向某个值后,这个值被释放掉了,而指针仍然存在,其指向的内存可能不存在任何值或已被其他变量重新使用。在 rust 中,如果有悬垂引用,rust 会抛出一个编译时错误。
1 | fn main() { |
总的来说,借用规则如下:
- 同一时刻只能拥有要么一个可变引用,要么任意多个不可变引用
- 引用必须总是有效的
使用 #![allow(unused_variables)]
属性标记,告诉编译器忽略未使用的变量。
切片,如下,切片是一个左闭右开的区间。
1 | let s = String::from("hello"); |
rust 中变量在离开作用域后,就会自动释放其占用的内存。
元组,是由多种类型组合到一起形成的,因此他是复合类型,元祖的长度是固定的,元祖中元素的顺序也是固定的。
1 | let x: (i32, f64, u8) = (500, 6.4, 1); |
结构体:
1 | struct User { |
初始化实例时,每个字段都需要进行初始化;初始化时的字段顺序不需要和结构体定义时的顺序一致。
打印结构体,使用 #[derive(Debug)]
作用于结构体,打印时使用 {:#?}
或 {:?}
可以结构化打印调试结构体。
1 | #[derive(Debug)] |
dbg!
输出到标准错误 stderr,而使用 println!
输出到标准输出 stdout。
1 | // 枚举 |
在 rust 中,数组有两种。array 速度很快但是长度固定。vector 是可动态增长但是有性能损耗。
对于数组越界访问时,程序会直接崩溃。这种就是 rust 的安全特性之一。在很多系统编程语言中,并不会检查数组越界问题,会导致在程序逻辑上出现大问题,而且这种问题会非常难以检查。
模式匹配,类型 switch 语句。在 rust 语言中,使用 match 来进行匹配。
1 | enum Directory { |
- match 的匹配必须要穷举出所有可能,因此使用
_
来代表未列出的所有可能性 - match 的每一个分支都必须是一个表达式,且所有分支的表达式最终返回值的类型必须相同
X | Y
:类似逻辑运算符或,代表 X 和 Y 满足一个即可
rust 的对象定义和方法定义是分离的,这种数据和使用分离的方式,会给予使用者极高的灵活度。
1 | struct Rectangle { |
在 area
的签名中,我们使用 &self
替代 rectangle: &Rectangle
,&self
其实是 self: &Self
的简写(注意大小写)。在一个 impl
块内,Self
指代被实现方法的结构体类型,self
指代此类型的实例,换句话说,self
指代的是 Rectangle
结构体实例,这样的写法会让我们的代码简洁很多,而且非常便于理解:我们为哪个结构体实现方法,那么 self
就是指代哪个结构体的实例。
self
依然有所有权的概念:
self
表示Rectangle
的所有权转移到该方法中,这种形式用的较少&self
表示该方法对Rectangle
的不可变借用&mut self
表示可变借用
泛型编程,
1 | struct Point<T, U> { |
定义特征,
1 | pub trait Summary { |