Rust_0基础学习笔记_01:所有权机制1

作为一个年近40且现代系统编程0基础的业余人士(文科背景,大学期间学过PHP,后来断断续续使用VBA解决一些工作中的小问题),笔者突发奇想开始学习起Rust这样一个新的语言。

这样的背景,导致笔者在学习Rust的过程中不可避免地会遇到很多基础知识方面的和概念上的障碍,会出现很多超纲的内容(哭)。

因此,笔者希望通过这样一些学习笔记,能够将各种渠道获得的碎片化的信息加上自己的思考,以结构化的形式,转化为自己的知识,同时记录自己学习的过程,仅此而已。如能得到与笔者同水平人士的共鸣,将不胜荣幸。同时,也强烈欢迎专业人士批评是正或者轻喷。

下面进入正题

目录

一、解决什么问题?

二、如何解决问题?

三、所有权机制是什么?

四、Rust所有权机制的三大支柱

编译期审核

RAII机制

Move机制


一、解决什么问题?

笔者认为,在尝试去学习、理解一个抽象事物的时候,比较有效率的方式,是首先弄清楚这个事物之所以出现或者发生的原因。比起直接钻入具体的细节,这可能会让人更加清楚自己到底在什么地方,并提升知识结构化的程度,从而提高学习的效率。坦率地说,对于笔者这样的0基础小白,把学习的范围扩展的太大,不一定是好事。但是,却也肯定不是坏事。因此,笔者最终还是决定采取延展的学习方式,即在条件许可的情况下,尽可能的弄清楚事物的上下文。这本身也是学习的乐趣之一。

那么,说回所有权(Ownership)Rust中这一最具代表性的机制。在弄清楚它是什么东西之前,我们先来尝试理解它到底要解决什么样的问题,实现什么样的目的。

一言以蔽之,Rust的所有权机制,就是尝试(结合其他相关机制,如类型系统)从根本上解决长久以来内存安全和代码运行效率的两难问题

所谓内存安全问题,主要指向的是程序【访问未定义内存】这一行为,比如空指针、悬垂指针、重复释放内存等。

为什么这些问题会成为问题?经过一番学习,笔者理解:当下的虚拟内存管理体系,包括栈和堆等一系列基础设置,应该说是迄今为止效率最高的内存管理体系(也许仅针对PC领域?)。但是,栈和堆在值的存放上的分工,导致指针这一机制不可或缺,从而衍生出一系列问题,可谓是环环相扣。

这一行为的后果,对程序来说有可能是灾难性的。因此,一个编程语言能否有效地避免访问未定义内存就显得至关重要。但是,一直以来,【有效避免访问未定义内存】这件事情不是免费的,而是有成本的,且成本不小。这种成本体现在两个方面:

  1. 耗费更多的系统资源;
  2. 耗费更多的程序员资源。

前者主要指的是GC机制,即自动化内存管理,如Java、Python等。因为是自动化,又是解释性语言,所以毫无疑问会耗费系统资源,造成性能问题;

后者主要指的是C的手动内存管理,按需分配,按需释放。因此,性能上优于GC机制。但是,这种机制下对程序员的要求很高。是人就会犯错,稍有疏忽,就会忘记释放内存或者非法释放内存,导致内存不安全。注:C++采用RAII机制,无需手动参与,但依然有不小的内存风险,依然需要经验丰富的程序员。

这就形成了一个鱼和熊掌不可兼得的局面:要安全还是要性能?对于有许多并发的大型、超大型项目,如网络游戏服务器,对性能的要求极高,因此只能选择C++。但与此同时,内存安全问题就像头上悬着的一把剑。

好了,第一个问题我们已经解决了:Rust要解决的问题,就是如何才能实现更安全且更高效的代码

二、如何解决问题?

Rust要如何解决这个问题呢?还是一言以蔽之,

“by restricting how your programs can use pointers” -- Programming Rust 2nd Edition

演译(笔者自创术语^^=演绎性翻译)过来就是:通过“框定程序能够使用指针的条件、范围和方式”这一方式。那么,具体是如何实现的呢?

Rust给出的总体方案是:【早发现早治疗】,在编译期就提前获知潜在的内存安全问题。如果出现问题,程序无法通过编译。换句话说,一旦编译通过,程序员就不用担心任何的内存安全问题,程序的性能也不会受到类似GC这种机制的干扰,果然是一箭双雕。

笔者合理推测,Rust之父Graydon Hoare想必是开了这样一个脑洞:我们也许很长一段时间内无法看到替代指针机制的新方案的诞生,那么在现有的框架下,既然无法有效率地解决指针带来的问题,那就解决提出问题的人,哦,不,就解决有问题的指针!既然runtime时的指针问题不好搞定,那就回到源头,在compile-time就抓出有问题甚至潜在问题的指针!

这就是继手动管理、runtime自动管理之后的第三条路线:编译期自动管理,即在编译期就介入内存管理,以从根本上规避之内存安全问题。

这应该是一个创造性的解决方案了。虽然笔者未经历过被指针支配的恐惧,但Rust的Ownership被如此“大吹特吹”,可以继续合理推测它肯定是非常有效的解决了C/C++的相关问题。

笔者猜测,这种思路可能应该早已有之,甚至连implement这一思路的语言也早已有之,并非Graydon Hoare的独创。但也许是Rust实践得最彻底最好?

第二个问题我们也解决了。

三、所有权机制是什么?

随着第二个问题的解决,我们终于接近问题的本体。

我们已经知道,Rust限制指针使用的方法,是在编译阶段就对程序进行检查

那么,我相信大家很快就会意识到这样一个问题:既然有了纪律部队来执行纪律,那这个纪律是什么?没错,【按照什么样的规则进行检查】就是纪律部队所执行的纪律,是保证Rust既要安全又要性能目标能够成功实现的最为重要的因素。

现在,我们可以先结论性地回答第三个问题了:所谓Rust中的所有权机制,就是检查内存安全问题时所适用的一套规则体系

笔者理解,C语言当中就有了所有权这种概念(或许需要手动内存管理的语言都有?),但将其作为语言系统性架构的一部分的,可能确实Rust是第一家。这种系统性,可以参见下文。

四、Rust所有权机制的三大支柱

Rust的所有权机制这套规则体系之所有能够运作起来,笔者认为,其有如下系统性的三大支柱:

[此处需要读者判断]

  • 编译期检查
  • RAII机制
  • Move机制
表格1:三大支柱
支柱        涵盖的问题   
编译期检查由其他语言的事中检查调整为事前检查,确保runtime没有内存问题->[安全]
RAII机制借鉴C++,比手动管理内存更安全;比GC管理内存更高效->[安全,性能]
Move机制所有权转移机制,对RAII机制的补充与闭环,最大限度保证内存安全->[安全]

编译期审核

这个上文中已经多次提及,不再展开。

这里可以脑洞一下,Hoare在构思Rust的时候,是先确定了Rust是一个编译型语言,之后才设计出编译期内存检查这一内存安全解决方案的呢;还是先确定了编译期内存检查,才反过来将Rust设计成一个编译型语言的呢?毕竟,安全性是Rust的最大卖点。

RAII机制

讨论之前,我们必须再延展一下:

程序中的许多变量,由于在编译的时候无法得知其大小(比如一个用来保存从数据库提取的数据的String),当实际绑定变量的时候,即runtime,要从堆上为其申请一个空间用以保存值。而栈帧中会保存一个指向这个堆空间的内存地址,或者说指针。程序要访问这个变量的值,就需要先从栈帧中找到指针,然后顺着指针找到堆上的地址。这就是笔者所理解的指针机制。

我们可以发现,指针和指针所指向的堆空间,实际上是两个完全独立的事物(废话)。既然是两个完全独立的事物,他们就【既可以挂钩,也可以脱钩】。

实际上,指针和堆空间的挂钩/脱钩,本身就是内存管理的主要内容和牛鼻子。而处理这个问题的方式,也是理解Rust的所有权机制的另一个关键因素。Rust借鉴了C++的RAII机制。

利用挂钩/脱钩这个框架,我们看一下C/C++/Rust的操作方式:

[此处需要读者判断]

表格2:挂钩与脱钩
语言挂钩      脱钩
Cmallocfree / p = NULL
C++声明对象时,自动调用构造函数     对象生命周期结束,自动调用析构函数
Rust绑定变量时,自动获得所有权变量生命周期结束,自动Drop

也就是说,Rust通过借鉴RAII机制,程序员不需要手动去实现申请内存和释放内存的操作,大幅提升安全性。同时,也避免了GC对于性能的影响。

但是,在保证内存安全这一点上,这依然不够,C++的RAII机制仍然会出现空指针等内存问题。于是,Rust引入了一个极其重要的机制:Move(所有权转移)

Move机制

不知不觉就啰嗦了三千字,加上Move的(对笔者而言)博大精深。这一块内容就留待下一期再考察吧。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值
>