Rust游戏开发实战

978-7-115-62660-8
作者: 赫伯特·沃尔弗森(Herbert Wolverson)
译者: 米明恒
编辑: 吴晋瑜

图书目录:

详情

本书主要介绍基于Rust语言开发游戏的方法,还介绍了适用于Unity、Unreal等游戏引擎的技巧。 本书先设置开发环境,然后引导读者制作自己的Flappy Bird,借实例讲解Rust语言的基础知识。全书引导读者逐步完成一个《地下城爬行者》(Dungeon Crawler)游戏项目,通过实战帮助读者掌握Rust的相关知识,掌握用Bevy开发游戏的方法,以及在不影响程序调试的情况下运行游戏系统,对所开发的游戏进行优化。 本书适合所有对Rust语言感兴趣的读者阅读,也适合从事游戏开发的读者参考。

图书摘要

版权信息

书名:Rust游戏开发实战

ISBN:978-7-115-62660-8

本书由人民邮电出版社发行数字版。版权所有,侵权必究。

您购买的人民邮电出版社电子书仅供您个人使用,未经授权,不得以任何方式复制和传播本书内容。

我们愿意相信读者具有这样的良知和觉悟,与我们共同保护知识产权。

如果购买者有侵权行为,我们可能对该用户实施包括但不限于关闭该帐号等维权措施,并可能追究法律责任。

版  权

著    [美]赫伯特·沃尔弗森(Herbert Wolverson)

译    米明恒

责任编辑 吴晋瑜

人民邮电出版社出版发行  北京市丰台区成寿寺路11号

邮编 100164  电子邮件 315@ptpress.com.cn

网址 http://www.ptpress.com.cn

读者服务热线:(010)81055410

反盗版热线:(010)81055315

内容提要

本书主要介绍基于Rust语言开发游戏的方法,还介绍了适用于Unity、Unreal等游戏引擎的技巧。

本书先设置开发环境,然后引导读者制作自己的Flappy Bird,借实例讲解Rust语言的基础知识。全书引导读者逐步完成一个《地下城爬行者》(Dungeon Crawler)游戏项目,通过实战帮助读者掌握Rust的相关知识,掌握用Bevy开发游戏的方法,以及在不影响程序调试的情况下运行游戏系统,对所开发的游戏进行优化。

本书适合所有对Rust语言感兴趣的读者阅读,也适合从事游戏开发的读者参考。

致  谢

本书的顺利付梓,离不开我的妻子梅尔·沃尔弗森(Mel Wolverson)的支持,感谢她给予我的支持和爱!

非常感谢我的父母——罗伯特·沃尔弗森(Robert Wolverson)和道恩·麦克拉伦(Dawn McLaren)。他们让我在很小的时候就接触到了计算机,并教会了我基础的编程技能,还忍受我喊了很多年的“看我做的这个东西”。是他们的教育让我热爱学习和教学,受益匪浅。

感谢紫水晶基金会(Amethyst Foundation)的厄伦德·索格·赫根(Erlend Sogge Heggen),是他把我介绍给了Pragmatic Bookshelf出版社。RoguelikeDev是一个非常棒的社区,促使我重回游戏开发领域,并在本书的编写过程中不断为我提供支持。特别感谢Cogmind的作者乔什·葛(Josh Ge)以及Caves of Qud的开发者布莱恩·巴克卢(Brian Bucklew)对整个RoguelikeDev开发社区的鼓励。感谢史蒂夫·科特里尔(Steve Cotterill)在我构思这本书的内容框架时,他给了我一些非常好的建议。最后,特别感谢沃尔特·皮尔斯(Walter Pearce)在Rust语言上给出的帮助。

感谢iZones的肯特·弗罗施尔(Kent Froeschle)和斯蒂芬·特纳(Stephen Turner)一直以来对我的鼓励,还为我安排了弹性的工作时间,耐心地帮我完善写作计划。

感谢耐心、细致地审阅本书的所有技术审校者,他们是巴斯·扎姆斯特拉(Bas Zalmstra)、乔什·斯奈思(Josh Snaith)、弗拉迪斯拉夫·巴蒂连科(Vladyslav Batyrenko)、托马斯·吉伦(Thomas Gillen)、雷姆科·库伊珀(Remco Kuijper)、福里斯特·安德森(Forest Anderson)、奥利维亚·伊夫里姆(Olivia Ifrim)和尤吉斯·巴尔丘纳斯(Jurgis Balciunas)。

最后,感谢编辑塔米·科隆(Tammy Coron)对我的帮助和支持,引导我顺利完成本书的编写。

前  言

Rust是一种系统级编程语言,它既具备与C、C++类似的强大功能,又具备内存安全性,还让并发编程不再令人畏惧,同时能够大幅提升开发效率。它提供了底层硬件开发所需的功能和性能,还提供了一种安全机制来避免很多低级语言易犯的错误。正是由于这些特性,Rust逐渐成了一种非常有竞争力的开发语言,为亚马逊、谷歌、微软以及许多游戏开发公司所应用。

开发游戏是学习Rust的一个很好的方法。不要被那些AAA品质[1]游戏的规模和做工吓倒。小型独立游戏的开发是很有趣的,将游戏开发作为业余兴趣可能会开启你的职业游戏开发生涯,或者其他领域的开发生涯。每个成功的游戏开发者都是从小处着手,逐渐积累技能,直到能够开发自己梦想中的游戏。

[1] AAA品质游戏通常是指由大型工作室开发,有巨额预算资助的游戏。——译者注

本书将通过游戏开发实例引导你学习Rust。在经过一系列案例实践、构建越来越复杂的游戏之后,你将了解如何使用Rust语言进行游戏开发。本书强调务实的“做中学”方法,理论部分篇幅很短,随后便是可供尝试的具体例子。学完本书,你能够掌握Rust语言的基础知识,并为解决更复杂的游戏开发问题做好准备。

读者对象

本书假设你有一些编程经验,并会通过循循善诱的方式介绍Rust和游戏开发的概念。只要你用其他编程语言写过比“Hello, World”更复杂的程序,那么在阅读本书示例时应该会感到非常轻松。

本书适合任何想要学习Rust的读者,包括没有Rust语言基础的人,也非常适合想要尝试游戏开发的Rust开发者。本书并不是单纯的编程语言入门教程,对新入行的(游戏)开发者也可能有所帮助。

本书内容

本书将引导你亲历一个典型的游戏开发过程,并会穿插着讲解Rust的关键概念,力求在构建实际可玩游戏的过程中,让你掌握新知识,增加技能储备。

“第1章 Rust及其开发环境”:Rust之旅由此开启。本章会介绍语言工具链的安装,并在文本编辑器中编写Rust源代码。本章将指引你一步一步地创建“Hello, World”程序,并学习使用诸如Cargo和Clippy之类的Rust工具来提高工作效率。

“第2章 Rust的第一步”:介绍 Rust 开发的基础知识,通过编写一个树屋(treehouse)访客管理系统帮助你提升Rust开发技能。本章涵盖文本输入和输出、使用结构体来组织数据,以及一些Rust核心概念,例如迭代器、模式匹配、if语句、函数和循环。

前两章介绍了制作简单游戏所需的一切知识。从第3章开始,你将正式开始构建游戏。

“第3章 构建第一个Rust游戏”:引导你创建本书的第一个游戏——Flappy Dragon。在此过程中,你会用到前两章学到的知识。

“第4章 设计地下城探险类游戏”:介绍如何规划游戏。本章将介绍如何编写游戏设计文档,从而把粗略的想法转变为一个具有真实可玩性的游戏。你将设计一个Rogue风格的地下城探险类游戏,将粗略的需求逐步细化,并最终得到一个最简可行产品(Minimum Viable Product,MVP)。

“第5章 编写地下城探险类游戏”:开始构建第4章所设计的地下城探险类游戏。本章将介绍随机数、游戏地图的存储结构,以及玩家的交互控制的处理,还将在游戏地图中初步添加与怪兽相关的资源,并介绍如何实现基于图块的图形界面。

“第6章 创建地下城居民”:随着开发的深入,游戏变得越来越复杂。本章会使用实体组件系统(Entity Component Systems,ECS)来控制系统的复杂性、实现代码复用,以及管理游戏中各个实体元素之间的交互关系。本章将使用ECS来实现玩家和怪兽,通过复用不同的系统来减少需要编写的代码数量。在本章结束时,你会得到一个应用了多线程和并发技术的游戏。

“第7章 与怪兽交替前行”:在游戏中添加一个回合制框架,实现玩家和怪兽轮流移动的功能。你将了解到如何设计一个能实现特定游戏规则的游戏框架,并根据游戏的不同环节来切换不同的ECS系统,还将学习如何让怪兽在地图中随意走动。

“第8章 生命值和近身战斗”:为游戏中的实体(玩家和怪兽)赋予生命值,并在玩家角色的上方显示血条。你会了解到如何让怪兽自动搜寻玩家,如何实现一个战斗系统,从而让玩家可以消灭敌人,或者被敌人消灭。

“第9章 胜与负”:增加一个游戏结束画面,以告诉玩家输掉了游戏,当然也会添加游戏胜利的判断逻辑,以及一个祝贺玩家获胜的画面。

“第10章 视场”:在本章之前,游戏玩家的角色是全知全能的——他们可以看到完整的地图。本章介绍视场的概念,以及一种让玩家在探索的过程中逐步熟悉地图的方法。使用ECS系统,能够给怪兽施加相同的视场限制——如果怪兽不知道玩家在哪里,它们就无法“纠缠”玩家。

“第11章 更具可玩性的地下城”:介绍一种新的地图生成算法。本章还会讨论一个更高级的Rust话题——trait,以及如何通过一个通用的程序接口来实现可互换性,这是一个在团队合作开发中非常有用的技能。

“第12章 地图的主题风格”:添加新的地图渲染方法,会用到第11章介绍的trait相关的知识。你可以通过更改地图的图块素材集来把地下城风格变成森林风格或其他风格。

“第13章 背包和道具”:为游戏添加物品功能、背包管理功能,以及升级奖励机制。

“第 14 章 更深的地下城”:将单层地下城升级为向更深处错综蔓延的地下城。本章将介绍如何用数据表格来把玩家的经验等级和游戏难度关联起来。

“第 15 章 战斗系统和战利品”:为游戏增加“战利品列表”功能,以及让玩家在探索地下城的过程中不断获得更高级物品的机制。玩家会找到越来越有趣的各种宝剑——在战斗中宝剑将会体现出各自不同的威力。这里将通过更高级的战利品来平衡逐渐增加的游戏难度,同时让你了解到风险-收益曲线在游戏中的应用。

“第 16 章 最后的步骤和润色”:介绍如何打包并发布前面所编写的游戏,还将介绍一些让游戏变得更加与众不同的方法,并给出进阶学习游戏开发的建议。

本书未涉及的内容

本书侧重于先让你了解实际案例,再向你解释案例中所使用的各种技术的原理,以此讲授理论知识。本书不会深入探讨Rust的各个细节,而是会在引入新概念时告诉你如何去寻找相关的学习资料。

本书不会涉及游戏创意的相关内容,但无论是制作一款伟大的在线对弈游戏还是射击游戏,本书涉及的概念都会让你受益。这些概念很容易应用到其他游戏引擎中,包括Unity、Godot、Unreal和Amethyst等。掌握本书中的相关内容,有助于你更好地开始制作心目中的游戏。

如何阅读本书

如果你是Rust开发新手,那么请按顺序阅读本书的各章内容以及相关的示例。如果你已经比较熟悉Rust语言,那么可以在粗略阅读概述性内容之后直接切入游戏开发部分。即便你是资深的游戏开发者,还是可以从本书中学到很多关于Rust的知识,以及面向数据的设计模式的知识。

不要急于把全书看完,体验学习的过程同样重要。阅读的过程可能会给你带来后续制作游戏的灵感。你不妨在阅读过程中随时记录下想做的事,以及如何用书中介绍的内容去实现这些事。

体例约定

本书的代码以Rust工作区的形式给出,这种做法便于把多个Rust项目放在一起进行管理。书中的代码被划分到如下的目录中:

root
/章节名称
/示例名称
/src --- 示例的源代码
/resources --- 示例所附带的其他文件
Cargo.toml --- 配置文件,用来指导Rust的Cargo构建系统构建和运行示例
/src --- 小程序源码,用来提醒你应该进入某个具体案例所在目录而不是在顶级目录中运行示例
Cargo.toml --- 配置文件,用来告诉Rust的Cargo构建系统工作区是由哪些项目组成的

你可以进入“章节名称/示例名称”目录下,然后通过运行cargo run命令来执行示例代码。

书中的代码会标明所引用的源代码路径,这些路径有时会突然切换到本章的另一个代码目录中。这样的设计可以让你在迭代开发的过程中,每一步都有可以运行的完整示例,从而使你更容易跟上本书的节奏。举个例子,你在某一章中可能会看到出自code/FirstStepsWithRust/hello_yourname项目的代码,而在同一章节稍后的地方又会发现出自code/FirstStepsWithRust/treehouse_guestlist_trim/项目的代码。

在线资源

以下罗列的是一些在线资料,希望对你学习Rust语言有所帮助。

Rust by Example是一本很好的以案例驱动形式来介绍Rust编程语言的书。

The Rust Programming Language [KN19] 通过提供深入的概念讲解和指导来学习Rust的细节。本书支持在线阅读。

Rust标准库文档,它详细描述了Rust标准库中的一切内容。这是一份很好的参考资料,当你忘记标准库中某些组件的使用方法时,请查阅Rust标准库文档。

Reddit的/r/rust和/r/rust_gamedev两个频道提供了优质的学习资源。RoguelikeDev社区则对于开发本书中介绍的地下城类型游戏非常有帮助。Reddit中还有很多指向Discord论坛的链接,你可以在那里结识很多乐于分享的技术人。

小结

无论你想专注于学习Rust还是想涉足游戏开发领域,本书都可以起到很好的帮助作用。无论是完整地开发一款新游戏,还是参与一部分功能的开发,都是令人兴奋的事情,运行游戏,看到所创建的人物动起来,那一刻的快乐是无以言表的。让我们先着手搭建Rust开发环境,然后直接进入第一行Rust代码的编写。

  作者自述:我的游戏开发经历

我的成长非常幸运。我父亲教过计算机相关课程,也教了我许多计算机知识。有一天,他带了一台BBC Micro Model B计算机回到家,那真是一个改变我命运的日子!那台计算机有32KB的RAM、彩色显示器,还能从磁带驱动器加载程序!——真是不可思议。我的父母给我买了很多游戏,包括Repton这样的益智游戏和各种复刻的街机游戏,让我的游戏库不断扩大。没过多久,我就萌生了制作游戏的想法,于是父亲便耐心地引导我学习BASIC语言。客观来说,起初我编写的游戏非常糟糕,但这也无关紧要,因为我自己做出了一些东西,并且体会到了向朋友们展示成果的快乐。

早期的BASIC游戏开发经历让我走上了一条有趣的道路。之后我学习了Pascal语言,后来又学习了C和C++语言。我先后学会了为Windows和Linux开发游戏,后来参与完成了几个小组项目,并找到了一份编写商业和网络软件的工作——这很大一部分要归功于我的游戏开发经历。后来,我接触到了Rust语言,发现它是最适合我的。

虽然这本书主要介绍的是Rust和游戏开发方面的内容,但我更希望它能鼓励你去创作一些有趣的东西。

第一部分 初识Rust

在这一部分,你将了解Rust的基础知识,并编写自己的第一个游戏——Flappy Dragon

第1章 Rust及其开发环境

在本章中,你需要先下载并安装Rust及其必要的工具,然后熟悉自己所搭建的开发环境,并在其中创建并运行自己的第一个Rust程序。

你将了解到Cargo——它是Rust生态中的构建工具,如瑞士军刀一般用途广泛。将Cargo与rustfmt工具结合起来使用可以帮助开发者自动规范代码格式,而将其与Clippy工具结合起来使用则可以帮助开发者避免代码中的很多常见错误。Cargo还可以帮助开发者查找并安装依赖项,并确保其使用最新版本。

在阅读本章的过程中,你将逐步熟悉Rust语言以及一些可以提升开发体验的工具。到本章结束时,你应该做好进入Rust世界并在其中纵横驰骋的准备了。

1.1 安装Rust

Rust语言需要开发者在自己的计算机上安装一套工具链(toolchain)。这套工具链包含Rust编译器,以及能够降低Rust语言使用难度的各种工具。安装这套工具链最简单的方法是访问RustUp网站。RustUp会检测你当前所使用的操作系统,并给出适合当前平台的安装指引。打开一个Web浏览器并访问RustUp官方网站,你将看到一个图1-1所示的页面。

(a) Microsoft Windows上的RustUp

(b) Linux、OS X和类UNIX操作系统上的RustUp

图1-1

下一步操作会因操作系统的不同而有所差异。RustUp网站将通过一个指南来手把手引导开发者操作。

如何打开命令提示符或终端[1]

 在Windows系统上,你可以按下+组合键,在弹出的运行对话框中输入cmd后按回车键。

在macOS系统上,你可以在Finder中搜索Terminal并且运行它。

[1] 本书将涉及命令提示符(Command Prompt)、终端(Terminal)和控制台(Console)三种不同的表达方式。它们曾有各自的含义,但如今它们之间的差异逐渐消失了。在本书中,上述三种表达方式指的都是可以输入命令的窗口。在Windows系统中,我们通常称之为命令提示符,而在Linux、macOS X等系统中则通常称之为终端或控制台。——译者注

1.1.1 在Microsoft Windows上安装Rust

如果你是Microsoft Windows的用户,则可以下载并运行rustup-init.exe这个文件。RustUp可能会在继续下一步之前给出提示,要求你先安装C++构建工具。Microsoft Windows并没有附带开发工具,而且Windows平台上的开发工具不是开源的——受法律限制,RustUp不能直接把这些开发工具提供给开发者。假如你看到了这个提示信息,请执行如下操作。

(1)访问Visual Studio的下载页面。

(2) 在Tools for Visual Studio 2019处,单击下载Build Tools for Visual Studio 2019并运行安装包。

(3)根据屏幕上的提示完成C++构建工具的安装。

1.1.2 在其他操作系统上安装Rust

如果你使用的是由UNIX衍生出来的操作系统,例如macOS X或者Linux,则RustUp将为你展示一条命令的文本和一个复制按钮。你要做的就是将命令复制到剪贴板,打开终端窗口,然后把命令行粘贴到终端窗口中——按下回车键,安装过程就会开始。

1.1.3 完成安装

RustUp安装工具的屏幕输出信息很冗长,它会确切地告诉使用者这个安装程序将要做的事情:

Welcome to Rust!
 
This will download and install the official compiler for the Rust
programming language, and its package manager, Cargo.
 
RustUp metadata and toolchains will be installed into the RustUp
home directory, located at:
 
  C:\Users\herbe\.rustup
This can be modified with the RUSTUP_HOME environment variable.
 
The Cargo home directory located at:
 
  C:\Users\herbe\.cargo
 
This can be modified with the CARGO_HOME environment variable.
 
The cargo, rustc, rustup and other commands will be added to
Cargo's bin directory, located at:
 
  C:\Users\herbe\.cargo\bin
 
This path will then be added to your PATH environment variable by
modifying the HKEY_CURRENT_USER/Environment/PATH registry key.
 
You can uninstall at any time with rustup self uninstall and
these changes will be reverted.

You are then presented with installation options:

Current installation options:
  
  default host triple: x86_64-pc-windows-msvc
      default toolchain: stable (default)
                 profile: default
  modify PATH variable: yes
 
1) Proceed with installation (default)
2) Customize installation
3) Cancel installation

大多数情况下,用户只需要输入1然后按回车键即可。如果用户需要修改Rust的安装位置,则需要输入2,然后按照屏幕上的提示去操作。值得注意的一点是,在自己的操作系统账户下安装Rust并不需要系统管理员权限。

一旦进入真正的安装环节,RustUp会下载若干个Rust包并把它们安装在当前这台计算机上。RustUp会修改path环境变量,如果打算在安装完成后继续使用当前的终端窗口,则最好把它重启一次[2]。恭喜你!Rust已安装完成,并可以使用了。

[2] 重新启动Shell,从而加载最新的环境变量。——译者注

安装完成后,请花一点时间来确保它能够正常运行。

1.1.4 验证安装是否成功

结束安装过程以后,请打开一个新的终端或命令提示符窗口。在命令提示符下输入rustup-V,然后按下回车键:

 rustup -V
  rustup 1.22.1 (b01adbbc3 2020-07-08)

上述所列出的版本号、git版本哈希值(hash)以及日期会和你实际操作时看到的有所差异。如果在输出中看到相关的软件包已经安装,就表示整个安装过程是成功的。

现在,从输出来看,Rust能够在这台计算机上运行,接下来你需要通过运行Hello World程序验证运行。

1.1.5 测试Rust能否正常使用

Rust可以通过一条简单的命令来构建一个输出Hello World的程序(参见1.4节)。现在,我们执行如下步骤,验证一下Rust是否真的可以在这台计算机上运行。

(1)创建一个新的文件夹,用来保存Rust项目。

(2)打开一个命令提示符窗口或者终端窗口。

(3)使用cd命令进入上述创建的文件夹,例如cd Rust

(4)输入 cargo new testrust ENTER

(5)输入cd testrust ENTER,切换到新建的testrust文件夹。

(6)输入cargo run ENTER,运行新创建的程序。

你将看到类似下面的输出:

 cargo run 
   Compiling hello v0.1.0 
      Finished dev [unoptimized + debuginfo] target(s) in 0.85s 
        Running `C:\Pragmatic\Book\code\target\debug\hello.exe` 
Hello, world! 

至此,我们就有了一个可以正常使用的Rust工具链,接下来需要了解如何保持工具链更新到最新的版本。

1.1.6 版本更新

Rust每隔6个星期会发布一个次要版本更新。次要版本更新包括缺陷修复和功能增强,这些改动不会影响已有代码的编译。Rust的主要版本更新每两到三年出现一次,这些更新可能会引入对Rust语言的大规模修改。Rust语言的核心开发者们在保持兼容性方面非常谨慎。如果某些功能要发生重大变更,那么开发者在编译时会看到deprecation warnings字样的警告,这些警告提示会存在相当长的一段时间。开发者可以通过rustup check命令来检查是否有新的版本发布,如图1-2所示。

图1-2所示的内容可能和你实际看到的有所不同,也可能没有可用的更新。如果有可用的新版本,则可以通过在命令提示符或终端里输入rustup update来安装它们。

恭喜你!Rust已经成功安装并顺利运行,你也了解了如何使它保持最新的版本。下一步要做的是搭建自己的开发环境。

图1-2

1.2 安装并配置开发环境

编写Rust代码的大部分时间里都需要使用文本编辑器。文本编辑器就像衣服一样,大多数人会用到它们,但在“哪一种是最好的”这个问题上,没有人能达成一致。此外,正如衣服一样,开发者会花费足够多的时间来寻找一款适合自己的编辑器,并且当它不再“合身”的时候,更换一个新的。如果你之前写过代码,那么大概率已经有一款自己喜欢的文本编辑器或者IDE了。

最好选择一款支持Rust的编辑器,语法高亮功能可以让阅读代码变得容易很多,自动补全功能和集成的调试器也会非常有用。目前,编辑器有很多选择,其中一部分选择如下所示。

从最简易的种类说起,Kate、Notepad++以及GEdit都可以胜任编写Rust代码的工作,只不过除了语法高亮,不具有其他的语言特性。

EMACS、Vim或Neovim都可以与Rust Analyser以及调试器进行集成,Rust Analyser是Rust的语言服务器[3]

[3] 语言服务器(Language Server)是一种后台程序,可以帮助编辑器实现特定语言的语法检查、代码跳转等功能。——译者注

JetBrains开发了CLion和IntelliJ,二者都可以较好地和Rust集成。

微软的Visual Studio Code,配上Rust AnalyzerCodeLLDB两个插件,可以和Rust实现非常好的集成。

Sublime Text可以与Rust集成。

寻找一款用起来顺手的编辑器或者IDE。一旦安装并配置好了开发环境,下一步你就应该了解Rust是如何组织和管理一个项目的了。

1.3 用Cargo管理项目

Rust附带了一个名为Cargo的工具,它可以帮助开发者完成与这门语言相关的各种日常操作。从创建全新的项目到为已有程序下载依赖包,Cargo可以帮助你处理其中所有的事情,可以运行所编写的程序、调用其他工具来修正代码的书写风格,以及发现常见的错误。Cargo好比一把“瑞士军刀”,堪称“万能”应对工具。

Cargo的名字来源于Rust代码的组织结构。Rust程序被称为crate,Cargo管理一系列的crate[4]。在本节中,我们将引导你创建第一个Rust项目,并探索可以用Cargo做什么。

[4] crate原意是盛放物品的板条箱,cargo的原意是由轮船等运输的大宗货物,因此cargo由很多crate组成。——译者注

你需要做出的第一个决定是:要把Rust项目存放在哪里。

1.3.1 为代码选择一个主目录

你需要决定把各个Rust项目存放在什么位置。在Windows系统上,笔者会使用c:\users\herbert\rust这个目录,而在Linux系统上则会使用/home/herbert/rust这个目录。实际上,你可以把项目放在任何自己喜欢的地方,但最好易于查找、易于记忆并且易于在键盘上输入。

如果项目的主目录还不存在,就创建一个。有了选定的主目录,你就可以向里面添加项目了。

1.3.2 用Cargo来开启一个新项目

每个新的项目都是从一个空的crate开始的,为了创建一个新的项目,你需要执行如下操作。

(1)打开一个终端窗口或者命令提示符窗口。

(2)进入为Rust代码选定的主目录中(使用cd命令)。

(3)不要自己手工为新项目创建子目录——Cargo会帮开发者自动创建一个。

(4)输入 cargo new [project name]

(5)至此,Rust已经创建了一个名为[project name]的子目录,执行 cd [project name]命令就可以进入子目录。

例如,笔者为了在选定的rust目录下创建一个名为Hello的项目,则会使用:

 cd c:\users\herbert\rust
 cargo new hello
  Created binary (application) `hello` package

为crate使用snake_case命名风格

 开发者可以为程序或者crate起任何名字,但最好使用snake_case风格的命名法——用下画线作为两个单词的分隔符。如果用户创建了一个名为MyCool-Thing的库,那么在项目中引用这个库的时候会遇到一些麻烦。

选择一个好的名字是一件很困难的事情。但是别担心,后续你可以通过修改Cargo.toml文件来更换名称。有个笑话是这么说的:“计算机科学领域只有两个难题,那就是命名和缓存失效,以及少算了一个1的错误”。

启动文本编辑器或者IDE,并在其中打开刚刚建立的项目目录。Cargo已经在里面创建好了下列的文件和文件夹。

(1)Cargo.toml:项目的“元数据”,用来描述当前项目。

(2)src文件夹:用来存放用户编写的代码。

(3)src/main.rs:一个短小的代码文件,包含了向终端输出“Hello, World!”所需要的代码。

cargo new命令已经创建了“Hello, World”程序——就像之前测试Rust是否正常运行那样。接下来,你将近距离观察一下在这个样例程序里面发生了什么。

1.3.3 运行Hello,World

首先要做的是打开一个终端窗口并进入项目目录中。你可以通过输入cargo run命令来运行这个新编写的程序:

 cargo run
      Compiling hello v0.1.0 (C:\Pragmatic\Book\code\InstallingRust) 
        Finished dev [unoptimized + debuginfo] target(s) in 1.18s
         Running `target\debug\hello.exe`
Hello, world!

如果仔细观察刚创建的项目,你会注意到有一个.git文件夹出现在项目里。Cargo可以帮助开发者把git或者其他版本控制系统集成到项目里面。

1.3.4 与版本控制系统的集成

版本控制系统(Version Control System, VCS)

 版本控制系统是一类非常有用的软件。一旦用户提交了文件,VCS就会追踪文件的变化并会存储项目中每一个文件的每一个版本。用户可以浏览历史记录,查找哪些变更破坏了哪些功能,或者在开发进入死胡同时恢复到先前的某一个版本。

使用版本控制软件是一个很明智的想法。git是十分受欢迎的一款软件,它原本是Linus Torvalds为了Linux的开发而特地编写的一个工具。其他流行的解决方案包括Subversion、Mercurial以及Perforce等。

git也可以和GitHub进行集成。Rust生态中的很多内容都可以在GitHub上找到,很多开发者也会在简历中写上自己的GitHub链接。

当使用cargo new来初始化一个新项目时,系统会顺带为其创建一个git仓库。有关git使用方法的介绍超出了本书的范围(那可能——而且必定——是一个能写一本鸿篇巨制的话题)。

如果开发者不想使用git,则需要对cargo new命令做小小的扩展:

cargo new --vsc=none [project name]

接下来,我们将深入Hello, World项目,并介绍一些Rust的基础知识。

1.4 创建第一个Rust程序

刚接触一门新的编程语言时,要先对其进行仔细的审视。一种广受欢迎的方法是创建一个能向用户输出“Hello, World”问候消息的程序。比较不同语言实现“Hello, World”程序的异同,是在不深入细节的情况下,获得某种编程语言在语法上直观感受的好方法。Hello World Collection项目提供了578种不同编程语言的“Hello, World”程序。

当使用cargo new开始一个新项目时,Rust会为开发者创建一个“Hello, World”程序,同时也会创建出一些必需的元数据,从而使得Cargo可以运行开发者编写的程序。

1.4.1 Cargo的元数据

打开在“Hello”项目中创建的Cargo.toml文件:

InstallingRust/HelloWorld/Cargo.toml

[package] 
name = "Hello" 
version = "0.1.0" 
authors = ["Your Name"] 
edition = "2018" 
# See more keys and their definitions at 
# https://doc.rust-lang.org/cargo/reference/manifest.html 
 
[dependencies]

这个文件描述了整个程序的基本信息,以及如何构建这个程序。它采用TOML(Tom's Obvious, Minimal Language)格式,可以把关于crate的各种信息以不同小节的形式进行组织和存储。[package]小节描述当前crate——如果把当前crate公开发布,这些信息将用于向它的潜在用户介绍自己。这个小节具有扩展性,从而能包含关于当前项目的很多信息。

Cargo已经创建好了运行“Hello, World”所需的一切,所以如果不想更改任何信息,则无须编辑Cargo.toml文件。其中的默认值如下。

(1)name:程序的名称,在这个例子中是“Hello”。它的默认值来自调用cargo new命令时所提供的名称。在编译程序时,这个名称将作为编译后输出文件的文件名。在Windows上,hello变为hello.exe。在类UNIX系统上,输出文件被命名为hello

(2)version:项目的版本号。Cargo将其初始值设定为0.1.0。只有当需要发布crate的一个新版本时才需要更新版本号。此外,当开发者认为取得了很大进展并需要明确指出这种进展时,也可以更新版本号。在1.8节中,我们将介绍Rust的语义版本控制。就现阶段而言,保持版本号是0.x.y这种形式即可。每一位数字都可以超过10——0.10.0这种写法是没有问题的。

(3)authors:一个列表,可以用一对方括号来表示。它可以包含用逗号分隔的一系列作者的名字——每个名字都写在一对双引号中。如果用户已经配置好了git,则姓名和邮件地址会自动从git中获取。

(4)edition:该项目所使用的Rust的主版本号。它的默认值总是当前最新的版本,在编写本书时,默认值是2018。不同的大版本之间允许引入巨大的语法变化,这可能会使得老旧的程序无法编译。指定edition参数可以告诉Rust编译器哪些语法规则是可以使用的。

现在,元数据准备就位,接下来我们可以进入主程序的源代码了。

1.4.2 Hello,World程序

打开“Hello”项目下src/main.rs文件。Cargo已经自动编写了必要的程序源代码,以在终端上显示出“Hello, World”。

接下来,请逐行仔细阅读这个程序:

InstallingRust/HelloWorld/src/main.rs

fn main() {
❷      println!("Hello, world!"); 
    }

main函数。main是一个特殊的函数,标记了整个程序的入口点。

定义main函数的语法如图1-3所示。

图1-3

println!宏和字符串字面量,相关内容参见下文的“打印文本”。

Rust代码中有很多花括号({..}),这些花括号代表了作用域。作用域表示具有紧密联系的一组代码。在某个作用域中创建的变量只能存在于这个作用域内——它们不会逃逸到作用域外,而且当作用域结束时,这些变量会被自动清除掉。在这个例子中,打印“Hello, World”是在main函数的作用域中发生的。

main函数是一个比较特殊的函数,扮演着Rust程序入口点的角色。无论程序中各个函数的排列顺序是什么样的,main函数总是第一个运行。

打印文本

main函数的函数体包含了如下一行代码:

println!("Hello,world!");

感叹号标记代表了println!是一个(macro)。Rust的宏系统非常强大——它允许使用一些在常规函数中不能使用的语法。这会使得宏的用法与众不同——因此,Rust在宏的名称里面加入了一个感叹号,用来提示当前正在调用的是一个宏。println!是一个非常灵活的宏,支持很多不同的显示格式。当前的这个例子并不涉及这些灵活的扩展功能选项,只需要打印出一些文字即可。

Hello,world!”是一个字符串字面量(string literal)。之所以称之为“字面量”,是因为它表示的是写在双引号之间的原始文字,而且被存储到程序中[5]。你可以在这里写入任何其他的文本来替换掉默认的“Hello,world!”。它还支持Unicode。“Привет, мир”和“こんにちは世界”都是可以显示在屏幕上的合法的字符串字面量,甚至emoji表情也是可以在这里使用的。

[5] C++涉及较多的字面量,可以查阅相关资料进行了解。“存储到程序中”指的是在编译后位于二进制文件的静态段中,而不是在数据段中。——译者注

慎用难以输入的符号

 能够随意使用各种符号是很好的一件事,但是注意不要做过头。笔者参加过一个项目,其中很多地方使用了美妙的数学符号,阅读起来令人愉悦,letθ=π*Δ这样的写法是对底层数学公式很自然的表述。但是当需要对代码进行修改时,修改符号就变得很棘手了,因为很多符号在键盘上都找不到。正是出于这个原因,Rust对函数名和变量名中可以使用的符号做了限制。

1.5 用Cargo来构建、检查并运行项目

在前面的1.3.3节中,我们已经用Cargo运行了一个项目。除此之外,Cargo提供了一些可以用来和程序交互的其他功能,例如,可以通过输入cargo help来查看全部功能的列表,或者输入cargo [command] --help来查看指定命令的各个选项的详细说明。用户可以实现如下操作。

(1)通过输入cargo check来快速检查项目是否可用。这将检查当前项目及其依赖项目中的基础错误。这样做通常会比完整构建一次整个项目快很多。

(2)用cargo build来编译当前项目——但是并不运行。

(3)用cargo clean来删除整个target目录(该目录是存放编译后输出文件的位置)。

Cargo还提供了一些选项来让用户自己控制构建程序时的参数。

调试构建与发布构建

当执行cargo run或者cargo build时,项目都是在调试模式下构建的。该模式下只有很少的优化,因此程序的运行速度会比正常水平慢很多。这样做可以让调试工作变得更简单,也可以让Rust告诉用户发生问题的精确位置——但代价是程序的运行速度会变慢。此外,它还会产生“调试信息”——这是一种调试工具可以读取的数据,通过它可以把错误信息和程序源代码的行号关联起来。这会导致编译出来的程序变得冗长。

你可以通过cargo run --release命令来实现在发布模式下编译并运行程序。编译器会应用多种优化算法,并且不占用额外空间来支持调试器的工作。用户会得到一个运行速度快很多并且体积小很多的程序,但是它会令排查错误变得困难重重。因此,只有真的需要发布程序时,才会再使用发布模式编译。

1.6 修正代码格式

当需要把代码分享给其他人时,标准统一的代码格式和布局会带来很多好处。Rust官方网站虽然给出了代码风格的指导手册,但里面的内容并不容易记住。为此,Rust提供了一个格式整改工具来帮助开发者遵循代码格式标准。cargo fmt命令将会修改源代码以使其遵循Rust的代码风格指导书推荐的格式。

假设你在匆忙中将“Hello,World!”程序都写到了一行代码中:

fn main() { println!("Hello, world!"); }

上述程序可以编译和运行,但它和官方推荐的代码风格有很大差异。如果将这样的代码分享给同事,或者将其作为开源项目的一部分,那么很有可能会收到关于代码格式方面的反馈建议。你可以通过运行cargo fmt来把这段精练的代码恢复为指导书推荐的格式。

cargo fmt在运行时会修改整个项目的格式。笔者建议你在编写代码的过程中定期运行它,始终保持项目中的代码风格一致。如果你不喜欢默认的风格,也可以配置其他选项。

经过整改后的格式看起来舒服了很多:

fn main() {
    println!("Hello, world!");
}

除了格式问题,Rust还提供了帮助你在代码内容中寻找缺陷的工具。

1.7 用Clippy来发现常见错误

Rust提供了一个叫作Clippy的工具,可以在用户编码的过程中给出提示和指导。你随时可以在终端里输入cargo clippy,它会给出一份关于当前项目的建议列表。Clippy给出的很多警告信息同样会在编译代码时出现在编译器的输出里。大多数支持Rust的开发环境能够和Clippy集成,这样就可以在编写代码的过程中给出实时的警告和提示。

接下来,我们演示如何用Clippy来修正一个简单的项目。进入此前选定的代码主目录,输入cargo new clippy来创建一个新的项目(见1.3.2节)。编辑src/main.rs文件使其包含下列内容:

InstallingRust/Clippy/src/main.rs

fn main() {
     let MYLIST = [ "One", "Two", "Three" ];
     for i in 0..3 {
          println!("{}", MYLIST[i]);
     }
}

在终端窗口中输入cargo run来运行这段程序,你会看到如下的输出(还有关于代码的若干条警告):

 cargo run 
  One 
   Two 
   Three

这段程序可以良好运行,但是仍然有改进空间,因为它忽视了Rust提供的一些很好用的语法特性以及命名规范。这都是Clippy可以帮助开发者解决的一些常见问题。只要在终端里输入cargo clippy命令,就可以得到一个建议列表。其中的第一个警告是:

Checking clippy v0.1.0 (C:\Pragmatic\Book\code\InstallingRust\Clippy)
warning: the loop variable `i` is only used to index `MYLIST`.
--> src\main.rs:3:14
  |
3 |     for i in 0..3 {
  |              ^^^^
  |
  = note: `#[warn(clippy::needless_range_loop)]` on by default

Clippy还建议创建一个网页,以更加详细地解释这一警告。

第二个警告是:

warning: variable `MYLIST` should have a snake case name 
 --> src\main.rs:2:9 
  | 
2 |       let MYLIST = [ "One", "Two", "Three" ]; 
  |           ^^^^^^ help: convert the identifier to snake case: `mylist` 
  = note: `#[warn(non_snake_case)]` on by default

Clippy在代码中发现了如下两个问题。

(1)代码风格错误:Rust语言约定变量名应该采用snake_case的命名方式,应该用my_list来取代MYLIST

(2)代码选择不恰当:没有必要通过下标来遍历列表。Rust提供了一套迭代器机制来避免在使用索引编号时可能出现的错误(列表很有可能会被修改,而当列表中元素的个数发生改变时,开发者很可能会忘了去更新循环的遍历范围)。Clippy在帮助Rust新手发现了这个常见错误的同时,也给出了一些用于替换原有代码的参考代码。

下面我们修改一下这两个错误。

(1)用ranged-for循环来替换掉普通的for循环。Clippy的提示已经给出了修改说明。这个问题的详细内容参见2.5节。

(2)用文本编辑器的查找替换功能把所有MYLIST替换为my_list

修正后的程序如下所示,它能够输出和之前一样的结果——但是Clippy检测不到错误了:

InstallingRust/ClippyFixed/src/main.rs

fn main() {
     let my_list = [ "One", "Two", "Three" ];
     for num in &my_list {
          println!("{}", num);
     }
}

让Clippy“吹毛求疵”

如果你觉得Clippy发现的错误还不够多,那么可以通过配置让Clippy在检查中变得更严格。在main.rs文件的第一行添加如下内容:

#![warn(clippy::all, clippy::pedantic)]

犹如“吹毛求疵”一般严格的检查在编写代码时很有帮助——特别是想把代码分享给别人的时候。Clippy还提供了一些更为严格的检查选项,但是在通常情况下并不建议使用。此外,一些还处于开发阶段的Clippy规则之间有时会发生冲突,它们给出的建议有时也不可靠。普通级别或“吹毛求疵”级别的检查是经过正确性验证的,这通常就是你所需要的。

请相信Clippy

 Clippy是一个忙碌的家伙——它是一个对所有代码都指指点点的“专横”的小吉祥物。笔者刚开始使用Clippy时,发现自己很讨厌Clippy给出的建议——特别是那些建议会带来巨大的修改工作量。但是在使用Rust工作一段时间并遵循Clippy给出的建议以后,笔者就很少再会写出能够令Clippy抱怨的代码了。Clippy是一个辅助学习的工具,可以帮助用户避免错误。

把Clippy调整到“咬文嚼字”级别是一种备受虐待的练习过程,但也的确是改进代码的好方法。

1.8 用Cargo进行包管理

Cargo可以为开发者安装依赖项。在crate.io上有越来越多的免费crate(Rust中对软件包的称呼),Cargo使得安装和使用这些crate,以及发布自己的crate,都变得很简单。

你可以通过输入cargo search [search term],或者访问crate网站来查找可用的crate。例如,以bracket-terminal为关键词进行搜索可以得到如下的结果:

 cargo search bracket-terminal
 bracket-terminal = "0.7.0"   # ASCII/Codepage 437 terminal emulator with a 
                                 game loop. Defaults to OpenGL, also support
                                 Amethyst,…

搜索功能也会检索crate的描述信息。例如,你需要寻找一个“slot map”,就可以得到多个搜索结果:

 cargo search slotmap
  slotmap = "0.4.0"                                     # Slotmap data structure 
   beach_map = "0.1.2"                                   # Implementation of a slotmap

在找到希望使用的crate以后,你需要将其添加到Cargo.toml中。在[dependencies]小节下添加一行并写上依赖的名字和版本号:

[dependencies]
bracket-lib = "0.8.0"

这里的版本号采用了语义化版本号的规范,就像在当前项目中所使用的版本号一样。语义化版本号所表达的含义如下。

(1)第一位数字表示“主”版本。某个crate一旦发布,就要尽力保证不做破坏兼容性的修改,从而保证主版本号不用增长。0号版本是一个特例。主版本号为0的crate处于预发布(pre-release)状态——它们可以做出破坏兼容性的修改。

(2)第二位数字表示“次”版本。添加新功能但同时保证不破坏兼容性的改动通常会导致次要版本号的增长。

(3)第三位数字表示修订号。对于一个缺陷的快速修复通常会导致修订号的增长。

用户可以通过一些限定符来实现对所使用的crate的版本号进行细粒度控制。

(1)=0.8.0将只使用0.8.0这个版本,任何高或低的版本都不行。

(2)^0.8.0将使用任何版本号等于或大于0.8.0的版本,但只能在0.x这个范围内使用[6]

[6] 只能在主版本号为0.x的情况下使用。——译者注

(3)~0.8.0将使用任何次要版本号大于0.8.0的版本。如果有新版本出现,则会自动升级,即使升级会破坏crate的API兼容性。

除版本号外,还有一些其他的选项可供配置。你可以指定一个版本的来源:它可以来自crate.io,可以来自一个git仓库的地址,甚至来自一个存放在当前计算机上的crate的本地路径。举个例子,假设你想使用GitHub版本的bracket-lib库,则可以按如下方式指定:

[dependencies]
bracket-lib = { git = "▓▓▓▓▓▓//github ▓▓▓▓/thebracket/bracket-lib" }

crate还提供了特性开关(feature flag),这使得crate能够提供可选功能。例如,bracket-lib可以被配置成使用Amethyst作为其后端,而不是使用OpenGL。用如下方法来开启这样的特性:

[dependencies]
bracket-lib = {
    git = "https://github.com/thebracket/bracket-lib",
    default-features = false,
    features = [ "amethyst_engine_vulkan" ]
}

你可以通过从Cargo.toml文件中删除对应条目的方法来为项目删除一个依赖项。运行cargo clean,则会把这些依赖从这台计算机上彻底删除[7]。在3.1.2节中,你将用到Cargo的依赖项。

[7] 从Cargo.toml中删除依赖项时仅表示项目不再使用该依赖了,但并不会删除已经下载到本地计算机的依赖项。如果希望释放存储空间,则需要在Cargo.toml中删除依赖项之后,再执行cargo clean来彻底将其删除。——译者注

1.9 小结

至此,你完成了Rust的安装,并且对其所提供的工具有了一定的了解。你尝试编写了第一个Rust程序,并且有了一个舒适的文本编辑环境。在第2章中,你将把这些知识投入实际应用,并着手编写其他Rust程序。

相关图书

仓颉编程快速上手
仓颉编程快速上手
深入浅出Go语言编程从原理解析到实战进阶
深入浅出Go语言编程从原理解析到实战进阶
Go语言编程指南
Go语言编程指南
JUnit实战(第3版)
JUnit实战(第3版)
Scala速学版(第3版)
Scala速学版(第3版)
Kafka实战
Kafka实战

相关文章

相关课程