对linear-type介绍的文章看得很少,也没兴趣和时间去研究,目前只限于对linear-type与生命期的关联有点朦胧感觉,其它深奥理论就茫然了。这里只对看到的片言只语乱弹点肤浅感想。
内存堆分配的指针/引用所带来的内存安全问题主要有三:空指针(未分配)、野指针(未初始化)、空悬指针(被其它指针提前释放)。对于并发多任务来说还有第四个问题:竞争导致值不确定。
无论是linear-type还是Rust语言的生命期,都是为了解决指针/引用所引发的问题,对于栈上连续分配的、随作用域同步分配和释放的变量,那些技术是没有意义的。
FP的纯函数 由于一次性定值,不允许再赋值和状态变化,也不存在同样问题。
非纯函数虽然允许变量真正能变,但如果在语言层面不存在显式的引用/指针类型,所有变量都是独立存在,对自己的内存空间操作,跟其它变量不发生地址相关的关系,只有纯值的复制。即使运行期代码有地址相关的操作,但隔绝了用户代码造成的那些问题。
像FP语言普遍采用的列表、字典都属于那样掩盖了引用实现细节的;数组也是自我存取的那类,因此都不是linear-type关照的目标。
空指针问题好解决——编译器遇到变量声明时即自动生成分配空间的代码。对于非要动态分配的,有null值把关,或者未分配的引用会引发异常。
野指针问题也好解决——语言层面强迫必须在声明或分配的同时赋予初始值。
空悬指针对于有GC的系统也不是问题,因为每次GC时是要检查引用是否清零的。
看来只有对那些极度讲究效率、不设GC的语言才会有问题现实存在。
FP语言本来就不是属于极度讲究效率的,一般都有虚拟机运行环境,肯定带GC。
为了避免多个指针变量引用同一个内存地址,从而避免其中一个释放操作导致其它变量变成孤魂野鬼,简单粗暴的做法就是GHC的linear-type方案——最多只使用一次,以确保每次使用后就可以放心释放——释放后肯定不会再有使用。为了确保释放肯定在使用之后,又要求必须至少使用一次。
用了这个机制,任何引用变量或参数都不能再转给另一个引用变量,或者转出后就再也不能使用了(即不再引用了),也就意味着不可能有两个及以上的引用变量指向同一块内存空间(按GC的技术就是引用计数器永远不会超过1),当然也就不可能出现空悬指针情况,更不会并发竞争。这就对原本很严格的纯FP不允许就地修改变量的约束作了放宽,解决纯癖带来的诸多不便 和逻辑与代码的复杂化,比如可以直接操作大数组,或可以假装大数组复制 而实质只是在原地操作了。这不就等同于上面提到的“在语言层面不存在显式的引用/指针类型”吗!反正我没琢磨出两者有啥区别。
Rust不是FP式语言,没那么严格,策略是(大意概括,非严格规则):
1,一块内存可以给多个引用变量只读(不可变引用)。但这块内存的生命期就是最初生成这块内存的变量(即所有者)的作用域,而其它后来来读取的引用变量的作用域不能超过这块内存当初的生命期,否则这块内存会到期(其作用域末尾)释放 而导致其它正在读取的变量全都变鬼。
“召来”其它变量来读(添加不可变引用)并不改变原有所有者。
2,如果允许对一块内存改写(可变引用),那么就只允许一个变量能写,其它变量通通不能读更不能写。能写的变量就是该内存块的所有者,当然可变引用的所有者只有一个,最初就是生成(申明可变)这块内存的变量。
3,如果有另一个变量取得了对这块内存的引用(无论读亦或写),那么原来那个所有者就失去了读写权利,后来那个变量取得了原来的读写权限,相当于所有权转移掉了。
好复杂!FP粉又有更多炫耀的素材了。
所以我觉得,GHC的linear-type方案(一次性使用)更简洁和严谨。至于使用上会否带来方便,功能上是否齐全,就不得而知了。
坐等大佬们出成熟的方案。