Rust的核心功能之一 ownership ,运行的程序都需要使用计算机管理内存的方式,比如Java 具备垃圾回收,还有一些语言需要手动去释放内存。而Rust则是第三种方式,通过所有权管理内存,编译器在编译时会根据一些列规则检查,在运行时所有权系统的任务功能都不会减慢程序。
- 所有权和生命周期是 Rust 和其它编程语言的主要区别,也是 Rust 其它知识点的基础。
所有权
栈
栈是程序运行的基础。每当一个函数被调用时,一块连续的内存就会在栈顶被分配出来,这块内存被称为帧(frame)。
栈以放入值的顺序存储并以相反的顺序取出值。这也被称为 后进先出 (last in , first out) 。添加数据的时候加 进栈 (pushing anto the stack) ,而移出数据叫 出栈 (poping off th stack)。
在调用的过程中,一个新的帧会分配足够的空间存储寄存器的上下文。在函数里使用到的通用寄存器会在栈保存一个副本,当这个函数调用结束,通过副本,可以恢复出原本的寄存器的上下文,就像什么都没有经历一样。此外,函数所需要使用到的局部变量,也都会在帧分配的时候被预留出来。
栈的操作时非常快的,这主要得益于它存取数据的方式,数据的存取位置总时在栈顶,而不需要重新寻找一个位置去存放或者读取。另一个属性就是,栈中所有的数据都必须占据已知且固定的大小。
- 但是工作中我们依然要避免大量数据存放栈中,避免栈溢出(stack overfow),运行程序调用栈超出了系统运行的最大空间,就无法创建新的帧。就会出现溢出现象。
堆
在编译时,一切无法确定大小或者大小可以改变的数据,都无法安全地放在栈上,最好放在堆上。
- 堆主要存放大小未知,可能大小变化的数据,堆事缺乏组织的。当向堆中存放数据时,要申请一个空间,操作系统在堆的某处足够大的空间,把它标记为已经使用。并返回一个指针(pointer),这个过程叫做堆上分配内存(allocating on the heap)。
- 访问堆上的数据比访问栈上的数据慢,因为必须通过指针来访问。现代处理器在内存中跳转越少就越快(缓存)。当代码调用一个函数时,传递给函数的值(包括可能指向堆上数据的指针)和函数的局部变量被压入栈中。当函数结束时,这些值被移出栈。
案例一
1 | fn say(name : String){ |
- 字符串的数据结构,在编译器是不确定的,运行期才代码才知道大小。比如
tomcat
和jetty
,当say方法执行的时候才知道参数的具体长度。
案例二
1 | let mut arr = Vec::new(); |
- 列表初始化长度是4,而不是2。在堆上内存分配会使用libc提供的mallo函数。系统调用的代价是昂贵的,所以我们要避免频繁地 malloc()。
内存泄漏
如果手工管理堆内存的话,堆上内存分配后忘记释放,就会造成内存泄漏。一旦有内存泄漏,程序运行得越久,就越吃内存,最终会因为占满内存而被操作系统终止运行。
堆越界
如果堆上内存被释放,但栈上指向堆上内存的相应指针没有被清空,就有可能发生使用已释放内存(use after free)的情况。
GC垃圾回收处理
- 比如Java采用垃圾回收(GC),来自动管理内存,定期标记(mark)找出不在被引用的对象,然后清理(sweep)掉。
- 但是GC是不确定的,可能引起STW(Stop The World)。
ARC自动引用计数处理方式
- 在编译时,它为每个函数插入 retain/release 语句来自动维护堆上对象的引用计数,当引用计数为零的时候,release 语句就释放对象。
栈与堆
- 栈上存放的数据是静态的,固定大小,固定生命周期;堆上存放的数据是动态的,不固定大小,不固定生命周期。
所有权规则
- Rust中每一个值都有一个被称为其所有者的变量。
- 值有且只有一个所有者。当所有者(变量)离开作用域,这个值将被丢弃。
作用域
变量作用域
1 | { // 作用域开启, var无效,未声明。 |
当变量
var
离开作用域的时候,Rust会调用一个特殊的函数drop
自动处理。在 C++ 中,这种 item 在生命周期结束时释放资源的模式有时被称作 资源获取即初始化(*Resource Acquisition Is Initialization (RAII)*)。
内存分配
定义某个变量且赋值,在编译期间就知道了具体内容。
案例一
1 | let x = 5; |
- 将
x
的值赋给y
,这时候有了两个变量且值都等于5。此时栈中会存放两个5。
案例二
1 | fn main() { |
- Console
1 | --> src/main.rs:5:28 |
- 编译器会提示两个错误,将s1 赋值给 s2 后 s1 就不能被访问了。
- 如果还想让s1 被访问就需要 clone复制一份堆数据出来。
1 | fn main() { |
- Console
1 | Hello, world! |
Copy
Rust 有一个叫做
Copy
trait 的特殊注解,可以用在类似整型这样的存储在栈上的类型上
- 所有整数类型,比如 u32。
- 布尔类型,bool,它的值是 true 和 false。
所有浮点数类型,比如 f64。 - 字符类型,char。
- Tuple元组,当且仅当其包含的类型也都是 Copy 的时候。比如,(i32, i32) 是 Copy 的,但 (i32, String) 就不是。
所有权与函数
将值传递给函数在语义上与给变量赋值相似。向函数传递值可能会移动或者复制,就像赋值语句一样。
案例
1 |
|