rust的目标是提供极高性能的同时,又拥有极高的内存安全性
这个是怎么做到的?
很多人一上手就被rust的新概念给吓到了,好像在内存管理上跟其他语言完全不一样,教程虽然看得懂,但是真动起手来写代码来举步维艰。
我经过一些项目 总结了一些东西 对我自己来说很有用,希望对你也有用
rust写起来困难的一大原因是引用的概念跟其他语言完全不一样
通常我们说的引用,通常是带入了gc类语言的引用概念,比如go,java,python等等,这类语言的引用实际上相当先进,后面的语言runtime偷偷帮忙做了很多事情
根本不需要关心内存的释放,遇事不决就引用,用完就扔,gc会帮你处理,大部分情况下都会工作的非常好,当然代价就是性能啦
这类gc类语言的引用已经统治编程多年,导致我们对引用的理解略有根深蒂固,所以rust的引用觉得会有些不适应,写起代码来不知道该咋用,其实根本不是一码事,要换一种方式去理解
rust官方文档已经说啦,rust的引用其实是借用,你看,为了防止跟老家伙语言的引用混淆,社区也尽力啦!
对于rust的引用,我是这么理解的,其实这就是一种“安全指针”
都知道指针有两类安全问题,一个是指向的地方有问题(空指针、野指针、未初始化指针),一个是指针作废的时机有问题(内存泄漏)
所以带有gc的语言的引用,就是为了有性能代价的解决上述所有指针安全问题
而没有gc的语言像是C++的引用,是有一些折衷的,他至少保证指针不会凭空产生,创建时必须指向某个对象,但是也就仅此而已了,其他问题依然是没有保证的,只能依赖开发者多多注意,所以C++的引用在内存安全上的存在感是很低的,大家基本上只是把它当作一个传参语法糖在用
rust则是走了另外一条路,对指针做了一些限制,以保证不会出现上述指针的问题
这就是生命周期和引用规则
首先确保引用只会从已存在的对象生成,这是最基本的,就像C++做到的,那么什么时候作废呢?
简单理解,生命周期的作用就是为了确保指针的生命一定不会比与指向的对象长,大部分情况下的生命周期可以被编译器自动推导出来,而编译器推导不出来的,就由开发者自己指定,这就是生命周期标记符存在的意义
你看 一个生命周期 就搞定了指针本身的所有问题 而且不以运行时性能为代价,而是以开发者的心智为代价(笑)
那么指针问题仅仅是一部分的内存安全问题,还有一些内存安全问题是竞争导致的,这就是引用规则要解决的问题
rust简单粗暴的规定了,引用如果是可变的,这个对象的引用只能存在一个,而不可变引用可以存在无数个,从根本上断绝了可能会出现的内存竞争问题
说到这里,你可能觉得这也太离谱了,这也太麻烦了,这还咋用呢,我每一个引用都需要自己考虑生命周期,这也太累了吧?
别急,rust的设计哲学是,给你一个安全的底层,但依然保留了用简单易用的高级特性的能力,这就是rust的智能指针
当你不想自己设计生命周期的时候,单线程你就用Rc、多线程你就用Arc
当你不想管可变引用这类乱糟糟的事情的时候,单线程你就全都用 RefCell、多线程Mutex
我们在做并发开发的时候,甚至可以简单粗暴的这样分类:
如果你的对象需要被并发访问,你就用Arc包装起来
如果你的对象需要被并发修改,你就用Mutex包装起来
甚至如果你不追求极致性能,你的代码里可以一点都不不出现生命周期、可变引用,转而全都用智能指针,这样完全没有任何问题,仅仅会让你的代码性能从C++退化到现代C++呢,而且是保证内存安全哦(笑)
你可能会问,那这我直接用现代C++的智能指针不就完了吗。
区别就在于如果你还想贪图更多性能,rust存在一个性能极致的安全底层,而C++没得
甚至rust还给你了unsafe能力,让你完全放飞自我干翻编译器(慎重)
rust的引用算得上是Rust语言层面上中最独特的部分了
希望通过此文能够多少打消大家的一些关于rust引用类型的恐惧,提供一些工程启发