Rust: 初探“所有权”奥秘
作为一个没有学过C/C++的开发者(比如说我),在初步学习Rust中需要了解一些JavaScript中不存在的东西,那么所有权就是Rust中的核心功能之一。我需要一篇文章记录这一次的学习,在真正内容开始之前我需要描述一些基础知识,在最后也会简单看看”引用和借用“,”slice类型“这些与之相关的Rust概念。因为是Rust初学者,请大家阅读本篇文章带着自己的思考,因为每个人的思考方式和理解都不一样,所以可能会导致某些错误...。
参考资料:
预热
程序员编写的所有程序都必须管理其使用计算机内存的方式,比如说JavaScript这一类的语言具有垃圾回收机制(GC)它可以不断寻找不再使用的内存,因此不需要开发者手动干预;在另外一些语言中,我们需要亲自分配和释放内存;但是在Rust中就利用了所有权的概念管理内存,编译器在编译阶段会根据规则进行检查,同样的也不需要程序员手动干预内存,这些有关内存的工作都交给了所有权和编译器。所以我们学习Rust中的所有权的时候,准确说应该是学习编译器是通过什么“规则”来进行检查的,这个规则对我们更重要。
堆栈
我们在写JavaScript的时候,通常业务开发我们不需要考虑堆栈,但是在Rust中我们需要考虑一个值是在堆上还是在栈上,这和所有权息息相关,所以我们先简单回顾一下堆和栈的基础知识。堆和栈都是代码运行时可使用的的内存,它们是不相同的。大家都知道栈是先进后出
,像服务生手中一个一个托盘一样,最先放入的托盘一般都会最晚从上方取出;栈中所有数据都占用固定的大小,程序在编译时都会看值大小是否可能会变化,如果是则需要存储到堆中,因为堆中所有的数据都是凌乱的,需要分配器去在堆中区域开辟一块空间,标记为已使用并且返回指针
,这个过程也叫做在堆上分配内存
数据直接入栈当然也比在堆上分配内存更快,入栈直接放到栈顶即可,入堆的话分配器不仅要查看内存,分配内存还要记录以便为下一次分配做准备;访问堆上的数据也比访问栈中的数据要慢(因为指针),而因为处理器缓存的原因,跳转越少就越快,当访问的数据彼此都是栈中那自然快,但是彼此数据一个在栈中一个在堆中那自然会稍慢一点。那么所有权
将会帮助我们处理堆中的重复数据,无用的数据以及跟踪哪些代码在使用堆中数据,总的来说所有权的存在就是为了管理堆数据。
所有权规则
- 每一个值都有一个被称为其
所有者
的变量 - 值在任一时刻只有一个
所有者
- 当所有者(变量)离开作用域的时候就会被丢弃
感觉目前理解这几个规则还比较晦涩,但是我们可以随着笔记深入慢慢地理解
作用域
所有权规则中既然提到了作用域,那我们简单看看作用域,其实和JavaScript相差不大,我们非常容易理解
fn main(){ // a在这里无效
let a = "hello seho" // a有效
} // 作用域结束了,a无效
看起来很简单不是么?和JS一样?
我们此时会发现hello seho这个值是通过字面量硬编码创建了一个字符串并且绑定到a中,这种情况下a将会在栈中,因为它有着固定的大小,那么当作用域结束之后,a将会直接出栈。
内存和分配
对于以上的hello seho这种值,在我们的实际业务中并不常见,我们大多数会存在一个大小不定的内容,而且会在程序的运行时增大或者减小,那么为了支持这种需求只能在堆中开辟出一块内存,意味着程序需要在运行时去创建这块的区域,而且当我们处理完内容,需要把内存还给内存分配器。
第一步很好说,我们调用String::from
这种方法就可以在堆中创建一块区域,那么第二步怎么还呢?
在C中是需要手动的释放内存的,但是程序终究是人写的,有时候忘记还了,有时候过早还了,有时候重复还了,都会造成系统bug。在有GC
的语言中,GC会记录并且清除不使用的内容从而释放内存,所以这也就是我为啥喜欢JS的原因了,真的太爽了....
我们把上面的代码改造一下
fn main(){ // a在这里无效
let a = String::from(""hello seho"") // a有效
} // 作用域结束了,a无效
在作用域结束之后,rust会调用一个特殊函数drop
从而释放内存,在stirng内部会实现这个drop函数...
移动
我们在JS中写过很多这样的代码
let a = 1;
let b = a;
很显然,它在栈中创建了2个变量, a和b,他们的值都是1
在rust中也一样,但是如果我使用String::from
创建变量呢,在堆中和栈中会有不同么?
let a = String::from("hello seho");
let b = a;
很显然,a的值是hello seho,b“复制”了a的指针,它们都存在栈中,指针指向了堆中的hello seho。
诶,难道说,rust的表现和js一样?在我们前面提到了所有权的规则之一,当变量离开了作用域将会自动释放,但是此时a和b都指向同一堆中内容,此时不是造成了多次释放的问题?答案是肯定的,在我们之前就说到过,没有释放,过早释放,多次释放都会对程序造成影响,所以rust在针对我们上面这种代码的时候,作出了一个处理即当a被“复制”到了b身上,此时a不再生效,程序会在编译期间报错。
如此看来,就避免了多次释放的问题,而“复制”也不是真正的复制,而是移动
;
那么如果真的想克隆一个一摸一样的值,可以调用clone方法,而我们在上面写了一个整型的例子,为什么没有调用clone也可以被克隆,是因为它们本身就是栈中的数据,在栈中的拷贝是快速的,不需要通过移动
这种机制来实现拷贝
到此为止,我们应该能理解所有权到底是什么了,我们在本篇笔记中学习了所有权的概念和规则,以及复习了堆栈基本知识,还有rust的内存分配。我们在本篇笔记中大量的使用了js这门语言作为参照对象,如果你不熟悉js的话,下次笔记我会将语言的比较去掉,这样应该会更加容易理解,有问题发评论区,吴彦祖和迪丽热巴发的评论我肯定都会回复