为什么要使用 Rust 语言、Rust 语言有什么优势

Rust 是一种采用过去的知识解决将来的问题的技术。” ——Graydon Hoare

Rust 是一种快速、高并发、安全且具有授权性的编程语言,最初由 Graydon Hoare 于2006 年创造和发布。现在它是一种开源语言,主要由 Mozilla 团队和许多开源社区成员共同维护和开发。

虽然 Rust 是一种通用的多范式语言,但它的目标是 C 和 C++占主导地位的系统编程领域。这意味着你可以使用 Rust 编写操作系统、游戏引擎和许多性能关键型应用程序。同时,它还具有足够的表现力,你可以使用它构建高性能的 Web 应用程序、网络服务,类型安全的数据库对象关系映射(Object Relational Mapping,ORM)库,还可以将程序编译成WebAssembly 在 Web 浏览器上运行。Rust 还在为嵌入式平台构建安全性优先的实时应用程序方面获得了相当大的关注,例如 Arm 基于 Cortex-M 的微控制器,目前该领域主要由 C语言主导。Rust 因其广泛的适用性在多个领域都表现良好,这在单一编程语言中是非常罕见的。

Rust 作为一门静态和强类型语言而存在。静态属性意味着编译器在编译时具有所有相关变量和类型的信息,并且在编译时会进行大量检查,在运行时只保留少量的类型检查。它的强类型属性意味着不允许发生诸如类型之间自动转换的事情,并且指向整数的变量不能在代码中更改为指向字符串。例如在 JavaScript 等弱类型语言中,你可以轻松地执行类似“two = "2"; two = 2 + two;”这样的操作。JavaScript 在运行时将 2 的类型弱化为字符串,因此会将 22 作为字符串存储到变量 two 中,这与你的意图完全相反并且毫无意义。在 Rust 中,与上述代码意义相同的代码是“let mut two = "2"; two = 2 + two;”,该代码将会在编译时捕获异常,并提示信息:“cannot add '&str' to '{integer}'”。

因此,强类型属性使 Rust 可以安全地重构代码,并在编译时捕获大多数错误,而不是在运行时出错。用 Rust 编写的程序表现力和性能都非常好,因为使用它你可以拥有高级函数式语言的大部分特性,例如高阶函数和惰性迭代器,这些特性使你可以编译像 C/C++程序这样高效的程序。它的很多设计决策中强调的首要理念是编译期内存安全、零成本抽象和支持高并发。让我们来详细说明这些理念。

编译期内存安全:Rust 编译期可以在编译时跟踪程序中资源的变量,并在没有垃圾收集器(Garbage Collectors,GC)的情况下完成所有这些操作。

这意味你不会遇到在 free、double free 命令之后调用指针,或者运行时挂起指针等“臭名昭著”的问题。Rust 中的引用类型(类型名称前面带有&标记的类型)与生命周期标记隐式关联('foo),有时由程序员显式声明。在生命周期中,编译器可以跟踪代码中可以安全使用的位置,如果它是非法的,那么会在编译期报告异常。为了实现这一点,Rust 通过这些引用上的生命周期标签来运行借用/引用检查算法,以确保你永远不能访问已释放的内存地址。这样做也可以防止你释放被其他某些变量调用的任何指针。

零成本抽象:编程的目的就是管理复杂性,这是通过良好的抽象来实现的。接下来让我们来看一个 Rust 和 Kotlin 的良好抽象示例。抽象让我们能够编写高级并且易于阅读和推断的代码。我们将比较 Kotlin 的流和 Rust 的迭代器在处理数字列表时的性能,并参照 Rust提供的零成本抽象原则。这里的抽象是指能够使用以其他方法作为参数的方法,根据条件过滤数字而不使用手动循环。在这里引入 Kotlin 是因为它看上去和 Rust 存在相似性。代码很容易理解,我们的目标是给出更高层面的解释,并对代码中的细节进行详细阐述,因为这个示例的重点是理解零成本特性。

首先,我们来看 Kotlin 中的代码:

 import java.util.stream.Collectors  

 fun main(args: Array<String>)
 {
     //创建数字流
     val numbers = listOf(1, 2, 3, 4, 5, 6, 7, 8, 9, 10).stream()
     val evens = numbers.filter { it -> it % 2 == 0 }
     val evenSquares  = evens.map { it -> it * it }
     val result = evenSquares.collect(Collectors.toList())
    println(result)       // prints [4,16,36,64,100]  

    println(evens)
    println(evenSquares)
 }

我们创建了一个数字流(第 6 行)并调用了一系列方法(filter 和 map)来转换元素, 以收集仅包含偶数的序列。这些方法可以采用闭包或函数(第 8 行中的“ it -> it * it”)来转换集合中的元素。在函数式编程语言中,当我们在流/迭代器上调用这些方法时,对于每个这样的调用,该语言会创建一个中间对象来保存与正在执行的操作有关的任何状态或元数据。因此,evens 和 evenSquares 将在 JVM 堆上分配两个不同的中间对象。在堆上分配资源将会产生内存开销,这是我们在 Kotlin 中为抽象必须额外付出的代价。

当我们输出 evens 和 evenSquares 的值时,确实得到了两个不同的对象,如下所示:

java.util.stream.ReferencePipeline$Head@51521cc1
java.util.stream.ReferencePipeline$3@1b4fb997 

@之后的十六进制值是 JVM 对象的哈希值。由于哈希值不同,所以它们是不同的对象。在 Rust 中,我们会做相同的事情:

 fn main() {
     let numbers = vec![1, 2, 3, 4, 5, 6, 7, 8, 9, 10].into_iter();
     let evens = numbers.filter(|x| *x % 2 == 0);
     let even_squares = evens.clone().map(|x| x * x);
     let result = even_squares.clone().collect::<Vec<_>>();
     println!("{:?}", result); // 输出 [4,16,36,64,100]
     println!("{:?}\n{:?}", evens, even_squares);
 } 

接下来将解释上述代码的细节。在第 2 行中,我们调用 vec![]创建一个数字列表,然 后调用 into_iter()方法使其成为一个数字的迭代器/流。使用 into_iter()方法从集合中创建 了一个包装器的迭代器类型(这里 Vec<i32>是一个有符号的 32 位整数列表),即 IntoIter([1,2,3,4,5,6, 7,8,9,10]),此迭代器类型引用原始的数字列表。然后我们执行 filter 和 map 转换(第 3 行和第 4 行),就像我们在 Kotlin 中所做的那样。第 7 行输出 evens 和 even_squares 的类型,如下所示(为了简洁,省略了一些细节):

evens:        Filter { iter: IntoIter( <numbers> ) }
even_squares: Map { iter: Filter { iter: IntoIter( <numbers> ) }} 

中间对象 Filter 和 Map 是基础迭代器结构上的包装器类型(未在堆上分配),它本身是一个包装器,包含对第 2 行的原始数字列表的引用。第 4 行和第 5 行的包装器结构在分别调用 filter 和 map 时创建,它们之间没有任何指针解引用,并且不会像 Kotlin 那样产生堆分配的开销。所有这些可归结为高效的汇编代码,这相当于使用循环(语句)的手动编写版本。

支持高并发:当我们说 Rust 是并发安全的时,其含义是该语言具有应用程序接口(Application Programming Interface,API)和抽象能力,使得编写正确和安全的并发代码变得非常容易。而在 C++中,并发代码出错的可能性非常大。在 C++中同步访问多个线程的数据时,需要在每次进入临界区时调用 mutex.lock(),并在退出它时调用 mutex.unlock():

// C++
mutex.lock();          // 互斥锁锁定
 // 执行某些关键操作
mutex.unlock();        // 执行完毕

在大量开发人员共同协作的大型代码库中,你可能会忘记在多线程访问共享对象之前调用 mutex.lock(),这可能导致数据访问冲突。在其他情况下,你可能忘记解开互斥锁(Mutex),并使其他想要访问数据的线程一直处于等待状态。

Rust 对此有不同的处理方式。在这里,你将数据包装成 Mutex 类型,以确保来自多个线程的数据进行同步可变访问:

// Rust
use std::sync::Mutex;
fn main() {
    let value = Mutex::new(23);
    *value.lock().unwrap() += 1;         // 执行一些修改
}                                        // 这里自动解锁

在上述代码中,我们能够在变量 value 调用 lock()方法之后修改数据。Rust 采用了保护共享数据自身,而不是代码的概念。Rust 与 Mutex 和受保护的数据的交互并不是独立的,这和 C++中的情况一样。你无法在 Mutex 类型不调用 lock()方法的情况下访问内部数据。

那么 lock()方法的作用是什么?调用 lock()方法之后会返回一个名为 MutexGuard 的东西,它会在变量超出作用域范围之后自动解除锁定,它是 Rust 提供的众多安全并发抽象之一。

另一个新颖的想法是标记特征的概念,它在编译期验证,并确保在并发代码中同步和安全地访问数据,第 4 章详细介绍了该特征。类型会被称为 Send 和 Sync 的标记特征进行注释标记,以指示它们是否可以安全地发送到线程或者在线程之间共享。当程序向线程发送值时,编译器会检查该值是否实现了所需的标记特征,如果没有,则禁止使用该值。通过这种方式,Rust 允许你毫无顾虑地编写并发代码,编译器在编译时会捕获多线程代码中的异常。

编写并发代码已经很难了,使用 C/C++会让它变得更加困难和神秘。当前 CPU 没有获得更多的时钟频率;相反,我们添加了更多内核。因此,并发编程是正确的发展方向。Rust 使得编写并发代码变得轻而易举,并且降低了编写安全的并发代码的门槛。

Rust 还借鉴了 C++的 RAII 原则用于资源初始化,这种技术的本质是将资源的生命周期和对象的生命周期绑定,而堆分配类型的解除分配是通过执行 drop 特征上的 drop()方法实现的。当变量超出作用域时,程序会自动调用此方法。它还用 Result 和 Option 类型替代了空指针的概念,我们将在第 6 章对此进行详细介绍。这意味着 Rust 不允许代码中出现null/undefined 的值,除非通过外部函数接口与其他语言交互,以及使用不安全代码时。该语言还强调组合,而不是继承,并且有一套特征系统,它由数据类型实现,类似于 Haskell的类型类,也被称为加强型的 Java 接口。

但同样重要的是,Rust 社区非常活跃和友好。该语言包含非常全面的文档,可以在Rust 官网中找到。Rust 在 Stack Overflow 的开发者调查上连续 3 年(2016 年、2017 年和2018 年)被评为最受欢迎的编程语言,因此编程社区对它非常青睐。总而言之,如果你希望编写具有较少错误的高性能软件,又希望感受当前流行语言的特性和极佳的社区文化, 那么 Rust 应该是一个不错的选择。

到此这篇关于为什么要使用 Rust 语言、Rust 语言有什么优势的文章就介绍到这了,更多相关Rust 语言优势内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!

(0)

相关推荐

  • Rust 能够取代 C 语言吗

    Rust 是 Mozilla 基金会的一个雄心勃勃的项目,号称是 C 语言和 C++ 的继任者.一直以来,C/C++ 中的一些基本问题都没能得到解决,比如分段错误.手动内存管理.内存泄漏风险和不可预测的编译器行为.Rust 的诞生就是为了解决这些问题,并提高安全性和性能. Evrone(一家软件公司)在很多项目中使用了 Rust,我们的工程师们这方面在积累了丰富的经验.在这篇文章中,我们将分享 Rust 的一些主要特性. 主要特性 强静态类型:无垃圾回收以及通过指针手动控制数据存储位置的能力:强

  • C#语言主要语言区域

    目录 一.数组.集合和 LINQ 1.数组 二.字符串内插 三.模式匹配 四.委托和 Lambda 表达式 五.async/await 六.属性 一.数组.集合和 LINQ C# 和 .NET 提供了许多不同的集合类型. 数组包含由语言定义的语法. 泛型集合类型列在 System.Collections.Generic 命名空间中. 专用集合包括 System.Span<T> (用于访问堆栈帧上的连续内存),以及 System.Memory<T> (用于访问托管堆上的连续内存).

  • 为什么要学习C语言 C语言优势分析

    不止一个学生问到我:"老师,为什么我们的应用程序设计要学C语言而不是别的?C语言不是已经过时了吗?如果现在要写一个Windows程序,用VB或Dephi开发多快呀,用C行吗?退一万步,为什么选择C而不是C++呢?" 这个问题三言两语还真说不全.简单来说,C语言是计算机程序语言的基础,是实用的程序设计工具,学好C语言对你今后学习JAVA.C++.VB等可以打下良好的基础,因为这些语言大部分都是由C语言扩充或衍生而来的.C可以用于开发比较底层的东西,比如驱动.通信协议之类,在Unix和Li

  • 详解易语言导入语言包方法

    可能有不少软件作者因为语言包的问题而纠结,因为易语言没自带导入语言包的命令,所以只能用最普通的方式来导入语言包 1.新建一个"易语言"窗口程序 2.然后在桌面创建两个语言配置文件 你可以选择自己喜爱的语言 我在这里创建了英语和中文两个语言配置文件 3.组件有一个按钮 六个标签 两个单选框 标签内容对应语言配置文件 你可以根据自己喜爱更改 我只是举个例子 4.具体代码如下: .版本 2 .支持库 shell .程序集 窗口程序集_启动窗口 .程序集变量 语言, 文本型 .子程序 导入语言

  • Rust应用调用C语言动态库的操作方法

    目录 外部功能接口FFI UDP套接字的读超时 Rust调用C语言动态库中的函数 避免重复造轮子,使用Rust官方C语言库 外部功能接口FFI 虽然高级(脚本)编程语言的功能丰富,表达能力强,但对底层的一些特殊操作的支持并不完善,就需要以其他编程语言来实现.调用其他编程语言的接口,被称为Foreign Function Interface,直译为外部功能接口.该接口通常是调用C语言实现的外部功能模块,因为C语言接近于全能,几乎任何功能都能够实现:正如同使用汇编语言也可以实现很多功能一样,但开发效

  • 我放弃Python转Go语言的9大理由(附优秀书籍推荐)

    前言 Go大概2009年面世以来,已经8年了,也算是8年抗战.在这8年中,已经有很多公司开始使用Go语言开发自己的服务,甚至完全转向Go开发,也诞生了很多基于Go的服务和应用,比如Dokcer.k8s等,很多的大公司也在用,比如google(作为开发Go语言的公司,当仁不让).Facebook.腾讯.百度.阿里.京东.小米以及360,当然除了以上提到的,还有很多公司也都开始尝试Golang,这其中是什么原因呢?让我们来一起分析分析. 原因 1:性能 Go 极其地快.其性能与 Java 或 C++

  • 放弃 Python 转向 Go语言有人给出了 9 大理由

    转用一门新语言通常是一项大决策,尤其是当你的团队成员中只有一个使用过它时.今年 Stream 团队的主要编程语言从 Python 转向了 Go.本文解释了其背后的九大原因以及如何做好这一转换. 一.为什么使用 Go 原因 1:性能 Go 极其地快.其性能与 Java 或 C++相似.在我们的使用中,Go 一般比 Python 要快 30 倍.以下是 Go 与 Java 之间的基准比较: 原因 2:语言性能很重要 对很多应用来说,编程语言只是简单充当了其与数据集之间的胶水.语言本身的性能常常无关轻

  • IntelliJ安装并使用Rust IDE插件

    Rust 是一个由Mozilla主导开发的通用编译型编译语言.它的设计准则为"安全,并发,实用",支持函数式,并发式,过程式以及面向对象的编程风格. Rust插件的主要特性如下: 导航特性:Go to Class.Go to Symbol.Go to Super Module.Structure.Go to Definition. 编辑器特性:代码自动完成.格式化(计划支持rustfmt).合并行.智能按键(如自动插入匹配的符号).自动填充后缀.基本的Intention和重构(如引入变

  • C++的替代:微软如何使用rust?

    微软拥有世界上最大的C/C++代码库之一.从Windows.Office到Azure云,微软的所有核心产品都在该代码库上运行.但因为C++不是内存安全的语言,代码库中自然频频出现内存漏洞,大量的时间被耗费在修补漏洞上. 微软自去年开始寻找用以替代的编程语言来解决内存安全问题,寻找终有所得--微软开始尝试使用Rust,并在一些情境下将其集成进代码库中.Rust是一种相对较新的编程语言,具有与C和C++相同的底层性能,并具备现代编程语言应有的功能集. 微软认为Rust颇具潜力,本文就将介绍微软将其用

  • Rust 函数详解

    目录 函数参数 函数返回值 高阶函数 函数指针类型 函数作为参数 函数作为返回值 相关资料 Rust 支持多种编程范式,但更偏向于函数式,函数在 Rust 中是"一等公民",函数可以作为数据在程序中进行传递.跟 C.C++ 一样, Rust 也有一个唯一的程序入口 main 函数. 示例:程序入口 main 函数 fn main() { println!("Hello, world!"); } Rust 使用 fn 关键字来声明和定义函数,使用 snake case

  • Rust语言中的String和HashMap使用示例详解

    目录 String 新建字符串 更新字符串 使用 + 运算符或 format! 宏拼接字符串 索引字符串 字符串 slice 遍历字符串 HashMap 新建 HashMap HashMap 和 ownership 访问 HashMap 中的值 更新 HashMap 直接覆盖 新插入 更新旧值 总结 String 字符串是比很多开发者所理解的更为复杂的数据结构.加上 UTF-8 的不定长编码等原因,Rust 中的字符串并不如其它语言中那么好理解. Rust 的核心语言中只有一种字符串类型:str

随机推荐