一起养成写作习惯!这是我参与「掘金日新计划 · 4 月更文挑战」的第19天,点击查看活动详情。
变量作用域
所有的编程语言几乎都有这个概念,作用域(Scope)就是一个代码中的一个范围,在这个范围内,你声明的变量是有效的、可用的;在这个范围外,变量就失效、不可用了。
一个最广为人知的作用域就是函数作用域:
- 一个函数内部声明的变量,你在函数外面使用就是错误的,这种一般称之为局部变量
那么还有别的作用域吗?当然有:
- 循环体
- 条件判断体
等等。
甚至有的语言里,你直接hu两个花括号,这两个花括号之间就是一个作用域,花括号之内的变量,出去了就失效,如下rust代码:
fn main(){ // 这就是main函数的作用域
{ // 这也是一个作用域
}
}
大部分语言对于变量作用域的使用,也就到此为止了,就是变量可不可用的问题。但是Rust对于作用域有更多的挖掘。
程序什么时候释放内存
现代的编程语言,对于内存的使用,基本上都是分成 栈和堆两个区域的。
栈的使用主要是为了实现函数调用,没有栈就无法实现函数调用。
一个函数在调用之前,首先要把局部变量全都弄到栈上去,然后再执行逻辑。
当然,执行完了函数之后,栈就会缩下去,释放空间。
这是编译时可以确定的空间,我们就放在栈上,那么编译时无法确定大小的空间呢?比如说,用户输入一个数字,按照这个数字,申请一块内存。
显然这块内存不能放在栈上,这是由于栈的实现必须在编译时确定,这种无法确定的空间,我们就在另一块内存上取用,这就是堆。
对于栈来说,回收很方便,只要在函数结束时,缩回原状态即可。
那么堆呢,流行的有两个解决方案:
- C/C++ 的,靠程序员手动释放
- Java 等语言的,实现一个垃圾回收器,按照一定的算法,自动释放
那么Rust这种是啥?
Rust作用域对于内存的影响
如果一个变量的值,有引用堆上的数据,那么这个变量离开作用域的时候,就会被drop一次,也就是释放一次。
看这个例子:
{ // 这个作用域开始
let s = String::from("hello"); // 从这句开始,s变量进入上面的作用域
} // 这个作用域结束 ,s 内部,堆上的空间被回收
String这个东西是Rust标准库里的一个类型,这个类型是一个可变的字符串类型,真正的内容是存放在堆上的,借用一个官方的图:
左边是栈上,右边是堆上。
这个不重要,重要的是,s这个变量一旦离开了作用域,右边堆上的空间就会被收回。
double free 问题
一个堆上的空间,被回收两次,这就是。
Rust怎么来解决这种问题呢?
答案就是:一个空间在任意时候,只能属于一个变量,这个变量离开作用域的时候,进行一次回收。
看下面的代码:
{
let s1 = String::from("hello");
let s2 = s1;
}
首先,let s2 = s1;,用官方的话来说是一个浅拷贝,也就是说堆上的内容是没有进行拷贝的。用一张官方图来解释此时此刻:
这不是重点,重点是,离开这个花括号作用域的时候,由于两个变量同时失效,那么此时就会发生所谓的。
为了解决这种问题,Rust规定,s2失效的时候,执行一次回收。
那么s1呢?
用官方的话来说:s1的值,被move到了s2那里。
用官方的图来说:
也就是说,move之后,原来的变量就不能用了。此时s1这个变量很尴尬,他能做的只有一件事情,就是重新给他一个新的内存,前提是 s1 声明的时候加上mut修饰:
{
let s1 = String::from("hello");
let s2 = s1;
// 这里开始,s1这个变量几乎啥也干不了
println!("{}", s1) // 连打印都不行,相当于废了😁
}
到这里,我们知道:
- 谁拥有
值的所有权,谁就负责释放 值的所有权,可以被move给别的变量,此时,原变量几乎废了
对于值来说,除了move还有borrow,这个后面再说吧。