Swift程序员对Rust印象:内存管理
像许多开发人员一样,我对 Rust 感兴趣已经有一段时间。不仅是因为它经常出现在各种技术媒体如 Hacker News 头条上,以及它在安全性和性能方面采用了一些新颖方法,而且还发现程序员在谈论它时带着一些特别喜爱和钦佩之感。最重要的是,它具有与我最喜欢的入门语言 Swift 相同的目标和功能。
由于我最近花时间在一些小型个人项目中试用 Rust,因此我想花一点时间记录一下我对该语言的印象,尤其是它与 Swift 相比。
大图景
Rust 和 Swift 有很多共同点:它们都是编译语言,具有强大的、现代的类型系统,并且注重安全性。代数类型、对可选值(Option)的语言级别设计等特性,有助于将许多错误类别从运行时发现转移到编译时检查。
那么这两种语言有何不同?我觉得它们之间最准确的差异描述是:
Swift 使编写安全代码变得容易。
Rust 使编写不安全的代码变得困难。
这两个语句听起来可能等效,但是有一个重要的区别。两种语言都有实现安全性的工具,但是要实现安全性,他们会做出不同的权衡:Swift 优先考虑开发便捷即使一定程度牺牲性能,而 Rust 更看重性能,即便某种程度牺牲开发便捷。
权衡:性能与开发效率
证明这种优先级差异的最直接的方法是比较这些语言的内存管理的不同。我先从 Rust 开始,因为该语言的内存管理方法是其独特的卖点之一。
在 Rust 中,内存主要是静态管理的(还有其他的内存管理模式,比如引用计数,但是可以先忽略它们)。这意味着,Rust 编译器会分析你的程序,并根据一系列规则来决定何时分配和释放内存。
为了提供安全性,Rust 使用一种称为借用检查(borrow checking)的新颖策略。在实践中,这种工作方式是,作为程序员,每次传递一个变量(即对存储位置的引用)时,都必须指定引用是可变的(mutable)还是不可变(immutable)的。然后编译器会使用一系列规则,来确保不能同时在两个位置上更改同一个内存区域,因此也证明你的代码没有数据竞争。
就内存使用和性能而言,此方法具有一些非常实用的属性。借用检查在内存使用方面过于吝啬,因为它通常避免进行值复制。由于这些检查在编译时而不是在运行时完成的,因此还避免了垃圾回收等解决方案的性能开销。
但是,它在易用性方面确实存在一些缺点。由于 Rust 变量所有权的性质,有些设计模式根本无法在 Rust 中使用。例如,实现双向链接列表或全局变量之类的东西就并不容易。随着时间的前移,有可能会有更直观的方式,并且有针对这些问题的解决方法,但是 Rust 无疑对程序员施加了其他语言没有的限制。
尽管不像 Rust 那样经常被谈论,但是 Swift 在内存管理方面也是非常有趣。
Swift 有两种基本类型的变量:引用类型和值类型。通常,引用类型是堆分配的,并通过引用计数进行管理。这意味着在运行时,将跟踪对引用计数对象的引用数量,并在计数达到零时释放该对象。Swift 中的引用计数始终是原子的:这意味着每次引用计数更改时,所有 CPU 线程之间都必须同步。这样的好处是消除了在多线程应用程序中错误地释放引用的可能性,但是由于 CPU 同步非常昂贵,因此也要付出可观的性能代价。
Rust 也具有用于引用计数和原子引用计数的工具,但是这些工具是可选的,而不是默认的。
相反,值类型通常是栈上分配的,并且它们的内存是静态管理的。但是,Swift 中值类型的行为与 Rust 的方式大不相同。在 Swift 中,值类型具有所谓的“写时复制(copy-on-write)”行为,这意味着每次将值类型写入新变量或传递给函数时,都会进行复制。
写时复制实现了借用检查的某些相同目标:作为程序员,你通常不必担心由于程序其他地方某些意外调用而导致某个值神秘地被改变。与 Rust 借用检查相比,它需要的认知成本更低,因为 Rust 中需要面临所有各种与所有权相关的编译错误类别,这些在 Swift 中根本不存在。但这样做确实要付出代价:这些额外的复制副本需要额外的内存使用和 CPU 周期才能完成。
在 Rust 中,也可以复制(copy)值,并让借用检查(borrow check)报错消失,但这确实增加了视觉噪音,因为复制必须显式声明。
因此这里有一个很好的例子,说明了这两种语言之间的取舍:Swift 为你提供了保持一定程度安全性的管理内存的广泛假设,这有点像 C++ 程序员在进行具体优化之前,常会根据最佳实践来处理内存。这使得着手编写代码非常容易,不需要一开始过多考虑底层细节,并且还获得了一些基本的运行时安全性和正确性,这不会在使用 Python 甚至 Golang 之类的语言时获得。
但是,它的确带有一些性能瓶颈,在运行程序之前,你甚至都不会觉得它会变慢。你也能写出高性能 Swift 代码,但是这往往需要深入的分析和优化才能实现。
另一方面,Rust 为你提供了许多用于指定如何管理内存的特定工具,然后对如何使用它们以避免不安全行为设置了一些严格的限制。这为你提供了非常出色的性能特征,但也是需要你承担遵守这些规则带来的的额外认知开销。
我得出的结论是,尽管这些语言都有一些共同的目标,但由于它们具有不同的基础特征,因而适用于不同的场景。例如,Rust 应该是嵌入式开发之类的不二选择,因为在嵌入式开发中,极致地使用内存和CPU周期极为重要,并且代码从开发编译到运行周期可能较久,因此在编译时就能捕获所有可能的问题就很重要。Swift 对于数据科学或无服务器逻辑之类的应用来说可能是一个更好的选择,而性能是次要的考虑因素,在不用考虑很多底层细节的情况下,更接近问题域是更有价值的。
无论如何,将来我都会非常感兴趣地同时使用这两种语言,并且在这篇文章后续更新中,我将保持对 Swift 和 Rust 之间的比较进行更多观察。
原文:
https://blog.spencerkohan.com/impressions-of-rust-as-a-swift-developer-2/
参考阅读:
高可用架构
改变互联网的构建方式