在寒假期间简单对Rust的数据类型和语法有了一个粗浅的了解之后,卡在了Ownership这个不同于其他编程语言的内存管理概念上。由于很少有实践,学习Rust就暂时搁置了下来,而在GPT-4和Cursor推出以后,再次燃起了一些兴趣。本文便是用于记录在AI助教的帮助下学习Rust语言的过程。

入门扫盲——基础编程概念

在有其他语言的基础上,理解起来不会太费劲的概念。但是也需要注意不同语言之间的区别。

  • 变量:一个默认不会变的量,因此它如果会变,请定义时加上mut
    • 不可变变量是不可以二次赋值的,但是可以重新let x定义,此时第一个值会被隐藏。通常这一点可以应用在不同作用域。
    • 同时,与使用mut相比,重新赋值还可以改变变量的数据类型(因为本质上是定义了一个同名的新变量)。
    • 与不可变变量相比,常量更加不可变,并且在定义时需要显式定义数据类型。
  • 数据类型:分为两个子类scalar标量和compound复合类型
    • 整型:与大部分静态语言相同。不过出现整形溢出时,debug模式编译器会报Panic而release模式会默认补码操作,但是这种wrapping的行为不太好
    • 浮点型:f32f64(default),分别是单精度与双精度
    • 布尔型:truefalse
    • 字符型: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的规则

    1. Rust中的每一个值都有一个所有者
    2. 值在任一时刻有且只有一个所有者
    3. 当所有者(变量)离开作用域,这个值将被丢弃
  • 变量的作用域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:为了保证学习可持续,本系列打算高频更新以督促进度,所以可能学到一部分知识点就发一次,虽然零碎但是起码能保证一点一点学习,可能后面会考虑再整理吧