Rust编程——开头

241 阅读17分钟

欢迎来到《Rust编程》。

你的Rust超级高速之旅从这里开始。系好安全带,准备好享受一段充满学习与探索的精彩旅程。在这段旅程中,你将揭示Rust的优势,以及这门语言如何积极地改变现代编程语言的认知。每个旅程的阶段都会探讨Rust语言的不同领域。通过这种方式,你将全面了解这门语言,成为一名Rustacean——一个专业的Rust开发者,并且希望成为不断壮大的Rust社区的活跃成员。

本章的目标包括以下内容:

  • 提供Rust的概述和描述
  • 审视Rust的编程风格
  • 列举Rust的众多优势
  • 确定并解释Rust术语
  • 审视Rust应用的关键要素
  • 创建你的第一个Rust程序

引言

Rust是一种通用编程语言,旨在创建安全、可靠且可扩展的应用程序。这门语言融合了多种编程范式的特点,如下文所述。Rust最初是作为一种系统编程语言设计的,但它已经发展成为一种多用途语言,能够创建各种类型的应用,包括系统编程、Web服务、桌面应用、嵌入式系统等。尽管这听起来有些陈词滥调,但你用Rust能够实现的东西,唯有你的想象力能限制它。

不同! 这正是Rust的准确描述。虽然Rust的语法基于C和C++语言,但它与其他C语言家族的语言的相似性通常也就止步于此。而且,Rust并非仅仅为了与众不同而不同,它的不同有着明确的目的。

Rust的借用检查器就是一个具有目的的不同的典型例子。借用检查器是Rust中的一项独特功能,它通过强制执行与单一所有权原则相关的规则,促进安全的编码实践。没有其他语言具备这种特性。因此,借用检查器对于许多开发者来说是一个陌生的概念,但它无疑是极为宝贵的。

在许多方面,Rust代表了“经验教训”。Rust中的一些独特功能,是从其他语言的经验教训或失败中汲取的。Rust愿意在必要时偏离常规,是这门语言的一个重要特征。例如,许多编程语言在有效的内存管理上存在困难,而Rust的所有权特性正是为了实现有效的内存管理

说实话,学习Rust有时可能会让人感到沮丧。它需要投入时间,也需要一些脑细胞的牺牲。然而,你会发现,学习Rust的投资是完全值得的。例如,学会如何与借用检查器合作,而不是与之对抗,是极其宝贵的。

我写这本书的目标是增加Rustacean的数量——即在Rust编程语言方面具备高水平熟练度的人。我希望你能凭借新掌握的语言技能,成为Rust社区的活跃成员。现在,你已经正式开始了成为Rustacean的旅程。

函数式编程

Rust拥抱多种编程范式,包括函数式编程、表达式导向编程和模式导向编程。让我们从函数式编程开始,探索这些不同的编程范式。由于本书的重点是Rust,我将仅对这些编程模型做简要回顾。

什么是函数式编程?它是一种编程模型,其中函数是语言的基本构建块。在函数式编程中,函数是一级公民。你可以像使用变量一样使用函数:它可以是局部变量、函数参数,或者作为函数的返回值。函数甚至可以对其他函数执行操作,这就是所谓的高阶函数。

Rust是轻量级的函数式编程语言。它并不包含函数式编程中的所有特性,如惰性求值、声明式编程风格、尾调用优化等。然而,Rust确实支持一种函数式编程风格。

函数式编程语言通常会限制过程式编程能力,例如全局函数。由于它们并不冲突,Rust允许过程式编程和函数式编程风格的混合使用。

纯函数是函数式编程的核心。在函数式编程中,纯函数是完全通过其函数接口描述的。函数参数与特定的返回值之间有直接的关联,并且没有副作用。此外,纯函数的结果应该是可重复的。例如,一个依赖于内部随机数的函数,其结果无法预测,这就不是纯函数。

不可变性是函数式编程的重要组成部分,也是Rust的核心理念之一。例如,纯函数在很大程度上依赖不可变状态,以消除副作用。指针、全局变量和引用通常会被排除在纯函数之外,以避免从函数中泄漏的副作用。

总结来说,函数式编程提供了几个好处:

  • 增强的灵活性,函数作为一级公民
  • 更高的透明度,关注函数而非单独的代码行
  • 不可变性,通过消除函数中的副作用,简化程序的维护,避免常见问题

表达式导向

Rust也是一种表达式导向的语言,这是一种编程风格,在这种风格中,大多数操作都是表达式,它们返回一个值,而不是像语句那样什么也不返回。表达式导向编程与函数式编程非常相似,所有的函数式编程语言也是表达式导向语言。

那么,什么是语句,什么是表达式呢?

语句不返回值,但它们可能会导致副作用。副作用的种类几乎是无限的,包括数据库操作或更新共享变量。事实上,有些语句的目的是为了产生副作用。

表达式是一个或多个操作,它们返回一个值,并且副作用最小,甚至没有副作用。纯函数就是表达式的例子。

在Rust中,表达式是首选,即使是控制流语句,如ifwhile语句,实际上也是表达式。

表达式导向编程的许多好处包括:

  • 没有副作用,表达式导向的程序更易于维护。
  • 表达式的值通过其接口完全定义,使得表达式更具透明性。
  • 由于表达式是由接口驱动的,它们更容易进行测试。
  • 表达式导向编程使得文档编写更容易。没有副作用的表达式可以作为文档的替代。
  • 表达式更容易组合。
  • 能够结合多种编程范式是Rust的另一个优势。

代码示例 1.1 展示了在Rust中同时使用函数式和表达式导向编程的例子。阶乘函数是一个没有副作用的纯函数。作为表达式,阶乘函数返回阶乘计算的结果。

代码清单 1.1 阶乘是一个纯函数和表达式。

fn factorial(n:i32) -> i32 {
    match n {
        0..=1 => 1,
        _ => n * factorial(n - 1),
    }
}

模式导向

通常,在Rust的专业编程中,模式有着广泛的应用。可以说,模式在Rust编程中无处不在。这种对模式匹配的支持是Rust编程风格独特性的一个重要因素。与C++或Java等语言相比,Rust的源代码看起来确实不同!

模式匹配通常与switch语句相关联。例如,C++、Java、Go等语言都有switch语句。这是基于字符串或整数表达式的单维模式匹配。而在Rust中,模式匹配被扩展到用户定义类型和序列的实例。

Rust使用match表达式,而不是switch语句。然而,模式导向编程远不止match表达式。在Rust中,每个表达式的实例都是进行模式匹配的机会。例如,即使是简单的赋值或条件表达式,也可能触发模式匹配。这为重新构思代码提供了有趣的方式。

Rust中的模式导向编程提供了几个好处:

  • Rust中的模式非常富有表现力,使你能够将复杂的代码简化为更简单的表达式。
  • 在Rust中,模式导向编程是表达式导向编程的一个补充模型。
  • Rust支持全面的模式匹配,这使得程序更可靠,错误更少。

display_firstname函数展示了模式匹配的用法,如代码清单 1.2所示。函数的参数name是一个元组,元组的字段分别是lastfirst。在match表达式中,使用模式来解构元组,提取出名字并打印。

代码清单 1.2 display_firstname函数展示了名字。

fn display_firstname(name:(&str, &str)) {
    match name {
        (_, first) => println!("{}", first),
    }
}

特性

在最近的多项调查中,Rust被专业开发人员频繁选择为他们最喜爱的编程语言。这一认可的很大一部分与Rust的独特特性有关。

让我们来探索那些构成Rust及其流行度的核心特性。

安全性

安全性是一个重要的跨领域特性,几乎涉及语言的所有方面。安全代码是稳健的、可预测的,并且不容易出现意外错误。凭借这些特性,Rust提供了一个坚实的基础,开发者可以在此基础上自信地构建应用程序。不可变变量、单一所有权原则以及其他特性都为这一目标做出了贡献。

此外,Rust在编译时强制执行安全编码实践。所有权模型和借用检查器就是这一方法的完美示例。在编译时,借用检查器会进行一系列检查,包括所有权检查。如果所有权检查失败,借用检查器会给出解释,编译过程就无法成功完成。

以下是Rust中实现安全代码的一些因素:

  • 不可变性是默认设置,这可以防止无意中的修改。
  • 强制正确的生命周期,避免反模式,如悬空引用。
  • 引用的安全指针
  • 对于变长资源(如向量),采用“资源获取即初始化”(RAII)策略,确保可靠的内存管理。

所有权

所有权特性通过单一所有者原则提供了安全的内存访问。这个原则规定每个变量有且只有一个所有者,且不允许有多个所有者。

这种方法防止了内存共享所有权的问题。竞态条件、不稳定的变量和悬空引用是通过这种方法避免的潜在问题。本书稍后会介绍一些允许共享所有权的例外情况。

让我们用一个汽车的类比来展示单一所有者原则。基本情况是:有一辆车,Bob是它唯一的所有者。

接下来是两个场景:

  1. Bob拥有这辆车。
  2. Ari偶尔想开这辆车。

作为车主,Bob随时可以开车,除非他把车借给了别人。如果有人(如Ari)想开车,Bob必须要么卖车给Ari,要么把车借给Ari。无论哪种方式,Bob都失去了对这辆车的所有权,至少是暂时的。

如果Bob把车借给Ari,步骤如下:

  1. Bob开车。
  2. Bob把车借给Ari。Ari开车。当Ari开完后,把车还给Bob。
  3. Bob开车。

借用检查器负责在编译时强制执行正确的所有权管理,包括借用。我们将在本书后面解开借用检查器的谜团,目标是让借用检查器成为你的好朋友。

生命周期

生命周期是Rust的一个特性,防止访问不再可用的值。在Rust中,引用是基本的指针。如果允许不正确地访问已经被丢弃的值,可能会导致悬空引用和潜在的程序崩溃。这样会导致应用程序的不稳定性和不可预测性。Rust通过生命周期模型消除了这个问题。

借用检查器管理生命周期。在编译时,你会收到无效生命周期的通知。这类问题最好在编译时被隔离,而不是在运行时暴露出来。

当生命周期存在歧义时,生命周期注解为借用检查器提供了正确生命周期的提示。如果生命周期的判断是显而易见的,生命周期注解是可选的,借用检查器会自动推导。这叫做生命周期省略。

生命周期特性的好处是提供了一个稳定的内存环境,避免了悬空引用的风险。

无畏并发

无畏并发是Rust的一个重要特性,值得列入主要特性之一。无畏并发为并发编程提供了一个安全的环境。在许多方面,这个安全环境得益于前面提到的特性。例如,Rust的所有权模型在很大程度上消除了并发编程中的竞态条件。

当从顺序编程过渡到并发编程时,通常会进行所谓的硬化过程,以确保多线程代码的安全环境。移除全局变量(作为共享数据)通常是硬化过程中的一步。无畏并发消除了硬化过程的需求。

并发编程常常被认为是编程中的“鬼怪”。它可能增加复杂性,使应用程序变得更难维护。最糟糕的是,并发编程中的问题通常直到运行时才会被发现。无畏并发为并发编程创造了一个更安全的环境。

零开销抽象

零开销抽象是Rust的一个特性。是的,你没看错。因此,它是这里提到的最后一个特性。零开销抽象的政策是,Rust的特性应该在可避免的情况下,不会在运行时引入性能开销。

代际垃圾回收是多个流行的托管语言(如Java、C#和Go)管理动态内存的内在特性。垃圾回收可能是代价高昂且非确定性的。因此,你无法预测垃圾回收何时发生。与之相比,Rust没有垃圾回收模型。完全没有!如本章前面所述,所有权提供了确定性的内存管理,而没有额外开销。这就是零开销抽象的一个例子。

Rust术语

许多技术,包括编程语言,都有自己的术语。熟悉这些术语可以帮助你在与同行或更广泛的Rust社区成员交流时更加得心应手。

对于Rust来说,其核心术语是crate,即“运输箱”。以下是Rust中的一些重要术语,它们将帮助你理解并使用Rust语言。

  • Rust:我们先从最重要的术语开始:“Rust”本身。Rust不是一个首字母缩略词,也不是一个特殊的技术术语。Rust这个名字来源于“铁锈菌”(rust fungi),它是一种强大的病原体,攻击活的植物。

    Rust的原设计者Graydon Hoare曾说过:“我想我给它起名叫Rust是因为菌类。铁锈菌是非常神奇的生物。”

  • Crate:在Rust中,crate是编译单元。常见的crate包括可执行文件、库文件和外部crate。

    • 可执行crate:可执行crate是一个独立的二进制可执行文件,可以独立于其他crate启动。
    • 库crate:库crate提供服务给其他crate,并不能独立执行。
    • 外部crate:外部crate是外部依赖。例如,Crate A引用了Crate B,但Crate B并不在同一个包中,因此Crate B是Crate A的外部crate或依赖。
  • Packages(包) :一个package由多个crate组成,提供一个特定的服务。一个package可以由多个可执行crate组成,并且可能包含一个库crate。

  • Modules(模块) :在Rust中,module类似于其他编程语言中的命名空间。你可以使用模块在crate内部创建层次化的程序结构。模块也有助于避免名称冲突。

  • Cargo:Rust中有多个与cargo相关的实体,这扩展了crate的主题(cargo即在crate中)。

    • Cargo工具:Cargo工具是Rust的包管理器。
    • Cargo.toml:Cargo.toml文件是Rust的清单和配置文件。
    • Cargo.lock:Cargo.lock文件记录了所有依赖项及其具体版本。
    • RS:RS(Rust源代码)是Rust源代码文件的扩展名。

图1.1展示了Rust中各种元素之间的相互关系。

image.png

工具

Rust开发环境提供了多种工具,涵盖了从编译Rust源代码到发布crate的各项服务。理解这些工具将提升你的生产力。这里列出的只是一些重要的Rust工具,书中将介绍更多的工具。

以下是一些重要的Rust工具:

  • Rustup工具:Rustup工具是Rust的安装器,它还安装工具链。你可以通过rustup.rs下载Rust安装器,并按照网站上的说明成功安装Rust。

  • Cargo工具:Cargo是一个多功能工具,主要的角色是包管理器。它还提供编译代码、格式化源代码和创建新crate等辅助功能。

    下面是一个使用Cargo创建库crate的命令:

    cargo new --lib mylib
    
  • Rustc工具:Rustc是Rust的编译器。Rustc可以将Rust源文件(.rs)编译成可执行文件或库二进制文件。

    下面是使用Rustc编译一个简单crate的命令:

    rustc source.rs
    
  • Rustdoc工具:Rustdoc工具将嵌入在Rust源文件中的文档注释编译成帮助文档,输出为HTML格式。

  • Clippy工具:Clippy是一个全面的测试工具,包含多个lints(静态代码检查器)。它帮助识别常见问题和最佳实践,从而优化你的代码。

  • Rustfmt工具:Rustfmt工具用于重新格式化源文件,使其遵循Rust的风格指南。

在Rust环境中,Cargo是核心工具。你可以使用Cargo来完成许多维护环境和包的任务。

关于安全性的说明

在我们现在所处的这个注重安全的世界中,安全编码实践已成为一项必要的要求。几乎没有应用程序能够完全免于安全考虑。在现代计算时代,应用程序无处不在,包括移动设备、物联网设备、可穿戴设备、企业环境和云计算。这大大增加了安全失败的潜在影响。

Rust提供了一个安全的编码环境,减少了攻击面,使得使用Rust进行开发本质上更为安全。攻击者可以识别并可能利用的漏洞更少。

总结

本章介绍了与Rust相关的软件编程范式以及其众多显著特性。

这些不同编程范式的可用性提供了灵活性,并且采用了一种"最佳实践"的方式。以下Rust特性与各种编程模型相辅相成,定义了语言的重要属性:

  • 安全性(Safeness)
  • 所有权(Ownership)
  • 生命周期(Lifetimes)
  • 可靠的并发(Trustworthy Concurrency)
  • 零成本抽象(Zero-cost Abstraction)

现在,你已经对Rust及其工具链有了基础的了解。在下一章,你将完成你的第一个Rust应用程序,并进一步探索重要工具,如Rustc和Cargo。