Rust 之Scope和Ownership

2,092 阅读4分钟

一起养成写作习惯!这是我参与「掘金日新计划 · 4 月更文挑战」的第19天,点击查看活动详情

变量作用域

所有的编程语言几乎都有这个概念,作用域(Scope)就是一个代码中的一个范围,在这个范围内,你声明的变量是有效的、可用的;在这个范围外,变量就失效、不可用了。

一个最广为人知的作用域就是函数作用域:

  • 一个函数内部声明的变量,你在函数外面使用就是错误的,这种一般称之为局部变量

那么还有别的作用域吗?当然有:

  • 循环体
  • 条件判断体

等等。

甚至有的语言里,你直接hu两个花括号,这两个花括号之间就是一个作用域,花括号之内的变量,出去了就失效,如下rust代码:

fn main(){ // 这就是main函数的作用域
    { // 这也是一个作用域
    }
}

大部分语言对于变量作用域的使用,也就到此为止了,就是变量可不可用的问题。但是Rust对于作用域有更多的挖掘。

程序什么时候释放内存

现代的编程语言,对于内存的使用,基本上都是分成 栈和堆两个区域的。

的使用主要是为了实现函数调用,没有就无法实现函数调用

一个函数在调用之前,首先要把局部变量全都弄到上去,然后再执行逻辑。

当然,执行完了函数之后,就会缩下去,释放空间。

这是编译时可以确定的空间,我们就放在上,那么编译时无法确定大小的空间呢?比如说,用户输入一个数字,按照这个数字,申请一块内存。

显然这块内存不能放在上,这是由于的实现必须在编译时确定,这种无法确定的空间,我们就在另一块内存上取用,这就是

对于来说,回收很方便,只要在函数结束时,缩回原状态即可。

那么呢,流行的有两个解决方案:

  • C/C++ 的,靠程序员手动释放
  • Java 等语言的,实现一个垃圾回收器,按照一定的算法,自动释放

那么Rust这种是啥?

Rust作用域对于内存的影响

如果一个变量的值,有引用上的数据,那么这个变量离开作用域的时候,就会被drop一次,也就是释放一次。

看这个例子:

{   // 这个作用域开始
    let s = String::from("hello"); // 从这句开始,s变量进入上面的作用域
}   // 这个作用域结束 ,s 内部,堆上的空间被回收

String这个东西是Rust标准库里的一个类型,这个类型是一个可变的字符串类型,真正的内容是存放在上的,借用一个官方的图:

image.png

左边是上,右边是上。

这个不重要,重要的是,s这个变量一旦离开了作用域,右边上的空间就会被收回。

double free 问题

一个堆上的空间,被回收两次,这就是   double    free \ \ \ double \ \ \ \ free

Rust怎么来解决这种问题呢?

答案就是:一个空间在任意时候,只能属于一个变量,这个变量离开作用域的时候,进行一次回收。

看下面的代码:

{
    let s1 = String::from("hello");
    let s2 = s1;
}

首先,let s2 = s1;,用官方的话来说是一个浅拷贝,也就是说上的内容是没有进行拷贝的。用一张官方图来解释此时此刻:

image.png

这不是重点,重点是,离开这个花括号作用域的时候,由于两个变量同时失效,那么此时就会发生所谓的 double  free\ double\ \ free

为了解决这种问题,Rust规定,s2失效的时候,执行一次回收。

那么s1呢?
用官方的话来说:s1的值,被move到了s2那里。
用官方的图来说:

image.png

也就是说,move之后,原来的变量就不能用了。此时s1这个变量很尴尬,他能做的只有一件事情,就是重新给他一个新的内存,前提是 s1 声明的时候加上mut修饰:

    {
        let s1 = String::from("hello");
        let s2 = s1;
        // 这里开始,s1这个变量几乎啥也干不了
        println!("{}", s1) // 连打印都不行,相当于废了😁 
    }

到这里,我们知道:

  • 谁拥有的所有权,谁就负责释放
  • 的所有权,可以被move给别的变量,此时,原变量几乎废了

对于来说,除了move还有borrow,这个后面再说吧。