查看原文
其他

Why Rust?|选择 Rust 编程语言的理由

DmitriyKashitsyn PolkaWorld 2020-04-01

加入 www.polkaworld.org 社区,共建 Web 3.0!

本文由区块链开发公司 Parity Technologies 软件开发者 Dmitriy Kashitsyn 撰写,阐述了他们使用 Rust 开发的理由。目前区块链领域有多家公司都在使用 Rust 开发,Parity 也是其中之一。


编程很难。


不是因为我们的硬件很复杂,而是因为我们都是人类。我们的注意力是有限的,我们的记忆是易变的,换句话说,我们容易犯错误。


计算机和软件无处不在:在太空中,在空气中,在地面上,甚至在我们的身体里。每天越来越多的系统成为自动化的,越来越多的生命依赖于软件及其质量。


航空电子设备、自动驾驶汽车、核电站、交通控制系统、植入起搏器。这类系统中的漏洞几乎总是危及人类的生命。(https://en.wikipedia.org/wiki/Therac-25)


“通过测试检查程序正确性” 和 “逻辑证明程序正确性” 之间有着巨大的区别。不幸的是,即使我们对代码的每一行都进行了测试,我们仍然不能确定它是否正确。然而,有一个正式的系统来证明我们的代码是正确的(至少在某些方面)是另一回事。



Rust 的方式


Rust(https://www.rust-lang.org/en-US/)作为一种语言是不同的。这并不是因为它奇特的语法或受欢迎的社区,而是因为在用它编写程序时获得的信心。Rust 的编译器非常严格和迂腐,它检查你使用的每个变量和引用的每个内存地址。这看起来可能会阻止你编写有效且有表现力的代码,但令人惊讶的是,情况恰恰相反,编写一个有效且通顺的 Rust 程序实际上比编写一个潜在的危险程序容易。在后一种情况下,你将与编译器抗争,因为几乎你尝试的每个操作都会导致内存安全问题。

 

https://phil-opp.github.io/talk-konstanz-may-2018/#14. 


上图的右边部分显示了并发性和内存安全性问题,这些问题根本不可能出现在 Rust 的安全子集中。


所以,只要使用 Rust,它们就可以防止那个时期大约一半的 bug。此外,缓冲区溢出漏洞(buffer overflow bugs)是最危险的漏洞之一,因为它们经常导致机密泄露(http://heartbleed.com/)、拒绝服务和远程代码执行漏洞。


同时,它也表明 “一个人只需要知道如何写 C ” 和 “把 low-level 的事留给专业人士” 这样的想法是不够的。Linux 内核是由业界最优秀的前 5% 开发者编写的,然而,每年,我们都会从同样好的 ol 内存错误中获得 CVEs。


当然,与数百万行的工作代码相比,这 50 个错误算不了什么。但又是生死攸关的问题,记得吗?当我们谈到关键系统时,即使是最微小的错误也可能导致灾难性的后果。更不用说这 50 个被发现的 bug。但谁知道还有多少呢?通过使用 Rust,我们可以事先知道答案。



Rust 有多快


你可能会想,当然,Rust 可以提供所有的东西防止这些陷阱,但代价是什么呢?在现代编程语言中,内存安全通常伴随着垃圾收集器的开销。并发问题通常通过使用特殊的同步原语锁定所有受影响的数据结构和执行路径来解决。


但对于 Rust,情况并非如此。它的所有功能都来自于它巧妙的类型系统,它可以在编译时解决所有这些问题。同样的设计既防止了内存问题也防止了数据竞争的问题。


就像 C++ 一样,你只需支付你所用的。例如,在 Rust 中,只有在绝对需要时才使用 mutex。而且,Rust 编译器会强制你使用它,所以你永远不会忘记添加它。


所有这些基本上都是零成本的。由于大多数检查是在编译时执行的,编译的程序集与 C 或 C++ 编译器所生成的程序不会有太大的不同。


因此,Rust 在嵌入式电子(http://blog.japaric.io/)、物联网(https://www.tockos.org/),甚至操作系统开发(https://wiki.osdev.org/Rust)等领域都有着非常广阔的发展前景,这些领域由于控制要求高、资源和性能限制严格,以前都是 C 语言主导的领域。


最新版本的 Rust 甚至给用户空间带来了 SIMD 支持。以前,由于 API 稳定性的限制,它只在夜间发布。现在,你可以通过直接使用矢量指令或使用方便的封装库(https://github.com/AdamNiederer/faster)来释放硬件的全部潜力。即使你不打算这样做,编译器仍然会在可能的情况下自动对循环和其他东西进行矢量化,在许多情况下,可以达到与手工编写的矢量代码相当的性能水平。



我们为什么使用 Rust


Parity 使用 Rust 的原因是一样的。因为它让我们可以毫无畏惧地编写复杂而高效的软件。我们可以自由试验,因为我们确信 Rust 会帮我们解决后顾之忧。无论是简单的命令行实用程序还是多线程怪物,它都没有区别。Rust 确保我们的程序没有未定义的行为、数据竞争或任何内存安全问题。更不用说,Rust 速度非常快,写起来很有趣,很容易阅读,基本上没有运行时间。


内存错误是如此之难,因为你无法简单地编写测试来捕获它们。如果你在测试期间没有发现一个 bug,那么它可能会在代码中停留数年,就像一颗定时炸弹在等待它的时机一样。当然,也有一些工具,比如 Valgrind(http://valgrind.org/),可能有助于捕捉这些 bug。但是,如果生成的代码不是在调试会话期间执行的,或者是以不会导致内存问题的方式执行的,即使 Valgrind 也不会捕获到 bug。


因此,通过使用 Rust,我们消除了最复杂、最不可预测的一类错误。



测试的角色


当然,内存安全问题只是故事的一部分。例如,我们可以编写一个函数来求其整数参数的和,但是它只返回一个任意常数作为结果。或者我们可以编写一个产生可预测值的随机数生成器。这种行为并没有违反 Rust 的内存保证,但显然是不正确的。

 

https://xkcd.com/221/


这就是测试的目的。测试允许我们检查编译器无法识别的不变量。基本上,我们需要确保一个对应的测试覆盖了返回的每个结果以及程序中做出决策的每个点。在上面的示例中,测试必须检查函数是否确实返回其参数的和,并且生成的随机值是否足够随机。


从某种意义上说,逻辑错误更容易处理。根据定义,它们来自程序员编写程序时所想到的同一个域(而内存错误则不在这个域内)。


幸运的是,我们知道如何处理这些 bug。在过去的几十年里,程序员和计算机科学家创造了一套方法和工具,通过这些方法和工具,我们可以减少逻辑 bug 的数量,并将其保持在最低限度。



数学的力量


在最严格和最复杂的方法中,程序的正确性是要证明的,而不是检查的。像 Iris(http://iris-project.org/)和 Coq(https://coq.inria.fr/) 这样的语言可以用来证明整个程序的正确性。与检查某些输入(如测试)的程序有效性不同,它被证明为一个数学定理,一次性地检查所有可能的输入和每个可能的场景。只有通过建立这样的证明,你才能获得信心,程序是正确的(只要你的规格和理解是正确的)。


基本上,Rust 也做了同样的事情,但是针对一组有限的问题,特别是并发性和内存安全性。它使用逻辑来证明你的程序在这些方面是正确的。想想看,只要编写普通的 Rust 代码,在你每次编译项目时,你就会像有一帮数学家在帮你一起研究一个定理一样的信心。


不幸的是,证明系统的每一个部分都是如此复杂和耗时,以至于它通常只针对软件中最关键的部分,比如操作系统内核、加密算法,以及某些情况下的语言标准库。


很长一段时间以来,像 Haskell 这样的函数式编程语言的一个杀手锏特性就是正式证明代码,而传统的命令式编程语言由于广泛使用了共享的易变性、不安全的指针算法和不受控制的副作用,大部分情况下仍然无法证明代码。但 Rust 可以改变这一点,尽管它是一种命令式语言,但它仍在走向正式证明的道路上。


来自 RustBelt 的 Ralf Jung et al. 已经发表了一些论文,证明了 Rust 语言声明的基本不变量确实存在于标准库的一些重要原语中。


问题是,出于性能原因,Rust 标准库包含许多潜在的不安全代码和原始指针算法。


为了证明标准库的正确性,Ralf Jung 和他的同事设计了一种使用分离逻辑和他们自己的演算来解决不安全问题的方法,他们称之为 λrust。使用这种演算,他们试图证明标准库原语和容器按预期工作,并且它们不违反 Rust 的基本不变量。作为一个副产品,他们甚至在同步原语中发现了一些 bug,比如 MutexGuard 和 Arc。

MutexGuardhttps://www.ralfj.de/blog/2017/06/09/mutexguard-sync.html)

Arc(https://www.ralfj.de/blog/2018/07/13/arc-synchronization.html)


但这项工作远未完成。正如作者所指出的:

我们在 libstd 中已经证明了这一点。那将需要比我们所能调动的更多的人力。相反,我们关注的是看起来最有趣的 libstd 原语,它似乎最强调类型系统。这主要是关于内部的易变性。因此,我们已经验证了 Cell、RefCell、Rc、Mutex、RwLock、Arc 和一些在本博客文章中列出的更独立的方法:

https://www.ralfj.de/blog/2017/07/08/rustbelt.html。


我们希望有一天我们能够为使用 Parity 编写的代码提供相同级别的正确性证明。


再加上它的控制级别、捕捉内存和并发问题的能力,Rust 正在成为最先进的主流通用语言之一,可以成功地用于编写健壮、安全和高效的程序。


原文:https://www.parity.io/why-rust/

翻译:PolkaWorld


  • 欢迎学习 Substrate: 

    https://substrate.dev/

  • 关注 Substrate 进展:

    https://github.com/paritytech/substrate

  • 关注 Polkadot 进展:

    https://github.com/paritytech/polkadot

  • 申请 Bootcamp: 

    https://bootcamp.web3.foundation/


阅读更多:


Parity:我们为什么选择 Libp2p

Rust 2020

Substrate 是什么?


预告:


识别下方二维码,报名下周三的在线直播「Substrate 的无分叉升级和利用 Offchain Workers 创建身份 Oracles」⬇️


扫码关注公众号,回复 “1” 加入波卡群

关注 PolkaWorld

发现 Web 3.0 时代新机遇


点个 “在看” 再走吧!

    您可能也对以下帖子感兴趣

    文章有问题?点此查看未经处理的缓存