在寒假期间简单对Rust的数据类型和语法有了一个粗浅的了解之后,卡在了Ownership这个不同于其他编程语言的内存管理概念上。由于很少有实践,学习Rust就暂时搁置了下来,而在GPT-4和Cursor推出以后,再次燃起了一些兴趣。本文便是用于记录在AI助教的帮助下学习Rust语言的过程。
入门扫盲——基础编程概念
在有其他语言的基础上,理解起来不会太费劲的概念。但是也需要注意不同语言之间的区别。
- 变量:一个默认不会变的量,因此它如果会变,请定义时加上
mut
。- 不可变变量是不可以二次赋值的,但是可以重新
let x
定义,此时第一个值会被隐藏。通常这一点可以应用在不同作用域。 - 同时,与使用
mut
相比,重新赋值还可以改变变量的数据类型(因为本质上是定义了一个同名的新变量)。 - 与不可变变量相比,常量更加不可变,并且在定义时需要显式定义数据类型。
- 不可变变量是不可以二次赋值的,但是可以重新
- 数据类型:分为两个子类
scalar
标量和compound
复合类型- 整型:与大部分静态语言相同。不过出现整形溢出时,debug模式编译器会报Panic而release模式会默认补码操作,但是这种wrapping的行为不太好
- 浮点型:
f32
与f64
(default),分别是单精度与双精度 - 布尔型:
true
和false
- 字符型:
char
,4字节的unicode标量值,用单引号包围;与之相对的双引号用来包围字符串字面量 - 元组:
tuple
,一个非常熟悉的概念,元组的长度是固定的 - 数组:
array
,同样熟悉的概念,但是在Rust中,数组成员类型必须相同并且长度也是固定的。 - Vector:尽管Vector并非原生复合类型,但是作为标准库提供的一种可以改变长度的类似数组的复合类型,还是在这里提一下,后续应该会深入学习
- 函数:
- 定义:使用关键字
fn
定义,函数定义的顺序无所谓,只要调用时处于调用处可见的作用域中即可 - 参数:
parameters
是函数签名的一部分,调用时用来传入arguments
。在Rust中,定义参数时必须提供类型注解。 - 语句和表达式:前者执行操作但不返回值,后者计算并返回值。Rust是一门基于表达式的语言。函数调用是一个表达式。宏调用是一个表达式。用大括号创建的一个新的块作用域也是一个表达式
- 函数返回值:不需要对返回值命名,但是需要提供类型注解。并且隐式返回最后的表达式
- 定义:使用关键字
- 控制流:
- if表达式:没什么太多好说的,唯一需要注意的可能是Rust中if表达式的条件必须是bool类型的值,与动态语言会自动转换不同。另外,如果想要在赋值语句中应用if表达式,需要保证不同分支结果的变量类型一致。
- 循环结构:loop和while以及for,都是常见的循环结构,不过Rust中可以定义循环标签,方便break和continue控制循环层。
Ownership所有权
一个与我此前学过的语言迥异的概念,解释起来其实并不复杂,但是很难适应。
Ownership的意义:一种内存管理机制,为了更好的追踪和分配堆上的内存。
Ownership的规则
- Rust中的每一个值都有一个所有者
- 值在任一时刻有且只有一个所有者
- 当所有者(变量)离开作用域,这个值将被丢弃
变量的作用域
scope
:从声明直到作用域结束
通过String理解所有权
String
是第二个字符串类型,用于未知大小的字符串。
2023-03-19 果然到Ownership就会半途而废是吗(
移动
是否可以理解成,当Rust处理堆上内存时,为了避免二次释放,所以浅拷贝不存在了,而是所有权的转移。或者说是发生浅拷贝之后,原变量不再拥有所有权了。 而深拷贝(Clone),永远不会自动创建。
拷贝
Copy是一个trait,不同于Clone,因为处理的对象是栈上的内存,所以并不会带来太多性能损耗。但是不允许自身或任何部分实现了Drop trait的类型使用。Drop是自动在变量离开作用域时调用的函数,用于释放内存。
引用与借用
reference可以理解成永远指向有效值的指针。使用&
表示。
创建引用的行为称之为借用,借用但是并不拥有值的所有权。所以当引用停止时并不需要通过返回值交还所有权。
如果需要修改引用对象的值,需要创建一个可变引用。关于可变引用有一些规则,为了避免数据竞态(果然Rust的设计没有写过很多C是体会不出来的),这里简单罗列一下:
- 不能同时创建两个可变引用
- 可以创建多个不可变引用
- 创建可变引用后,不可以再使用不可变引用
另外,Rust通过生命周期解决了垂悬引用的问题。我们日后再深究
Slice
Pythonista多么熟悉的概念
设计slice是为了解决索引与对象本身不关联的问题(令人大开眼界)
Slice的语法是..
,索引从0开始,可以省略开头和结尾(再次熟悉)
字符串的字面值就是Slice
let s = "Hello, world!";
此处s
的类型居然是&str
,是一个指向二进制程序特定位置的不可变引用
2023-03-20:为了保证学习可持续,本系列打算高频更新以督促进度,所以可能学到一部分知识点就发一次,虽然零碎但是起码能保证一点一点学习,可能后面会考虑再整理吧