这是用户在 2024-8-25 23:57 为 https://doc.rust-lang.org/book/print.html 保存的双语快照页面,由 沉浸式翻译 提供双语支持。了解如何保存?

The Rust Programming Language
Rust 编程语言

by Steve Klabnik and Carol Nichols, with contributions from the Rust Community
作者:Steve Klabnik 和 Carol Nichols,来自 Rust 社区的贡献

This version of the text assumes you’re using Rust 1.78.0 (released 2024-05-02) or later. See the “Installation” section of Chapter 1 to install or update Rust.
此版本的文本假设您使用 Rust 1.78.0(2024 年 5 月 2 日发布)或更高版本。请参阅第 1 章的“安装”部分,忽略安装或更新 Rust。

The HTML format is available online at https://doc.rust-lang.org/stable/book/ and offline with installations of Rust made with rustup; run rustup doc --book to open.
HTML 格式可以在https://doc.rust-lang.org/stable/book/上在线获取,也可以通过rustup安装 Rust 来离线获取;运行rustup doc --book以打开。

Several community translations are also available.
还提供多种社区翻译

This text is available in paperback and ebook format from No Starch Press.
No Starch Press 提供本文的平装本和电子书格式

🚨 Want a more interactive learning experience? Try out a different version of the Rust Book, featuring: quizzes, highlighting, visualizations, and more: https://rust-book.cs.brown.edu
🚨 想要更具互动性的学习体验吗?尝试 Rust Book 的不同版本,其特色包括:测验、突出显示、可视化等等https://rust-book.cs.brown.edu

Foreword 前言

It wasn’t always so clear, but the Rust programming language is fundamentally about empowerment: no matter what kind of code you are writing now, Rust empowers you to reach farther, to program with confidence in a wider variety of domains than you did before.
情况并不总是那么清楚,但 Rust 编程语言从根本上讲是关于赋权:无论您现在编写什么样的代码,Rust 都能让您走得更远,比以前更有信心地在更广泛的领域进行编程。

Take, for example, “systems-level” work that deals with low-level details of memory management, data representation, and concurrency. Traditionally, this realm of programming is seen as arcane, accessible only to a select few who have devoted the necessary years learning to avoid its infamous pitfalls. And even those who practice it do so with caution, lest their code be open to exploits, crashes, or corruption.
以处理内存管理、数据表示和并发性等低级细节的“系统级”工作为例。传统上,这个编程领域被认为是神秘的,只有少数经过多年学习以避免其臭名昭著的陷阱的人才能接触到。即使那些实践它的人也会谨慎行事,以免他们的代码容易被利用、崩溃或损坏。

Rust breaks down these barriers by eliminating the old pitfalls and providing a friendly, polished set of tools to help you along the way. Programmers who need to “dip down” into lower-level control can do so with Rust, without taking on the customary risk of crashes or security holes, and without having to learn the fine points of a fickle toolchain. Better yet, the language is designed to guide you naturally towards reliable code that is efficient in terms of speed and memory usage.
Rust 通过消除旧的陷阱并提供一套友好、完善的工具来帮助您一路走来,从而打破了这些障碍。需要“深入”到较低级别控制的程序员可以使用 Rust 来实现,而无需承担常见的崩溃或安全​​漏洞风险,也无需学习变化无常的工具链的优点。更好的是,该语言旨在引导您自然地获得在速度和内存使用方面高效的可靠代码。

Programmers who are already working with low-level code can use Rust to raise their ambitions. For example, introducing parallelism in Rust is a relatively low-risk operation: the compiler will catch the classical mistakes for you. And you can tackle more aggressive optimizations in your code with the confidence that you won’t accidentally introduce crashes or vulnerabilities.
已经在使用低级代码的程序员可以使用 Rust 来实现他们的目标。例如,在 Rust 中引入并行性是一个相对低风险的操作:编译器会为你捕获经典错误。您可以在代码中进行更积极的优化,并确信不会意外引入崩溃或漏洞。

But Rust isn’t limited to low-level systems programming. It’s expressive and ergonomic enough to make CLI apps, web servers, and many other kinds of code quite pleasant to write — you’ll find simple examples of both later in the book. Working with Rust allows you to build skills that transfer from one domain to another; you can learn Rust by writing a web app, then apply those same skills to target your Raspberry Pi.
但 Rust 并不局限于低级系统编程。它的表现力和人体工程学足以使 CLI 应用程序、Web 服务器和许多其他类型的代码编写起来非常愉快 - 您将在本书后面找到这两者的简单示例。使用 Rust 可以让你培养从一个领域转移到另一个领域的技能;您可以通过编写 Web 应用程序来学习 Rust,然后将这些相同的技能应用于您的 Raspberry Pi。

This book fully embraces the potential of Rust to empower its users. It’s a friendly and approachable text intended to help you level up not just your knowledge of Rust, but also your reach and confidence as a programmer in general. So dive in, get ready to learn—and welcome to the Rust community!
本书充分展现了 Rust 为其用户提供支持的潜力。这是一本友好且平易近人的文本,旨在帮助您不仅提高 Rust 知识,而且还提高您作为程序员的影响力和信心。因此,投入其中,准备好学习——欢迎来到 Rust 社区!

— Nicholas Matsakis and Aaron Turon
— 尼古拉斯·马萨基斯和亚伦·特隆

Introduction 介绍

Note: This edition of the book is the same as The Rust Programming Language available in print and ebook format from No Starch Press.
注意:本书的这个版本与No Starch Press提供的印刷版和电子书格式的《Rust 编程语言》相同。

Welcome to The Rust Programming Language, an introductory book about Rust. The Rust programming language helps you write faster, more reliable software. High-level ergonomics and low-level control are often at odds in programming language design; Rust challenges that conflict. Through balancing powerful technical capacity and a great developer experience, Rust gives you the option to control low-level details (such as memory usage) without all the hassle traditionally associated with such control.
欢迎阅读《Rust 编程语言》 ,这是一本关于 Rust 的介绍性书籍。 Rust 编程语言可帮助您编写更快、更可靠的软件。在编程语言设计中,高级人体工程学和低级控制常常是不一致的; Rust 挑战了这种冲突。通过平衡强大的技术能力和出色的开发人员体验,Rust 为您提供了控制低级细节(例如内存使用)的选项,而无需传统上与此类控制相关的所有麻烦。

Who Rust Is For Rust 适合谁

Rust is ideal for many people for a variety of reasons. Let’s look at a few of the most important groups.
由于多种原因,Rust 对许多人来说是理想的选择。让我们看看几个最重要的群体。

Teams of Developers 开发团队

Rust is proving to be a productive tool for collaborating among large teams of developers with varying levels of systems programming knowledge. Low-level code is prone to various subtle bugs, which in most other languages can be caught only through extensive testing and careful code review by experienced developers. In Rust, the compiler plays a gatekeeper role by refusing to compile code with these elusive bugs, including concurrency bugs. By working alongside the compiler, the team can spend their time focusing on the program’s logic rather than chasing down bugs.
Rust 被证明是一种高效的工具,可用于在具有不同系统编程知识水平的大型开发团队之间进行协作。低级代码很容易出现各种细微的错误,而在大多数其他语言中,这些错误只能通过经验丰富的开发人员进行广泛的测试和仔细的代码审查来发现。在 Rust 中,编译器扮演着看门人的角色,拒绝编译带有这些难以捉摸的错误(包括并发错误)的代码。通过与编译器一起工作,团队可以将时间集中在程序的逻辑上,而不是追查错误。

Rust also brings contemporary developer tools to the systems programming world:
Rust 还为系统编程世界带来了当代的开发工具:

  • Cargo, the included dependency manager and build tool, makes adding, compiling, and managing dependencies painless and consistent across the Rust ecosystem.
    Cargo 是随附的依赖项管理器和构建工具,使添加、编译和管理依赖项变得轻松且在整个 Rust 生态系统中保持一致。
  • The Rustfmt formatting tool ensures a consistent coding style across developers.
    Rustfmt 格式化工具可确保开发人员之间保持一致的编码风格。
  • The rust-analyzer powers Integrated Development Environment (IDE) integration for code completion and inline error messages.
    rust-analyzer 支持集成开发环境 (IDE) 集成,以实现代码完成和内联错误消息。

By using these and other tools in the Rust ecosystem, developers can be productive while writing systems-level code.
通过使用 Rust 生态系统中的这些工具和其他工具,开发人员可以在编写系统级代码时提高工作效率。

Students 学生

Rust is for students and those who are interested in learning about systems concepts. Using Rust, many people have learned about topics like operating systems development. The community is very welcoming and happy to answer student questions. Through efforts such as this book, the Rust teams want to make systems concepts more accessible to more people, especially those new to programming.
Rust 适合学生和那些有兴趣学习系统概念的人。使用 Rust,许多人了解了操作系统开发等主题。社区非常欢迎并乐意回答学生的问题。通过本书等努力,Rust 团队希望让更多人,尤其是那些刚接触编程的人更容易理解系统概念。

Companies 公司

Hundreds of companies, large and small, use Rust in production for a variety of tasks, including command line tools, web services, DevOps tooling, embedded devices, audio and video analysis and transcoding, cryptocurrencies, bioinformatics, search engines, Internet of Things applications, machine learning, and even major parts of the Firefox web browser.
数百家大大小小的公司在生产中使用 Rust 来执行各种任务,包括命令行工具、Web 服务、DevOps 工具、嵌入式设备、音频和视频分析和转码、加密货币、生物信息学、搜索引擎、物联网应用程序、机器学习,甚至是 Firefox 网络浏览器的主要部分。

Open Source Developers 开源开发人员

Rust is for people who want to build the Rust programming language, community, developer tools, and libraries. We’d love to have you contribute to the Rust language.
Rust 适合那些想要构建 Rust 编程语言、社区、开发工具和库的人。我们很高兴您为 Rust 语言做出贡献。

People Who Value Speed and Stability
重视速度和稳定性的人

Rust is for people who crave speed and stability in a language. By speed, we mean both how quickly Rust code can run and the speed at which Rust lets you write programs. The Rust compiler’s checks ensure stability through feature additions and refactoring. This is in contrast to the brittle legacy code in languages without these checks, which developers are often afraid to modify. By striving for zero-cost abstractions, higher-level features that compile to lower-level code as fast as code written manually, Rust endeavors to make safe code be fast code as well.
Rust 适合那些渴望语言速度和稳定性的人。所谓速度,我们指的是 Rust 代码运行的速度以及 Rust 允许您编写程序的速度。 Rust 编译器的检查通过添加功能和重构来确保稳定性。这与没有这些检查的语言中脆弱的遗留代码形成鲜明对比,开发人员通常不敢修改这些代码。通过努力实现零成本抽象、与手动编写代码一样快地编译为较低级别代码的高级功能,Rust 致力于使安全代码也成为快速代码。

The Rust language hopes to support many other users as well; those mentioned here are merely some of the biggest stakeholders. Overall, Rust’s greatest ambition is to eliminate the trade-offs that programmers have accepted for decades by providing safety and productivity, speed and ergonomics. Give Rust a try and see if its choices work for you.
Rust 语言也希望支持许多其他用户;这里提到的只是一些最大的利益相关者。总的来说,Rust 最大的野心是通过提供安全性生产力、速度人体工程学来消除程序员几十年来所接受的权衡。尝试一下 Rust,看看它的选择是否适合您。

Who This Book Is For
这本书适合谁

This book assumes that you’ve written code in another programming language but doesn’t make any assumptions about which one. We’ve tried to make the material broadly accessible to those from a wide variety of programming backgrounds. We don’t spend a lot of time talking about what programming is or how to think about it. If you’re entirely new to programming, you would be better served by reading a book that specifically provides an introduction to programming.
本书假设您已经用另一种编程语言编写了代码,但没有对是哪一种语言做出任何假设。我们努力让具有各种编程背景的人们能够广泛地访问这些材料。我们不会花很多时间讨论什么编程或如何思考它。如果您对编程完全陌生,那么阅读一本专门介绍编程的书会更好。

How to Use This Book
如何使用本书

In general, this book assumes that you’re reading it in sequence from front to back. Later chapters build on concepts in earlier chapters, and earlier chapters might not delve into details on a particular topic but will revisit the topic in a later chapter.
一般来说,本书假设您按从前到后的顺序阅读。后面的章节建立在前面章节中的概念的基础上,前面的章节可能不会深入研究特定主题的细节,但会在后面的章节中重新讨论该主题。

You’ll find two kinds of chapters in this book: concept chapters and project chapters. In concept chapters, you’ll learn about an aspect of Rust. In project chapters, we’ll build small programs together, applying what you’ve learned so far. Chapters 2, 12, and 20 are project chapters; the rest are concept chapters.
本书中有两种章节:概念章节和项目章节。在概念章节中,您将了解 Rust 的一个方面。在项目章节中,我们将应用您迄今为止所学到的知识来共同构建小程序。第2章、第12章和第20章是项目章节;其余的是概念章节。

Chapter 1 explains how to install Rust, how to write a “Hello, world!” program, and how to use Cargo, Rust’s package manager and build tool. Chapter 2 is a hands-on introduction to writing a program in Rust, having you build up a number guessing game. Here we cover concepts at a high level, and later chapters will provide additional detail. If you want to get your hands dirty right away, Chapter 2 is the place for that. Chapter 3 covers Rust features that are similar to those of other programming languages, and in Chapter 4 you’ll learn about Rust’s ownership system. If you’re a particularly meticulous learner who prefers to learn every detail before moving on to the next, you might want to skip Chapter 2 and go straight to Chapter 3, returning to Chapter 2 when you’d like to work on a project applying the details you’ve learned.
第 1 章解释了如何安装 Rust,如何编写“Hello, world!”程序,以及如何使用 Cargo、Rust 的包管理器和构建工具。第 2 章是关于用 Rust 编写程序的实践介绍,让您构建一个猜数字游戏。在这里,我们概括地介绍了概念,后面的章节将提供更多细节。如果您想立即动手,请阅读第 2 章。第 3 章介绍了与其他编程语言类似的 Rust 功能,在第 4 章中,您将了解 Rust 的所有权系统。如果您是一个特别细心的学习者,喜欢在继续下一章之前学习每个细节,您可能想跳过第 2 章并直接进入第 3 章,当您想要从事应用程序的项目时返回第 2 章你所学到的细节。

Chapter 5 discusses structs and methods, and Chapter 6 covers enums, match expressions, and the if let control flow construct. You’ll use structs and enums to make custom types in Rust.
第 5 章讨论结构和方法,第 6 章介绍枚举、 match表达式和if let控制流构造。您将使用结构和枚举在 Rust 中创建自定义类型。

In Chapter 7, you’ll learn about Rust’s module system and about privacy rules for organizing your code and its public Application Programming Interface (API). Chapter 8 discusses some common collection data structures that the standard library provides, such as vectors, strings, and hash maps. Chapter 9 explores Rust’s error-handling philosophy and techniques.
在第 7 章中,您将了解 Rust 的模块系统以及组织代码及其公共应用程序编程接口(API)的隐私规则。第8章讨论标准库提供的一些常见的集合数据结构,例如向量、字符串和散列映射。第 9 章探讨 Rust 的错误处理理念和技术。

Chapter 10 digs into generics, traits, and lifetimes, which give you the power to define code that applies to multiple types. Chapter 11 is all about testing, which even with Rust’s safety guarantees is necessary to ensure your program’s logic is correct. In Chapter 12, we’ll build our own implementation of a subset of functionality from the grep command line tool that searches for text within files. For this, we’ll use many of the concepts we discussed in the previous chapters.
第 10 章深入探讨了泛型、特征和生命周期,它们使您能够定义适用于多种类型的代码。第 11 章是关于测试的,即使有 Rust 的安全保证,测试也是确保程序逻辑正确所必需的。在第 12 章中,我们将构建我们自己的grep命令行工具功能子集的实现,该工具在文件中搜索文本。为此,我们将使用前面章节中讨论的许多概念。

Chapter 13 explores closures and iterators: features of Rust that come from functional programming languages. In Chapter 14, we’ll examine Cargo in more depth and talk about best practices for sharing your libraries with others. Chapter 15 discusses smart pointers that the standard library provides and the traits that enable their functionality.
第 13 章探讨了闭包和迭代器:来自函数式编程语言的 Rust 特性。在第 14 章中,我们将更深入地研究 Cargo 并讨论与其他人共享库的最佳实践。第 15 章讨论标准库提供的智能指针以及实现其功能的特征。

In Chapter 16, we’ll walk through different models of concurrent programming and talk about how Rust helps you to program in multiple threads fearlessly. Chapter 17 looks at how Rust idioms compare to object-oriented programming principles you might be familiar with.
在第 16 章中,我们将介绍并发编程的不同模型,并讨论 Rust 如何帮助您无所畏惧地在多线程中进行编程。第 17 章着眼于 Rust 习惯用法与您可能熟悉的面向对象编程原理的比较。

Chapter 18 is a reference on patterns and pattern matching, which are powerful ways of expressing ideas throughout Rust programs. Chapter 19 contains a smorgasbord of advanced topics of interest, including unsafe Rust, macros, and more about lifetimes, traits, types, functions, and closures.
第 18 章是关于模式和模式匹配的参考,它们是在 Rust 程序中表达想法的强大方式。第 19 章包含了一系列令人感兴趣的高级主题,包括不安全的 Rust、宏以及更多关于生命周期、特征、类型、函数和闭包的内容。

In Chapter 20, we’ll complete a project in which we’ll implement a low-level multithreaded web server!
在第 20 章中,我们将完成一个项目,在该项目中我们将实现一个低级多线程 Web 服务器!

Finally, some appendices contain useful information about the language in a more reference-like format. Appendix A covers Rust’s keywords, Appendix B covers Rust’s operators and symbols, Appendix C covers derivable traits provided by the standard library, Appendix D covers some useful development tools, and Appendix E explains Rust editions. In Appendix F, you can find translations of the book, and in Appendix G we’ll cover how Rust is made and what nightly Rust is.
最后,一些附录以更像参考的格式包含有关该语言的有用信息。附录 A 涵盖 Rust 的关键字,附录 B 涵盖 Rust 的运算符和符号,附录 C 涵盖标准库提供的可派生特征,附录 D 涵盖一些有用的开发工具,附录 E 解释 Rust 版本。在附录 F 中,您可以找到本书的翻译,在附录 G 中,我们将介绍 Rust 的制作方法以及夜间 Rust 是什么。

There is no wrong way to read this book: if you want to skip ahead, go for it! You might have to jump back to earlier chapters if you experience any confusion. But do whatever works for you.
阅读这本书没有错误的方法:如果你想跳过,就直接跳过去吧!如果您遇到任何困惑,您可能必须跳回到前面的章节。但做任何对你有用的事情。

An important part of the process of learning Rust is learning how to read the error messages the compiler displays: these will guide you toward working code. As such, we’ll provide many examples that don’t compile along with the error message the compiler will show you in each situation. Know that if you enter and run a random example, it may not compile! Make sure you read the surrounding text to see whether the example you’re trying to run is meant to error. Ferris will also help you distinguish code that isn’t meant to work:
学习 Rust 过程的一个重要部分是学习如何阅读编译器显示的错误消息:这些将引导您编写工作代码。因此,我们将提供许多无法编译的示例以及编译器在每种情况下向您显示的错误消息。请注意,如果您输入并运行随机示例,它可能无法编译!请务必阅读周围的文本,看看您尝试运行的示例是否会出错。 Ferris 还将帮助您区分不起作用的代码:

Ferris 费里斯Meaning 意义
Ferris with a question markThis code does not compile!
这段代码无法编译!
Ferris throwing up their handsThis code panics! 这段代码会引起恐慌!
Ferris with one claw up, shruggingThis code does not produce the desired behavior.
此代码不会产生所需的行为。

In most situations, we’ll lead you to the correct version of any code that doesn’t compile.
在大多数情况下,我们会引导您找到任何无法编译的代码的正确版本。

Source Code 源代码

The source files from which this book is generated can be found on GitHub.
生成本书的源文件可以在GitHub上找到。

Getting Started 入门

Let’s start your Rust journey! There’s a lot to learn, but every journey starts somewhere. In this chapter, we’ll discuss:
让我们开始您的 Rust 之旅吧!有很多东西需要学习,但每一次旅程都从某个地方开始。在本章中,我们将讨论:

  • Installing Rust on Linux, macOS, and Windows
    在 Linux、macOS 和 Windows 上安装 Rust
  • Writing a program that prints Hello, world!
    编写一个打印Hello, world!的程序
  • Using cargo, Rust’s package manager and build system
    使用cargo 、Rust 的包管理器和构建系统

Installation 安装

The first step is to install Rust. We’ll download Rust through rustup, a command line tool for managing Rust versions and associated tools. You’ll need an internet connection for the download.
第一步是安装 Rust。我们将通过rustup下载 Rust,这是一个用于管理 Rust 版本和相关工具的命令行工具。您需要连接互联网才能下载。

Note: If you prefer not to use rustup for some reason, please see the Other Rust Installation Methods page for more options.
注意:如果您出于某种原因不想使用rustup ,请参阅其他 Rust 安装方法页面以获取更多选项。

The following steps install the latest stable version of the Rust compiler. Rust’s stability guarantees ensure that all the examples in the book that compile will continue to compile with newer Rust versions. The output might differ slightly between versions because Rust often improves error messages and warnings. In other words, any newer, stable version of Rust you install using these steps should work as expected with the content of this book.
以下步骤安装最新稳定版本的 Rust 编译器。 Rust 的稳定性保证确保书中所有编译的示例都将继续使用较新的 Rust 版本进行编译。版本之间的输出可能略有不同,因为 Rust 通常会改进错误消息和警告。换句话说,使用这些步骤安装的任何较新、稳定的 Rust 版本都应该按照本书的内容按预期工作。

Command Line Notation 命令行表示法

In this chapter and throughout the book, we’ll show some commands used in the terminal. Lines that you should enter in a terminal all start with $. You don’t need to type the $ character; it’s the command line prompt shown to indicate the start of each command. Lines that don’t start with $ typically show the output of the previous command. Additionally, PowerShell-specific examples will use > rather than $.
在本章和整本书中,我们将展示终端中使用的一些命令。您应该在终端中输入的行均以$开头。您不需要键入$字符;它是显示的命令行提示符,指示每个命令的开始。不以$开头的行通常显示上一个命令的输出。此外,特定于 PowerShell 的示例将使用>而不是$

Installing rustup on Linux or macOS
在 Linux 或 macOS 上安装rustup

If you’re using Linux or macOS, open a terminal and enter the following command:
如果您使用的是 Linux 或 macOS,请打开终端并输入以下命令:

$ curl --proto '=https' --tlsv1.2 https://sh.rustup.rs -sSf | sh

The command downloads a script and starts the installation of the rustup tool, which installs the latest stable version of Rust. You might be prompted for your password. If the install is successful, the following line will appear:
该命令下载脚本并开始安装rustup工具,该工具将安装 Rust 的最新稳定版本。系统可能会提示您输入密码。如果安装成功,会出现下面一行:

Rust is installed now. Great!

You will also need a linker, which is a program that Rust uses to join its compiled outputs into one file. It is likely you already have one. If you get linker errors, you should install a C compiler, which will typically include a linker. A C compiler is also useful because some common Rust packages depend on C code and will need a C compiler.
您还需要一个链接器,这是 Rust 用来将其编译输出连接到一个文件中的程序。您很可能已经拥有一个。如果出现链接器错误,则应安装 C 编译器,该编译器通常包含链接器。 AC 编译器也很有用,因为一些常见的 Rust 包依赖于 C 代码,并且需要 C 编译器。

On macOS, you can get a C compiler by running:
在 macOS 上,您可以通过运行以下命令来获取 C 编译器:

$ xcode-select --install

Linux users should generally install GCC or Clang, according to their distribution’s documentation. For example, if you use Ubuntu, you can install the build-essential package.
Linux 用户通常应该根据其发行版的文档安装 GCC 或 Clang。例如,如果您使用 Ubuntu,则可以安装build-essential包。

Installing rustup on Windows
在 Windows 上安装rustup

On Windows, go to https://www.rust-lang.org/tools/install and follow the instructions for installing Rust. At some point in the installation, you’ll be prompted to install Visual Studio. This provides a linker and the native libraries needed to compile programs. If you need more help with this step, see https://rust-lang.github.io/rustup/installation/windows-msvc.html
在 Windows 上,请访问https://www.rust-lang.org/tools/install并按照安装 Rust 的说明进行操作。在安装过程中的某个时刻,系统会提示您安装 Visual Studio。这提供了编译程序所需的链接器和本机库。如果您需要有关此步骤的更多帮助,请参阅https://rust-lang.github.io/rustup/installation/windows-msvc.html

The rest of this book uses commands that work in both cmd.exe and PowerShell. If there are specific differences, we’ll explain which to use.
本书的其余部分使用可在cmd.exe和 PowerShell 中运行的命令。如果存在具体差异,我们将解释使用哪个。

Troubleshooting 故障排除

To check whether you have Rust installed correctly, open a shell and enter this line:
要检查 Rust 是否正确安装,请打开 shell 并输入以下行:

$ rustc --version

You should see the version number, commit hash, and commit date for the latest stable version that has been released, in the following format:
您应该看到已发布的最新稳定版本的版本号、提交哈希值和提交日期,格式如下:

rustc x.y.z (abcabcabc yyyy-mm-dd)

If you see this information, you have installed Rust successfully! If you don’t see this information, check that Rust is in your %PATH% system variable as follows.
如果你看到这个信息,说明你已经安装Rust成功了!如果您没有看到此信息,请检查 Rust 是否在您的%PATH%系统变量中,如下所示。

In Windows CMD, use: 在 Windows CMD 中,使用:

> echo %PATH%

In PowerShell, use: 在 PowerShell 中,使用:

> echo $env:Path

In Linux and macOS, use:
在 Linux 和 macOS 中,使用:

$ echo $PATH

If that’s all correct and Rust still isn’t working, there are a number of places you can get help. Find out how to get in touch with other Rustaceans (a silly nickname we call ourselves) on the community page.
如果一切正确,但 Rust 仍然无法工作,您可以从很多地方获得帮助。在社区页面上了解如何与其他 Rustaceans(我们给自己起的一个愚蠢的昵称)取得联系。

Updating and Uninstalling
更新和卸载

Once Rust is installed via rustup, updating to a newly released version is easy. From your shell, run the following update script:
通过rustup安装 Rust 后,更新到新发布的版本就很容易了。从 shell 中运行以下更新脚本:

$ rustup update

To uninstall Rust and rustup, run the following uninstall script from your shell:
要卸载 Rust 和rustup ,请从 shell 运行以下卸载脚本:

$ rustup self uninstall

Local Documentation 本地文档

The installation of Rust also includes a local copy of the documentation so that you can read it offline. Run rustup doc to open the local documentation in your browser.
Rust 的安装还包括文档的本地副本,以便您可以离线阅读。运行rustup doc以在浏览器中打开本地文档。

Any time a type or function is provided by the standard library and you’re not sure what it does or how to use it, use the application programming interface (API) documentation to find out!
每当标准库提供类型或函数并且您不确定它的作用或如何使用它时,请使用应用程序编程接口 (API) 文档来查找!

Hello, World! 你好世界!

Now that you’ve installed Rust, it’s time to write your first Rust program. It’s traditional when learning a new language to write a little program that prints the text Hello, world! to the screen, so we’ll do the same here!
现在您已经安装了 Rust,是时候编写您的第一个 Rust 程序了。学习一门新语言时,传统做法是编写一个小程序来打印文本Hello, world!到屏幕上,所以我们在这里也做同样的事情!

Note: This book assumes basic familiarity with the command line. Rust makes no specific demands about your editing or tooling or where your code lives, so if you prefer to use an integrated development environment (IDE) instead of the command line, feel free to use your favorite IDE. Many IDEs now have some degree of Rust support; check the IDE’s documentation for details. The Rust team has been focusing on enabling great IDE support via rust-analyzer. See Appendix D for more details.
注意:本书假设您对命令行有基本的熟悉。 Rust 对您的编辑、工具或代码所在位置没有具体要求,因此,如果您更喜欢使用集成开发环境 (IDE) 而不是命令行,请随意使用您最喜欢的 IDE。现在许多 IDE 都具有一定程度的 Rust 支持;有关详细信息,请查看 IDE 的文档。 Rust 团队一直致力于通过rust-analyzer提供强大的 IDE 支持。有关详细信息,请参阅附录 D忽略。

Creating a Project Directory
创建项目目录

You’ll start by making a directory to store your Rust code. It doesn’t matter to Rust where your code lives, but for the exercises and projects in this book, we suggest making a projects directory in your home directory and keeping all your projects there.
您将首先创建一个目录来存储 Rust 代码。对于 Rust 来说,你的代码所在的位置并不重要,但对于本书中的练习和项目,我们建议在你的主目录中创建一个项目目录,并将所有项目保存在那里。

Open a terminal and enter the following commands to make a projects directory and a directory for the “Hello, world!” project within the projects directory.
打开终端并输入以下命令来创建项目目录和“Hello, world!”目录项目位于项目目录中。

For Linux, macOS, and PowerShell on Windows, enter this:
对于 Linux、macOS 和 Windows 上的 PowerShell,请输入:

$ mkdir ~/projects $ cd ~/projects $ mkdir hello_world $ cd hello_world

For Windows CMD, enter this:
对于 Windows CMD,请输入:

> mkdir "%USERPROFILE%\projects" > cd /d "%USERPROFILE%\projects" > mkdir hello_world > cd hello_world

Writing and Running a Rust Program
编写并运行 Rust 程序

Next, make a new source file and call it main.rs. Rust files always end with the .rs extension. If you’re using more than one word in your filename, the convention is to use an underscore to separate them. For example, use hello_world.rs rather than helloworld.rs.
接下来,创建一个新的源文件并将其命名为main.rs 。 Rust 文件始终以.rs扩展名结尾。如果您的文件名中使用了多个单词,则惯例是使用下划线分隔它们。例如,使用hello_world.rs而不是helloworld.rs

Now open the main.rs file you just created and enter the code in Listing 1-1.
现在打开刚刚创建的main.rs文件并输入清单 1-1 中的代码。

Filename: main.rs 文件名: main.rs
fn main() { println!("Hello, world!"); }
Listing 1-1: A program that prints Hello, world!
清单 1-1:一个打印Hello, world!的程序

Save the file and go back to your terminal window in the ~/projects/hello_world directory. On Linux or macOS, enter the following commands to compile and run the file:
保存文件并返回到~/projects/hello_world目录中的终端窗口。在 Linux 或 macOS 上,输入以下命令来编译并运行该文件:

$ rustc main.rs $ ./main Hello, world!

On Windows, enter the command .\main.exe instead of ./main:
在 Windows 上,输入命​​令.\main.exe而不是./main

> rustc main.rs > .\main.exe Hello, world!

Regardless of your operating system, the string Hello, world! should print to the terminal. If you don’t see this output, refer back to the “Troubleshooting” part of the Installation section for ways to get help.
无论您使用什么操作系统,字符串Hello, world!应该打印到终端。如果您没有看到此输出,请参阅安装部分的“故障排除”忽略部分,了解获取帮助的方法。

If Hello, world! did print, congratulations! You’ve officially written a Rust program. That makes you a Rust programmer—welcome!
如果Hello, world!打印出来了,恭喜!您已经正式编写了一个 Rust 程序。这使您成为一名 Rust 程序员——欢迎!

Anatomy of a Rust Program
Rust 程序剖析

Let’s review this “Hello, world!” program in detail. Here’s the first piece of the puzzle:
让我们回顾一下这首《你好,世界!》详细的计划。这是拼图的第一块:

fn main() { }

These lines define a function named main. The main function is special: it is always the first code that runs in every executable Rust program. Here, the first line declares a function named main that has no parameters and returns nothing. If there were parameters, they would go inside the parentheses ().
这些行定义了一个名为main函数。 main函数很特殊:它始终是每个可执行 Rust 程序中运行的第一个代码。这里,第一行声明了一个名为main函数,它没有参数,也不返回任何内容。如果有参数,它们将放在括号()内。

The function body is wrapped in {}. Rust requires curly brackets around all function bodies. It’s good style to place the opening curly bracket on the same line as the function declaration, adding one space in between.
函数体被包裹在{}中。 Rust 要求所有函数体都用大括号括起来。将左大括号与函数声明放在同一行,并在中间添加一个空格是一种很好的风格。

Note: If you want to stick to a standard style across Rust projects, you can use an automatic formatter tool called rustfmt to format your code in a particular style (more on rustfmt in Appendix D). The Rust team has included this tool with the standard Rust distribution, as rustc is, so it should already be installed on your computer!
注意:如果您想在 Rust 项目中坚持使用标准样式,您可以使用名为rustfmt的自动格式化工具以特定样式格式化您的代码(附录 D中有关rustfmt更多信息忽略)。 Rust 团队已将该工具包含在标准 Rust 发行版中,就像rustc一样,因此它应该已经安装在您的计算机上!

The body of the main function holds the following code:
main函数的主体包含以下代码:

#![allow(unused)] fn main() { println!("Hello, world!"); }

This line does all the work in this little program: it prints text to the screen. There are four important details to notice here.
这一行完成了这个小程序中的所有工作:它将文本打印到屏幕上。这里有四个重要细节需要注意。

First, Rust style is to indent with four spaces, not a tab.
首先,Rust 风格是用四个空格缩进,而不是制表符。

Second, println! calls a Rust macro. If it had called a function instead, it would be entered as println (without the !). We’ll discuss Rust macros in more detail in Chapter 19. For now, you just need to know that using a ! means that you’re calling a macro instead of a normal function and that macros don’t always follow the same rules as functions.
第二, println!调用 Rust 宏。如果它调用了一个函数,那么它将被输入为println (不带! )。我们将在第 19 章中更详细地讨论 Rust 宏。现在,您只需要知道使用!意味着您正在调用宏而不是普通函数,并且宏并不总是遵循与函数相同的规则。

Third, you see the "Hello, world!" string. We pass this string as an argument to println!, and the string is printed to the screen.
第三,您会看到"Hello, world!"细绳。我们将此字符串作为参数传递给println! ,并且字符串被打印到屏幕上。

Fourth, we end the line with a semicolon (;), which indicates that this expression is over and the next one is ready to begin. Most lines of Rust code end with a semicolon.
第四,我们以分号( ; )结束该行,这表明该表达式已结束,下一个表达式已准备好开始。大多数 Rust 代码行都以分号结尾。

Compiling and Running Are Separate Steps
编译和运行是分开的步骤

You’ve just run a newly created program, so let’s examine each step in the process.
您刚刚运行了一个新创建的程序,所以让我们检查一下该过程中的每个步骤。

Before running a Rust program, you must compile it using the Rust compiler by entering the rustc command and passing it the name of your source file, like this:
在运行 Rust 程序之前,您必须使用 Rust 编译器来编译它,方法是输入rustc命令并向其传递源文件的名称,如下所示:

$ rustc main.rs

If you have a C or C++ background, you’ll notice that this is similar to gcc or clang. After compiling successfully, Rust outputs a binary executable.
如果您有 C 或 C++ 背景,您会注意到这与gccclang类似。编译成功后,Rust 输出一个二进制可执行文件。

On Linux, macOS, and PowerShell on Windows, you can see the executable by entering the ls command in your shell:
在 Linux、macOS 和 Windows 上的 PowerShell 上,您可以通过在 shell 中输入ls命令来查看可执行文件:

$ ls main main.rs

On Linux and macOS, you’ll see two files. With PowerShell on Windows, you’ll see the same three files that you would see using CMD. With CMD on Windows, you would enter the following:
在 Linux 和 macOS 上,您将看到两个文件。使用 Windows 上的 PowerShell,您将看到与使用 CMD 看到的相同的三个文件。使用 Windows 上的 CMD,您将输入以下内容:

> dir /B %= the /B option says to only show the file names =% main.exe main.pdb main.rs

This shows the source code file with the .rs extension, the executable file (main.exe on Windows, but main on all other platforms), and, when using Windows, a file containing debugging information with the .pdb extension. From here, you run the main or main.exe file, like this:
这显示了扩展名为.rs的源代码文件、可执行文件(Windows 上为main.exe ,但在所有其他平台上为 main ),以及使用 Windows 时包含扩展名为.pdb 的调试信息的文件。从这里,您运行mainmain.exe文件,如下所示:

$ ./main # or .\main.exe on Windows

If your main.rs is your “Hello, world!” program, this line prints Hello, world! to your terminal.
如果你的main.rs是你的“Hello, world!”程序中,这一行打印Hello, world!到您的终端。

If you’re more familiar with a dynamic language, such as Ruby, Python, or JavaScript, you might not be used to compiling and running a program as separate steps. Rust is an ahead-of-time compiled language, meaning you can compile a program and give the executable to someone else, and they can run it even without having Rust installed. If you give someone a .rb, .py, or .js file, they need to have a Ruby, Python, or JavaScript implementation installed (respectively). But in those languages, you only need one command to compile and run your program. Everything is a trade-off in language design.
如果您更熟悉动态语言,例如 Ruby、Python 或 JavaScript,您可能不习惯将程序编译和运行作为单独的步骤。 Rust 是一种提前编译的语言,这意味着您可以编译程序并将可执行文件提供给其他人,即使没有安装 Rust,他们也可以运行它。如果您向某人提供.rb.py.js文件,他们需要(分别)安装 Ruby、Python 或 JavaScript 实现。但在这些语言中,您只需要一个命令来编译和运行您的程序。一切都是语言设计的权衡。

Just compiling with rustc is fine for simple programs, but as your project grows, you’ll want to manage all the options and make it easy to share your code. Next, we’ll introduce you to the Cargo tool, which will help you write real-world Rust programs.
对于简单的程序来说,使用rustc进行编译就足够了,但随着项目的增长,您将需要管理所有选项并轻松共享代码。接下来,我们将向您介绍 Cargo 工具,它将帮助您编写真实的 Rust 程序。

Hello, Cargo! 你好,货物!

Cargo is Rust’s build system and package manager. Most Rustaceans use this tool to manage their Rust projects because Cargo handles a lot of tasks for you, such as building your code, downloading the libraries your code depends on, and building those libraries. (We call the libraries that your code needs dependencies.)
Cargo 是 Rust 的构建系统和包管理器。大多数 Rustaceans 使用这个工具来管理他们的 Rust 项目,因为 Cargo 会为您处理很多任务,例如构建代码、下载代码所依赖的库以及构建这些库。 (我们将您的代码需要的库称为“依赖项” 。)

The simplest Rust programs, like the one we’ve written so far, don’t have any dependencies. If we had built the “Hello, world!” project with Cargo, it would only use the part of Cargo that handles building your code. As you write more complex Rust programs, you’ll add dependencies, and if you start a project using Cargo, adding dependencies will be much easier to do.
最简单的 Rust 程序,就像我们迄今为止编写的程序一样,没有任何依赖项。如果我们建造了“你好,世界!”使用 Cargo 进行项目时,它只会使用 Cargo 中处理构建代码的部分。当你编写更复杂的 Rust 程序时,你将添加依赖项,如果你使用 Cargo 启动一个项目,添加依赖项会更容易。

Because the vast majority of Rust projects use Cargo, the rest of this book assumes that you’re using Cargo too. Cargo comes installed with Rust if you used the official installers discussed in the “Installation” section. If you installed Rust through some other means, check whether Cargo is installed by entering the following in your terminal:
由于绝大多数 Rust 项目都使用 Cargo,因此本书的其余部分假设您也使用 Cargo。如果您使用“安装”忽略部分中讨论的官方安装程序,Cargo 会随 Rust 一起安装。如果您通过其他方式安装了 Rust,请在终端中输入以下内容来检查 Cargo 是否已安装:

$ cargo --version

If you see a version number, you have it! If you see an error, such as command not found, look at the documentation for your method of installation to determine how to install Cargo separately.
如果您看到版本号,则说明您已拥有它!如果您看到错误,例如command not found ,请查看您的安装方法的文档,以确定如何单独安装 Cargo。

Creating a Project with Cargo
使用 Cargo 创建项目

Let’s create a new project using Cargo and look at how it differs from our original “Hello, world!” project. Navigate back to your projects directory (or wherever you decided to store your code). Then, on any operating system, run the following:
让我们使用 Cargo 创建一个新项目,看看它与我们原来的“Hello, world!”有何不同。项目。导航回您的项目目录(或您决定存储代码的任何位置)。然后,在任何操作系统上运行以下命令:

$ cargo new hello_cargo $ cd hello_cargo

The first command creates a new directory and project called hello_cargo. We’ve named our project hello_cargo, and Cargo creates its files in a directory of the same name.
第一个命令创建一个名为hello_cargo的新目录和项目。我们将项目命名为hello_cargo ,Cargo 在同名目录中创建其文件。

Go into the hello_cargo directory and list the files. You’ll see that Cargo has generated two files and one directory for us: a Cargo.toml file and a src directory with a main.rs file inside.
进入hello_cargo目录并列出文件。你会看到 Cargo 为我们生成了两个文件和一个目录:一个Cargo.toml文件和一个src目录,里面有一个main.rs文件。

It has also initialized a new Git repository along with a .gitignore file. Git files won’t be generated if you run cargo new within an existing Git repository; you can override this behavior by using cargo new --vcs=git.
它还初始化了一个新的 Git 存储库以及一个.gitignore文件。如果您在现有的 Git 存储库中运行cargo new ,则不会生成 Git 文件;您可以使用cargo new --vcs=git覆盖此行为。

Note: Git is a common version control system. You can change cargo new to use a different version control system or no version control system by using the --vcs flag. Run cargo new --help to see the available options.
注:Git 是一个常见的版本控制系统。您可以使用--vcs标志将cargo new更改为使用不同的版本控制系统或不使用版本控制系统。运行cargo new --help以查看可用选项。

Open Cargo.toml in your text editor of choice. It should look similar to the code in Listing 1-2.
在您选择的文本编辑器中打开Cargo.toml 。它应该类似于清单 1-2 中的代码。

Filename: Cargo.toml 文件名:Cargo.toml
[package] name = "hello_cargo" version = "0.1.0" edition = "2021" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies]
Listing 1-2: Contents of Cargo.toml generated by cargo new
清单1-2:由cargo new生成的Cargo.toml的内容

This file is in the TOML (Tom’s Obvious, Minimal Language) format, which is Cargo’s configuration format.
该文件采用TOMLignoreTom's Obvious,Minimal Language )格式,这是 Cargo 的配置格式。

The first line, [package], is a section heading that indicates that the following statements are configuring a package. As we add more information to this file, we’ll add other sections.
第一行[package]是一个节标题,指示以下语句正在配置包。当我们向该文件添加更多信息时,我们将添加其他部分。

The next three lines set the configuration information Cargo needs to compile your program: the name, the version, and the edition of Rust to use. We’ll talk about the edition key in Appendix E.
接下来的三行设置 Cargo 编译程序所需的配置信息:名称、版本和要使用的 Rust 版本。我们将在附录 Eignore中讨论edition密钥。

The last line, [dependencies], is the start of a section for you to list any of your project’s dependencies. In Rust, packages of code are referred to as crates. We won’t need any other crates for this project, but we will in the first project in Chapter 2, so we’ll use this dependencies section then.
最后一行[dependencies]是一个部分的开始,用于列出项目的任何依赖项。在 Rust 中,代码包被称为crates 。该项目不需要任何其他 crate,但我们将在第 2 章的第一个项目中使用,因此我们将使用此依赖项部分。

Now open src/main.rs and take a look:
现在打开src/main.rs看看:

Filename: src/main.rs 文件名:src/main.rs

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

Cargo has generated a “Hello, world!” program for you, just like the one we wrote in Listing 1-1! So far, the differences between our project and the project Cargo generated are that Cargo placed the code in the src directory and we have a Cargo.toml configuration file in the top directory.
Cargo 生成了“Hello, world!”为您准备的程序,就像我们在清单 1-1 中编写的程序一样!到目前为止,我们的项目和 Cargo 生成的项目之间的区别在于 Cargo 将代码放在src目录中,并且我们在顶层目录中有一个Cargo.toml配置文件。

Cargo expects your source files to live inside the src directory. The top-level project directory is just for README files, license information, configuration files, and anything else not related to your code. Using Cargo helps you organize your projects. There’s a place for everything, and everything is in its place.
Cargo 希望您的源文件位于src目录中。顶级项目目录仅用于 README 文件、许可证信息、配置文件以及与代码无关的任何其他内容。使用 Cargo 可以帮助您组织项目。一切都有一个地方,一切都在它的位置上。

If you started a project that doesn’t use Cargo, as we did with the “Hello, world!” project, you can convert it to a project that does use Cargo. Move the project code into the src directory and create an appropriate Cargo.toml file.
如果您启动了一个不使用 Cargo 的项目,就像我们对“Hello, world!”所做的那样项目,您可以将其转换为使用 Cargo 的项目。将项目代码移至src目录并创建适当的Cargo.toml文件。

Building and Running a Cargo Project
构建和运行货运项目

Now let’s look at what’s different when we build and run the “Hello, world!” program with Cargo! From your hello_cargo directory, build your project by entering the following command:
现在让我们看看构建和运行“Hello, world!”时有什么不同。与 Cargo 一起计划!在hello_cargo目录中,输入以下命令来构建项目:

$ cargo build Compiling hello_cargo v0.1.0 (file:///projects/hello_cargo) Finished dev [unoptimized + debuginfo] target(s) in 2.85 secs

This command creates an executable file in target/debug/hello_cargo (or target\debug\hello_cargo.exe on Windows) rather than in your current directory. Because the default build is a debug build, Cargo puts the binary in a directory named debug. You can run the executable with this command:
此命令在target/debug/hello_cargo (或 Windows 上的target\debug\hello_cargo.exe )而不是在当前目录中创建可执行文件。由于默认构建是调试构建,因此 Cargo 将二进制文件放在名为debug的目录中。您可以使用以下命令运行可执行文件:

$ ./target/debug/hello_cargo # or .\target\debug\hello_cargo.exe on Windows Hello, world!

If all goes well, Hello, world! should print to the terminal. Running cargo build for the first time also causes Cargo to create a new file at the top level: Cargo.lock. This file keeps track of the exact versions of dependencies in your project. This project doesn’t have dependencies, so the file is a bit sparse. You won’t ever need to change this file manually; Cargo manages its contents for you.
如果一切顺利的话, Hello, world!应该打印到终端。第一次运行cargo build还会导致 Cargo 在顶层创建一个新文件: Cargo.lock 。该文件跟踪项目中依赖项的确切版本。该项目没有依赖项,因此文件有点稀疏。您无需手动更改此文件; Cargo 为您管理其内容。

We just built a project with cargo build and ran it with ./target/debug/hello_cargo, but we can also use cargo run to compile the code and then run the resultant executable all in one command:
我们刚刚使用cargo build构建了一个项目,并使用./target/debug/hello_cargo运行它,但我们也可以使用cargo run来编译代码,然后在一个命令中运行生成的可执行文件:

$ cargo run Finished dev [unoptimized + debuginfo] target(s) in 0.0 secs Running `target/debug/hello_cargo` Hello, world!

Using cargo run is more convenient than having to remember to run cargo build and then use the whole path to the binary, so most developers use cargo run.
使用cargo run比必须记住运行cargo build然后使用二进制文件的整个路径更方便,因此大多数开发人员使用cargo run

Notice that this time we didn’t see output indicating that Cargo was compiling hello_cargo. Cargo figured out that the files hadn’t changed, so it didn’t rebuild but just ran the binary. If you had modified your source code, Cargo would have rebuilt the project before running it, and you would have seen this output:
请注意,这次我们没有看到表明 Cargo 正在编译hello_cargo的输出。 Cargo 发现文件没有改变,所以它没有重建,只是运行二进制文件。如果您修改了源代码,Cargo 将在运行之前重新构建项目,并且您将看到以下输出:

$ cargo run Compiling hello_cargo v0.1.0 (file:///projects/hello_cargo) Finished dev [unoptimized + debuginfo] target(s) in 0.33 secs Running `target/debug/hello_cargo` Hello, world!

Cargo also provides a command called cargo check. This command quickly checks your code to make sure it compiles but doesn’t produce an executable:
Cargo还提供了一个名为cargo check命令。此命令快速检查您的代码以确保它可以编译但不会生成可执行文件:

$ cargo check Checking hello_cargo v0.1.0 (file:///projects/hello_cargo) Finished dev [unoptimized + debuginfo] target(s) in 0.32 secs

Why would you not want an executable? Often, cargo check is much faster than cargo build because it skips the step of producing an executable. If you’re continually checking your work while writing the code, using cargo check will speed up the process of letting you know if your project is still compiling! As such, many Rustaceans run cargo check periodically as they write their program to make sure it compiles. Then they run cargo build when they’re ready to use the executable.
为什么你不想要一个可执行文件?通常, cargo checkcargo build快得多,因为它跳过了生成可执行文件的步骤。如果您在编写代码时不断检查您的工作,那么使用cargo check将加快让您知道项目是否仍在编译的过程!因此,许多 Rustaceans 在编写程序时定期运行cargo check以确保其编译。然后,当他们准备好使用可执行文件时,他们会运行cargo build

Let’s recap what we’ve learned so far about Cargo:
让我们回顾一下到目前为止我们所学到的有关 Cargo 的知识:

  • We can create a project using cargo new.
    我们可以使用cargo new创建一个项目。
  • We can build a project using cargo build.
    我们可以使用cargo build构建一个项目。
  • We can build and run a project in one step using cargo run.
    我们可以使用cargo run一步构建并运行一个项目。
  • We can build a project without producing a binary to check for errors using cargo check.
    我们可以在不生成二进制文件的情况下构建项目,以使用cargo check检查错误。
  • Instead of saving the result of the build in the same directory as our code, Cargo stores it in the target/debug directory.
    Cargo 没有将构建结果保存在与我们的代码相同的目录中,而是将其存储在target/debug目录中。

An additional advantage of using Cargo is that the commands are the same no matter which operating system you’re working on. So, at this point, we’ll no longer provide specific instructions for Linux and macOS versus Windows.
使用 Cargo 的另一个优点是,无论您使用哪种操作系统,命令都是相同的。因此,目前我们将不再提供针对 Linux 和 macOS 与 Windows 的具体说明。

Building for Release 构建发布

When your project is finally ready for release, you can use cargo build --release to compile it with optimizations. This command will create an executable in target/release instead of target/debug. The optimizations make your Rust code run faster, but turning them on lengthens the time it takes for your program to compile. This is why there are two different profiles: one for development, when you want to rebuild quickly and often, and another for building the final program you’ll give to a user that won’t be rebuilt repeatedly and that will run as fast as possible. If you’re benchmarking your code’s running time, be sure to run cargo build --release and benchmark with the executable in target/release.
当您的项目最终准备好发布时,您可以使用cargo build --release对其进行优化编译。此命令将在target/release而不是target/debug中创建可执行文件。这些优化使您的 Rust 代码运行得更快,但打开它们会延长程序编译所需的时间。这就是为什么有两种不同的配置文件:一种用于开发,当您想要快速且频繁地重建时,另一种用于构建最终程序,您将提供给用户,该程序不会重复重建并且运行速度与可能的。如果您要对代码的运行时间进行基准测试,请务必运行cargo build --release并使用target/release中的可执行文件进行基准测试。

Cargo as Convention 货物作为惯例

With simple projects, Cargo doesn’t provide a lot of value over just using rustc, but it will prove its worth as your programs become more intricate. Once programs grow to multiple files or need a dependency, it’s much easier to let Cargo coordinate the build.
对于简单的项目,Cargo 并不能提供比仅使用rustc更大的价值,但随着你的程序变得更加复杂,它会证明它的价值。一旦程序增长到多个文件或需要依赖项,让 Cargo 协调构建就会容易得多。

Even though the hello_cargo project is simple, it now uses much of the real tooling you’ll use in the rest of your Rust career. In fact, to work on any existing projects, you can use the following commands to check out the code using Git, change to that project’s directory, and build:
尽管hello_cargo项目很简单,但它现在使用了您在 Rust 职业生涯的其余部分中将使用的许多真实工具。事实上,要处理任何现有项目,您可以使用以下命令通过 Git 检查代码,更改到该项目的目录,然后构建:

$ git clone example.org/someproject $ cd someproject $ cargo build

For more information about Cargo, check out its documentation.
有关 Cargo 的更多信息,请查看其文档

Summary 概括

You’re already off to a great start on your Rust journey! In this chapter, you’ve learned how to:
您的 Rust 之旅已经有了一个良好的开端!在本章中,您学习了如何:

  • Install the latest stable version of Rust using rustup
    使用rustup安装最新稳定版本的 Rust
  • Update to a newer Rust version
    更新到较新的 Rust 版本
  • Open locally installed documentation
    打开本地安装的文档
  • Write and run a “Hello, world!” program using rustc directly
    编写并运行“Hello, world!”直接使用rustc进行编程
  • Create and run a new project using the conventions of Cargo
    使用 Cargo 约定创建并运行一个新项目

This is a great time to build a more substantial program to get used to reading and writing Rust code. So, in Chapter 2, we’ll build a guessing game program. If you would rather start by learning how common programming concepts work in Rust, see Chapter 3 and then return to Chapter 2.
现在是构建一个更充实的程序以习惯阅读和编写 Rust 代码的好时机。因此,在第 2 章中,我们将构建一个猜谜游戏程序。如果您想从学习 Rust 中常见编程概念的工作原理开始,请参阅第 3 章,然后返回第 2 章。

Programming a Guessing Game
编写一个猜谜游戏

Let’s jump into Rust by working through a hands-on project together! This chapter introduces you to a few common Rust concepts by showing you how to use them in a real program. You’ll learn about let, match, methods, associated functions, external crates, and more! In the following chapters, we’ll explore these ideas in more detail. In this chapter, you’ll just practice the fundamentals.
让我们一起完成一个实践项目来进入 Rust!本章通过向您展示如何在实际程序中使用它们来向您介绍一些常见的 Rust 概念。您将了解letmatch 、方法、关联函数、外部包等等!在接下来的章节中,我们将更详细地探讨这些想法。在本章中,您将只练习基础知识。

We’ll implement a classic beginner programming problem: a guessing game. Here’s how it works: the program will generate a random integer between 1 and 100. It will then prompt the player to enter a guess. After a guess is entered, the program will indicate whether the guess is too low or too high. If the guess is correct, the game will print a congratulatory message and exit.
我们将实现一个经典的初学者编程问题:猜谜游戏。它的工作原理如下:程序将生成一个 1 到 100 之间的随机整数。然后它会提示玩家输入猜测值。输入猜测后,程序将指示猜测是否太低或太高。如果猜测正确,游戏将打印一条祝贺消息并退出。

Setting Up a New Project
设置新项目

To set up a new project, go to the projects directory that you created in Chapter 1 and make a new project using Cargo, like so:
要设置新项目,请转到您在第 1 章中创建的项目目录并使用 Cargo 创建一个新项目,如下所示:

$ cargo new guessing_game $ cd guessing_game

The first command, cargo new, takes the name of the project (guessing_game) as the first argument. The second command changes to the new project’s directory.
第一个命令cargo new将项目名称 ( guessing_game ) 作为第一个参数。第二个命令更改为新项目的目录。

Look at the generated Cargo.toml file:
查看生成的Cargo.toml文件:

Filename: Cargo.toml 文件名:Cargo.toml

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

As you saw in Chapter 1, cargo new generates a “Hello, world!” program for you. Check out the src/main.rs file:
正如您在第 1 章中看到的, cargo new生成一个“Hello, world!”为您准备的节目。查看src/main.rs文件:

Filename: src/main.rs 文件名:src/main.rs

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

Now let’s compile this “Hello, world!” program and run it in the same step using the cargo run command:
现在让我们编译这个“Hello, world!”程序并使用cargo run命令在同一步骤中运行它:

$ cargo run Compiling guessing_game v0.1.0 (file:///projects/guessing_game) Finished `dev` profile [unoptimized + debuginfo] target(s) in 1.50s Running `target/debug/guessing_game` Hello, world!

The run command comes in handy when you need to rapidly iterate on a project, as we’ll do in this game, quickly testing each iteration before moving on to the next one.
当您需要快速迭代项目时, run命令会派上用场,正如我们将在本游戏中所做的那样,在继续下一个迭代之前快速测试每个迭代。

Reopen the src/main.rs file. You’ll be writing all the code in this file.
重新打开src/main.rs文件。您将在此文件中编写所有代码。

Processing a Guess 处理猜测

The first part of the guessing game program will ask for user input, process that input, and check that the input is in the expected form. To start, we’ll allow the player to input a guess. Enter the code in Listing 2-1 into src/main.rs.
猜谜游戏程序的第一部分将要求用户输入,处理该输入,并检查输入是否为预期形式。首先,我们将允许玩家输入猜测。将清单 2-1 中的代码输入到src/main.rs中。

Filename: src/main.rs 文件名:src/main.rs

use std::io; fn main() { println!("Guess the number!"); println!("Please input your guess."); let mut guess = String::new(); io::stdin() .read_line(&mut guess) .expect("Failed to read line"); println!("You guessed: {}", guess); }

Listing 2-1: Code that gets a guess from the user and prints it
清单 2-1:从用户那里获取猜测并打印出来的代码

This code contains a lot of information, so let’s go over it line by line. To obtain user input and then print the result as output, we need to bring the io input/output library into scope. The io library comes from the standard library, known as std:
这段代码包含了很多信息,所以让我们逐行看一下。为了获取用户输入然后将结果打印为输出,我们需要将io输入/输出库纳入范围。 io库来自标准库,称为std

use std::io; fn main() { println!("Guess the number!"); println!("Please input your guess."); let mut guess = String::new(); io::stdin() .read_line(&mut guess) .expect("Failed to read line"); println!("You guessed: {}", guess); }

By default, Rust has a set of items defined in the standard library that it brings into the scope of every program. This set is called the prelude, and you can see everything in it in the standard library documentation.
默认情况下,Rust 在标准库中定义了一组项目,并将其引入每个程序的范围内。该集合称为前奏,您可以在标准库文档中看到其中的所有内容。

If a type you want to use isn’t in the prelude, you have to bring that type into scope explicitly with a use statement. Using the std::io library provides you with a number of useful features, including the ability to accept user input.
如果您要使用的类型不在前奏中,则必须使用use语句显式将该类型引入作用域。使用std::io库为您提供了许多有用的功能,包括接受用户输入的能力。

As you saw in Chapter 1, the main function is the entry point into the program:
正如您在第 1 章中看到的, main函数是程序的入口点:

use std::io; fn main() { println!("Guess the number!"); println!("Please input your guess."); let mut guess = String::new(); io::stdin() .read_line(&mut guess) .expect("Failed to read line"); println!("You guessed: {}", guess); }

The fn syntax declares a new function; the parentheses, (), indicate there are no parameters; and the curly bracket, {, starts the body of the function.
fn语法声明一个新函数;括号()表示没有参数;大括号{开始函数体。

As you also learned in Chapter 1, println! is a macro that prints a string to the screen:
正如您在第 1 章中了解到的, println!是一个将字符串打印到屏幕上的宏:

use std::io; fn main() { println!("Guess the number!"); println!("Please input your guess."); let mut guess = String::new(); io::stdin() .read_line(&mut guess) .expect("Failed to read line"); println!("You guessed: {}", guess); }

This code is printing a prompt stating what the game is and requesting input from the user.
此代码打印一条提示,说明游戏是什么并请求用户输入。

Storing Values with Variables
用变量存储值

Next, we’ll create a variable to store the user input, like this:
接下来,我们将创建一个变量来存储用户输入,如下所示:

use std::io; fn main() { println!("Guess the number!"); println!("Please input your guess."); let mut guess = String::new(); io::stdin() .read_line(&mut guess) .expect("Failed to read line"); println!("You guessed: {}", guess); }

Now the program is getting interesting! There’s a lot going on in this little line. We use the let statement to create the variable. Here’s another example:
现在节目变得有趣了!这短短的一行里发生了很多事情。我们使用let语句来创建变量。这是另一个例子:

let apples = 5;

This line creates a new variable named apples and binds it to the value 5. In Rust, variables are immutable by default, meaning once we give the variable a value, the value won’t change. We’ll be discussing this concept in detail in the “Variables and Mutability” section in Chapter 3. To make a variable mutable, we add mut before the variable name:
这一行创建了一个名为apples新变量,并将其绑定到值 5。在 Rust 中,默认情况下变量是不可变的,这意味着一旦我们给变量赋予了值,该值就不会改变。我们将在第 3 章的“变量和可变性”忽略部分详细讨论这个概念。为了使变量可变,我们在变量名前添加mut

let apples = 5; // immutable let mut bananas = 5; // mutable

Note: The // syntax starts a comment that continues until the end of the line. Rust ignores everything in comments. We’ll discuss comments in more detail in Chapter 3.
注意: //语法开始一个注释,一直持续到该行末尾。 Rust 会忽略注释中的所有内容。我们将在第 3 章中更详细地讨论注释。

Returning to the guessing game program, you now know that let mut guess will introduce a mutable variable named guess. The equal sign (=) tells Rust we want to bind something to the variable now. On the right of the equal sign is the value that guess is bound to, which is the result of calling String::new, a function that returns a new instance of a String. String is a string type provided by the standard library that is a growable, UTF-8 encoded bit of text.
回到猜谜游戏程序,您现在知道let mut guess将引入一个名为guess的可变变量。等号 ( = ) 告诉 Rust 我们现在想要将某些内容绑定到变量。等号右侧是guess所绑定的值,它是调用String::new结果,该函数返回String的新实例。 String忽略是标准库提供的字符串类型,它是可增长的 UTF-8 编码文本位。

The :: syntax in the ::new line indicates that new is an associated function of the String type. An associated function is a function that’s implemented on a type, in this case String. This new function creates a new, empty string. You’ll find a new function on many types because it’s a common name for a function that makes a new value of some kind.
:: ::new语法表明newString类型的关联函数。关联函数是在类型(本例中为String上实现的函数。这个new函数创建一个新的空字符串。您会在许多类型上找到new函数,因为它是产生某种新值的函数的通用名称。

In full, the let mut guess = String::new(); line has created a mutable variable that is currently bound to a new, empty instance of a String. Whew!
完整来说, let mut guess = String::new(); line 创建了一个可变变量,该变量当前绑定到String的新空实例。哇!

Receiving User Input 接收用户输入

Recall that we included the input/output functionality from the standard library with use std::io; on the first line of the program. Now we’ll call the stdin function from the io module, which will allow us to handle user input:
回想一下,我们通过use std::io;包含了标准库中的输入/输出功能。在程序的第一行。现在我们将从io模块调用stdin函数,这将允许我们处理用户输入:

use std::io; fn main() { println!("Guess the number!"); println!("Please input your guess."); let mut guess = String::new(); io::stdin() .read_line(&mut guess) .expect("Failed to read line"); println!("You guessed: {}", guess); }

If we hadn’t imported the io library with use std::io; at the beginning of the program, we could still use the function by writing this function call as std::io::stdin. The stdin function returns an instance of std::io::Stdin, which is a type that represents a handle to the standard input for your terminal.
如果我们没有use std::io;导入io库在程序开始时,我们仍然可以通过将此函数调用编写为std::io::stdin来使用该函数。 stdin函数返回std::io::Stdin的实例,它是表示终端标准输入句柄的类型。

Next, the line .read_line(&mut guess) calls the read_line method on the standard input handle to get input from the user. We’re also passing &mut guess as the argument to read_line to tell it what string to store the user input in. The full job of read_line is to take whatever the user types into standard input and append that into a string (without overwriting its contents), so we therefore pass that string as an argument. The string argument needs to be mutable so the method can change the string’s content.
接下来,行.read_line(&mut guess)调用标准输入句柄上的read_line方法以获取用户的输入。我们还将&mut guess作为参数传递给read_line ,告诉它将用户输入存储在哪个字符串中。 read_line的完整工作是将用户输入到标准输入中的所有内容并将其附加到字符串中(而不覆盖其内容) ),因此我们将该字符串作为参数传递。字符串参数需要是可变的,以便该方法可以更改字符串的内容。

The & indicates that this argument is a reference, which gives you a way to let multiple parts of your code access one piece of data without needing to copy that data into memory multiple times. References are a complex feature, and one of Rust’s major advantages is how safe and easy it is to use references. You don’t need to know a lot of those details to finish this program. For now, all you need to know is that, like variables, references are immutable by default. Hence, you need to write &mut guess rather than &guess to make it mutable. (Chapter 4 will explain references more thoroughly.)
&表示该参数是一个引用,它为您提供了一种让代码的多个部分访问一条数据的方法,而无需多次将该数据复制到内存中。引用是一项复杂的功能,Rust 的主要优点之一是使用引用的安全性和易用性。您不需要了解很多细节即可完成此程序。现在,您需要知道的是,与变量一样,默认情况下引用是不可变的。因此,您需要编写&mut guess而不是&guess来使其可变。 (第 4 章将更彻底地解释参考文献。)

Handling Potential Failure with Result
处理潜在的失败和Result

We’re still working on this line of code. We’re now discussing a third line of text, but note that it’s still part of a single logical line of code. The next part is this method:
我们仍在研究这行代码。我们现在正在讨论第三行文本,但请注意,它仍然是单个逻辑代码行的一部分。下一部分是这个方法:

use std::io; fn main() { println!("Guess the number!"); println!("Please input your guess."); let mut guess = String::new(); io::stdin() .read_line(&mut guess) .expect("Failed to read line"); println!("You guessed: {}", guess); }

We could have written this code as:
我们可以将这段代码写成:

io::stdin().read_line(&mut guess).expect("Failed to read line");

However, one long line is difficult to read, so it’s best to divide it. It’s often wise to introduce a newline and other whitespace to help break up long lines when you call a method with the .method_name() syntax. Now let’s discuss what this line does.
然而,一长行很难阅读,所以最好将其分开。当您使用.method_name()语法调用方法时,引入换行符和其他空格来帮助分解长行通常是明智的做法。现在我们来讨论一下这条线的作用。

As mentioned earlier, read_line puts whatever the user enters into the string we pass to it, but it also returns a Result value. Result is an enumeration, often called an enum, which is a type that can be in one of multiple possible states. We call each possible state a variant.
如前所述, read_line将用户输入的任何内容放入我们传递给它的字符串中,但它也返回一个Result值。 Result忽略是枚举忽略,通常称为枚举,它是一种可以处于多种可能状态之一的类型。我们将每种可能的状态称为变体

Chapter 6 will cover enums in more detail. The purpose of these Result types is to encode error-handling information.
第 6 章ignore 将更详细地介绍枚举。这些Result类型的目的是对错误处理信息进行编码。

Result’s variants are Ok and Err. The Ok variant indicates the operation was successful, and inside Ok is the successfully generated value. The Err variant means the operation failed, and Err contains information about how or why the operation failed.
Result的变体是OkErrOk变量表示操作成功, Ok里面是成功生成的值。 Err变体表示操作失败, Err包含有关操作如何或为何失败的信息。

Values of the Result type, like values of any type, have methods defined on them. An instance of Result has an expect method that you can call. If this instance of Result is an Err value, expect will cause the program to crash and display the message that you passed as an argument to expect. If the read_line method returns an Err, it would likely be the result of an error coming from the underlying operating system. If this instance of Result is an Ok value, expect will take the return value that Ok is holding and return just that value to you so you can use it. In this case, that value is the number of bytes in the user’s input.
与任何类型的值一样, Result类型的值也定义了方法。 Result的实例有一个您可以调用的expect方法ignore。如果Result的此实例是Err值, expect将导致程序崩溃并显示您作为参数传递给expect的消息。如果read_line方法返回Err ,则可能是来自底层操作系统的错误的结果。如果Result的这个实例是Ok值, expect将获取Ok所保存的返回值并将该值返回给您,以便您可以使用它。在本例中,该值是用户输入中的字节数。

If you don’t call expect, the program will compile, but you’ll get a warning:
如果您不调用expect ,程序将编译,但您会收到警告:

$ cargo build Compiling guessing_game v0.1.0 (file:///projects/guessing_game) warning: unused `Result` that must be used --> src/main.rs:10:5 | 10 | io::stdin().read_line(&mut guess); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | = note: this `Result` may be an `Err` variant, which should be handled = note: `#[warn(unused_must_use)]` on by default help: use `let _ = ...` to ignore the resulting value | 10 | let _ = io::stdin().read_line(&mut guess); | +++++++ warning: `guessing_game` (bin "guessing_game") generated 1 warning Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.59s

Rust warns that you haven’t used the Result value returned from read_line, indicating that the program hasn’t handled a possible error.
Rust 警告您尚未使用从read_line返回的Result值,这表明程序尚未处理可能的错误。

The right way to suppress the warning is to actually write error-handling code, but in our case we just want to crash this program when a problem occurs, so we can use expect. You’ll learn about recovering from errors in Chapter 9.
抑制警告的正确方法是实际编写错误处理代码,但在我们的例子中,我们只想在出现问题时使该程序崩溃,因此我们可以使用expect 。您将在第 9 章中了解如何从错误中恢复 忽略 。

Printing Values with println! Placeholders
使用println!打印值占位符

Aside from the closing curly bracket, there’s only one more line to discuss in the code so far:
除了右大括号之外,到目前为止,代码中只剩下一行需要讨论:

use std::io; fn main() { println!("Guess the number!"); println!("Please input your guess."); let mut guess = String::new(); io::stdin() .read_line(&mut guess) .expect("Failed to read line"); println!("You guessed: {}", guess); }

This line prints the string that now contains the user’s input. The {} set of curly brackets is a placeholder: think of {} as little crab pincers that hold a value in place. When printing the value of a variable, the variable name can go inside the curly brackets. When printing the result of evaluating an expression, place empty curly brackets in the format string, then follow the format string with a comma-separated list of expressions to print in each empty curly bracket placeholder in the same order. Printing a variable and the result of an expression in one call to println! would look like this:
此行打印现在包含用户输入的字符串。大括号{}组是一个占位符:将{}想象成固定值的小螃蟹钳子。打印变量值时,变量名称可以放在大括号内。打印表达式求值结果时,将空大括号放在格式字符串中,然后在格式字符串后跟上以逗号分隔的表达式列表,以相同的顺序在每个空大括号占位符中打印。在一次调用println!看起来像这样:

#![allow(unused)] fn main() { let x = 5; let y = 10; println!("x = {x} and y + 2 = {}", y + 2); }

This code would print x = 5 and y + 2 = 12.
此代码将打印x = 5 and y + 2 = 12

Testing the First Part 测试第一部分

Let’s test the first part of the guessing game. Run it using cargo run:
让我们测试一下猜谜游戏的第一部分。使用cargo run运行它:

$ cargo run Compiling guessing_game v0.1.0 (file:///projects/guessing_game) Finished dev [unoptimized + debuginfo] target(s) in 6.44s Running `target/debug/guessing_game` Guess the number! Please input your guess. 6 You guessed: 6

At this point, the first part of the game is done: we’re getting input from the keyboard and then printing it.
此时,游戏的第一部分已经完成:我们从键盘获取输入,然后打印它。

Generating a Secret Number
生成一个秘密号码

Next, we need to generate a secret number that the user will try to guess. The secret number should be different every time so the game is fun to play more than once. We’ll use a random number between 1 and 100 so the game isn’t too difficult. Rust doesn’t yet include random number functionality in its standard library. However, the Rust team does provide a rand crate with said functionality.
接下来,我们需要生成一个用户将尝试猜测的秘密数字。每次的秘密数字应该不同,这样玩多次游戏就会很有趣。我们将使用 1 到 100 之间的随机数,这样游戏就不会太困难。 Rust 的标准库中尚未包含随机数功能。然而,Rust 团队确实提供了具有上述功能的rand

Using a Crate to Get More Functionality
使用 crate 获得更多功能

Remember that a crate is a collection of Rust source code files. The project we’ve been building is a binary crate, which is an executable. The rand crate is a library crate, which contains code that is intended to be used in other programs and can’t be executed on its own.
请记住,板条箱是 Rust 源代码文件的集合。我们一直在构建的项目是一个二进制 crate ,它是一个可执行文件。 rand crate 是一个库 crate ,其中包含旨在在其他程序中使用且不能单独执行的代码。

Cargo’s coordination of external crates is where Cargo really shines. Before we can write code that uses rand, we need to modify the Cargo.toml file to include the rand crate as a dependency. Open that file now and add the following line to the bottom, beneath the [dependencies] section header that Cargo created for you. Be sure to specify rand exactly as we have here, with this version number, or the code examples in this tutorial may not work:
Cargo 与外部板条箱的协调是 Cargo 真正的亮点。在编写使用rand的代码之前,我们需要修改Cargo.toml文件以包含rand箱作为依赖项。现在打开该文件,并将以下行添加到底部,即 Cargo 为您创建的[dependencies]部分标题下方。请务必使用此版本号完全按照我们此处的方式指定rand ,否则本教程中的代码示例可能无法工作:

Filename: Cargo.toml 文件名:Cargo.toml

[dependencies] rand = "0.8.5"

In the Cargo.toml file, everything that follows a header is part of that section that continues until another section starts. In [dependencies] you tell Cargo which external crates your project depends on and which versions of those crates you require. In this case, we specify the rand crate with the semantic version specifier 0.8.5. Cargo understands Semantic Versioning (sometimes called SemVer), which is a standard for writing version numbers. The specifier 0.8.5 is actually shorthand for ^0.8.5, which means any version that is at least 0.8.5 but below 0.9.0.
Cargo.toml文件中,标头后面的所有内容都是该部分的一部分,一直持续到另一个部分开始。在[dependencies]中,您告诉 Cargo 您的项目依赖哪些外部 crate,以及您需要这些 crate 的哪些版本。在本例中,我们使用语义版本说明符0.8.5指定rand箱。 Cargo 理解语义版本控制忽略(有时称为SemVer ),这是编写版本号的标准。说明符0.8.5实际上是^0.8.5的简写,这意味着至少 0.8.5 但低于 0.9.0 的任何版本。

Cargo considers these versions to have public APIs compatible with version 0.8.5, and this specification ensures you’ll get the latest patch release that will still compile with the code in this chapter. Any version 0.9.0 or greater is not guaranteed to have the same API as what the following examples use.
Cargo 认为这些版本具有与 0.8.5 版本兼容的公共 API,并且此规范确保您将获得最新的补丁版本,该版本仍将使用本章中的代码进行编译。不保证任何 0.9.0 或更高版本具有与以下示例使用的 API 相同的 API。

Now, without changing any of the code, let’s build the project, as shown in Listing 2-2.
现在,在不更改任何代码的情况下,让我们构建项目,如清单 2-2 所示。

$ cargo build Updating crates.io index Downloaded rand v0.8.5 Downloaded libc v0.2.127 Downloaded getrandom v0.2.7 Downloaded cfg-if v1.0.0 Downloaded ppv-lite86 v0.2.16 Downloaded rand_chacha v0.3.1 Downloaded rand_core v0.6.3 Compiling libc v0.2.127 Compiling getrandom v0.2.7 Compiling cfg-if v1.0.0 Compiling ppv-lite86 v0.2.16 Compiling rand_core v0.6.3 Compiling rand_chacha v0.3.1 Compiling rand v0.8.5 Compiling guessing_game v0.1.0 (file:///projects/guessing_game) Finished dev [unoptimized + debuginfo] target(s) in 2.53s

Listing 2-2: The output from running cargo build after adding the rand crate as a dependency
清单 2-2:添加 rand 箱作为依赖项后运行cargo build的输出

You may see different version numbers (but they will all be compatible with the code, thanks to SemVer!) and different lines (depending on the operating system), and the lines may be in a different order.
您可能会看到不同的版本号(但由于 SemVer,它们都与代码兼容!)和不同的行(取决于操作系统),并且这些行的顺序可能不同。

When we include an external dependency, Cargo fetches the latest versions of everything that dependency needs from the registry, which is a copy of data from Crates.io. Crates.io is where people in the Rust ecosystem post their open source Rust projects for others to use.
当我们包含外部依赖项时,Cargo 会从注册表中获取该依赖项所需的所有内容的最新版本,该注册表是来自Crates.io的数据副本。 Crates.io 是 Rust 生态系统中的人们发布开源 Rust 项目供其他人使用的地方。

After updating the registry, Cargo checks the [dependencies] section and downloads any crates listed that aren’t already downloaded. In this case, although we only listed rand as a dependency, Cargo also grabbed other crates that rand depends on to work. After downloading the crates, Rust compiles them and then compiles the project with the dependencies available.
更新注册表后,Cargo 检查[dependencies]部分并下载列出的所有尚未下载的包。在本例中,虽然我们只将rand列为依赖项,但 Cargo 还抓取了rand工作所依赖的其他 crate。下载包后,Rust 会编译它们,然后使用可用的依赖项编译项目。

If you immediately run cargo build again without making any changes, you won’t get any output aside from the Finished line. Cargo knows it has already downloaded and compiled the dependencies, and you haven’t changed anything about them in your Cargo.toml file. Cargo also knows that you haven’t changed anything about your code, so it doesn’t recompile that either. With nothing to do, it simply exits.
如果您立即再次运行cargo build而不进行任何更改,则除了Finished行之外,您将不会获得任何输出。 Cargo 知道它已经下载并编译了依赖项,并且您没有在Cargo.toml文件中更改它们的任何内容。 Cargo 还知道您没有更改代码的任何内容,因此它也不会重新编译。无事可做,它就简单地退出了。

If you open the src/main.rs file, make a trivial change, and then save it and build again, you’ll only see two lines of output:
如果您打开src/main.rs文件,进行一些简单的更改,然后保存并再次构建,您将只会看到两行输出:

$ cargo build Compiling guessing_game v0.1.0 (file:///projects/guessing_game) Finished dev [unoptimized + debuginfo] target(s) in 2.53 secs

These lines show that Cargo only updates the build with your tiny change to the src/main.rs file. Your dependencies haven’t changed, so Cargo knows it can reuse what it has already downloaded and compiled for those.
这些行表明 Cargo 仅通过对src/main.rs文件进行微小更改来更新构建。您的依赖项没有改变,因此 Cargo 知道它可以重用已经下载并编译的内容。

Ensuring Reproducible Builds with the Cargo.lock File
使用Cargo.lock文件确保可重复构建

Cargo has a mechanism that ensures you can rebuild the same artifact every time you or anyone else builds your code: Cargo will use only the versions of the dependencies you specified until you indicate otherwise. For example, say that next week version 0.8.6 of the rand crate comes out, and that version contains an important bug fix, but it also contains a regression that will break your code. To handle this, Rust creates the Cargo.lock file the first time you run cargo build, so we now have this in the guessing_game directory.
Cargo 具有一种机制,可确保您或其他任何人每次构建代码时都可以重建相同的工件:Cargo 将仅使用您指定的依赖项版本,直到您另有指示。例如,假设下周rand crate 的 0.8.6 版本将发布,该版本包含一个重要的错误修复,但它也包含一个会破坏您的代码的回归。为了处理这个问题,Rust 在您第一次运行cargo build时创建了Cargo.lock文件,因此我们现在将其放置在guessing_game目录中。

When you build a project for the first time, Cargo figures out all the versions of the dependencies that fit the criteria and then writes them to the Cargo.lock file. When you build your project in the future, Cargo will see that the Cargo.lock file exists and will use the versions specified there rather than doing all the work of figuring out versions again. This lets you have a reproducible build automatically. In other words, your project will remain at 0.8.5 until you explicitly upgrade, thanks to the Cargo.lock file. Because the Cargo.lock file is important for reproducible builds, it’s often checked into source control with the rest of the code in your project.
当您第一次构建项目时,Cargo 会找出符合条件的所有依赖项版本,然后将它们写入Cargo.lock文件。当您将来构建项目时,Cargo 将看到Cargo.lock文件存在,并将使用其中指定的版本,而不是再次执行确定版本的所有工作。这可以让您自动获得可重复的构建。换句话说,在您明确升级之前,您的项目将保持在 0.8.5,这要归功于Cargo.lock文件。由于Cargo.lock文件对于可重现的构建非常重要,因此它通常会与项目中的其余代码一起检入源代码管理。

Updating a Crate to Get a New Version
更新 crate 以获取新版本

When you do want to update a crate, Cargo provides the command update, which will ignore the Cargo.lock file and figure out all the latest versions that fit your specifications in Cargo.toml. Cargo will then write those versions to the Cargo.lock file. In this case, Cargo will only look for versions greater than 0.8.5 and less than 0.9.0. If the rand crate has released the two new versions 0.8.6 and 0.9.0, you would see the following if you ran cargo update:
当您确实想要更新 crate 时,Cargo 提供了命令update ,该命令将忽略Cargo.lock文件并找出符合您在Cargo.toml中的规范的所有最新版本。然后 Cargo 会将这些版本写入Cargo.lock文件。在这种情况下,Cargo 将仅查找大于 0.8.5 且小于 0.9.0 的版本。如果rand crate 已经发布了两个新版本 0.8.6 和 0.9.0,那么如果您运行cargo update您将看到以下内容:

$ cargo update Updating crates.io index Updating rand v0.8.5 -> v0.8.6

Cargo ignores the 0.9.0 release. At this point, you would also notice a change in your Cargo.lock file noting that the version of the rand crate you are now using is 0.8.6. To use rand version 0.9.0 or any version in the 0.9.x series, you’d have to update the Cargo.toml file to look like this instead:
Cargo 忽略 0.9.0 版本。此时,您还会注意到Cargo.lock文件中的更改,指出您现在使用的rand箱的版本是 0.8.6。使用rand版本 0.9.0 或 0.9 中的任何版本。 x系列,您必须更新Cargo.toml文件,使其看起来像这样:

[dependencies] rand = "0.9.0"

The next time you run cargo build, Cargo will update the registry of crates available and reevaluate your rand requirements according to the new version you have specified.
下次您运行cargo build时,Cargo 将更新可用 crate 的注册表,并根据您指定的新版本重新评估您的rand要求。

There’s a lot more to say about Cargo and its ecosystem, which we’ll discuss in Chapter 14, but for now, that’s all you need to know. Cargo makes it very easy to reuse libraries, so Rustaceans are able to write smaller projects that are assembled from a number of packages.
关于Cargoignore及其生态系统ignore还有很多要说的,我们将在第14章中讨论,但现在,这就是你需要知道的全部。 Cargo 使得重用库变得非常容易,因此 Rustaceans 能够编写由多个包组装而成的较小项目。

Generating a Random Number
生成随机数

Let’s start using rand to generate a number to guess. The next step is to update src/main.rs, as shown in Listing 2-3.
让我们开始使用rand生成一个数字来猜测。下一步是更新src/main.rs ,如清单 2-3 所示。

Filename: src/main.rs 文件名:src/main.rs

use std::io; use rand::Rng; fn main() { println!("Guess the number!"); let secret_number = rand::thread_rng().gen_range(1..=100); println!("The secret number is: {secret_number}"); println!("Please input your guess."); let mut guess = String::new(); io::stdin() .read_line(&mut guess) .expect("Failed to read line"); println!("You guessed: {guess}"); }

Listing 2-3: Adding code to generate a random number
清单 2-3:添加代码来生成随机数

First we add the line use rand::Rng;. The Rng trait defines methods that random number generators implement, and this trait must be in scope for us to use those methods. Chapter 10 will cover traits in detail.
首先我们添加行use rand::Rng;Rng特征定义了随机数生成器实现的方法,并且该特征必须在我们使用这些方法的范围内。第 10 章将详细介绍特征。

Next, we’re adding two lines in the middle. In the first line, we call the rand::thread_rng function that gives us the particular random number generator we’re going to use: one that is local to the current thread of execution and is seeded by the operating system. Then we call the gen_range method on the random number generator. This method is defined by the Rng trait that we brought into scope with the use rand::Rng; statement. The gen_range method takes a range expression as an argument and generates a random number in the range. The kind of range expression we’re using here takes the form start..=end and is inclusive on the lower and upper bounds, so we need to specify 1..=100 to request a number between 1 and 100.
接下来,我们在中间添加两行。在第一行中,我们调用rand::thread_rng函数,该函数为我们提供将要使用的特定随机数生成器:一个位于当前执行线程本地并由操作系统播种的随机数生成器。然后我们调用随机数生成器上的gen_range方法。该方法由我们通过use rand::Rng;引入范围的Rng特征定义。陈述。 gen_range方法采用范围表达式作为参数,并在该范围内生成一个随机数。我们在这里使用的范围表达式采用start..=end形式,并且包含下限和上限,因此我们需要指定1..=100来请求 1 到 100 之间的数字。

Note: You won’t just know which traits to use and which methods and functions to call from a crate, so each crate has documentation with instructions for using it. Another neat feature of Cargo is that running the cargo doc --open command will build documentation provided by all your dependencies locally and open it in your browser. If you’re interested in other functionality in the rand crate, for example, run cargo doc --open and click rand in the sidebar on the left.
注意:您不仅知道要使用哪些特征以及要从包中调用哪些方法和函数,因此每个包都有包含使用说明的文档。 Cargo 的另一个巧妙功能是,运行cargo doc --open命令将在本地构建所有依赖项提供的文档,并在浏览器中打开它。例如,如果您对rand箱中的其他功能感兴趣,请运行cargo doc --open并单击左侧边栏中的rand

The second new line prints the secret number. This is useful while we’re developing the program to be able to test it, but we’ll delete it from the final version. It’s not much of a game if the program prints the answer as soon as it starts!
第二个新行打印秘密号码。这在我们开发程序以对其进行测试时很有用,但我们将从最终版本中删除它。如果程序一开始就打印出答案,那就不算什么游戏了!

Try running the program a few times:
尝试运行该程序几次:

$ cargo run Compiling guessing_game v0.1.0 (file:///projects/guessing_game) Finished dev [unoptimized + debuginfo] target(s) in 2.53s Running `target/debug/guessing_game` Guess the number! The secret number is: 7 Please input your guess. 4 You guessed: 4 $ cargo run Finished dev [unoptimized + debuginfo] target(s) in 0.02s Running `target/debug/guessing_game` Guess the number! The secret number is: 83 Please input your guess. 5 You guessed: 5

You should get different random numbers, and they should all be numbers between 1 and 100. Great job!
您应该得到不同的随机数,并且它们都应该是 1 到 100 之间的数字。干得好!

Comparing the Guess to the Secret Number
将猜测与秘密数字进行比较

Now that we have user input and a random number, we can compare them. That step is shown in Listing 2-4. Note that this code won’t compile just yet, as we will explain.
现在我们有了用户输入和随机数,我们可以比较它们。该步骤如清单 2-4 所示。请注意,正如我们将解释的那样,该代码还无法编译。

Filename: src/main.rs 文件名:src/main.rs

use rand::Rng; use std::cmp::Ordering; use std::io; fn main() { // --snip-- println!("Guess the number!"); let secret_number = rand::thread_rng().gen_range(1..=100); println!("The secret number is: {secret_number}"); println!("Please input your guess."); let mut guess = String::new(); io::stdin() .read_line(&mut guess) .expect("Failed to read line"); println!("You guessed: {guess}"); match guess.cmp(&secret_number) { Ordering::Less => println!("Too small!"), Ordering::Greater => println!("Too big!"), Ordering::Equal => println!("You win!"), } }

Listing 2-4: Handling the possible return values of comparing two numbers
清单 2-4:处理比较两个数字时可能的返回值

First we add another use statement, bringing a type called std::cmp::Ordering into scope from the standard library. The Ordering type is another enum and has the variants Less, Greater, and Equal. These are the three outcomes that are possible when you compare two values.
首先,我们添加另一个use语句,将一个名为std::cmp::Ordering类型从标准库引入范围。 Ordering类型是另一个枚举,具有LessGreaterEqual变体。这是比较两个值时可能出现的三种结果。

Then we add five new lines at the bottom that use the Ordering type. The cmp method compares two values and can be called on anything that can be compared. It takes a reference to whatever you want to compare with: here it’s comparing guess to secret_number. Then it returns a variant of the Ordering enum we brought into scope with the use statement. We use a match expression to decide what to do next based on which variant of Ordering was returned from the call to cmp with the values in guess and secret_number.
然后我们在底部添加五个使用Ordering类型的新行。 cmp方法比较两个值,并且可以在任何可以比较的对象上调用。它需要引用您想要比较的任何内容:这里是将guesssecret_number进行比较。然后它返回我们通过use语句引入范围的Ordering枚举的变体。我们使用match忽略表达式来根据从调用cmp返回的Ordering变体(具有guesssecret_number中的值)来决定下一步要做什么。

A match expression is made up of arms. An arm consists of a pattern to match against, and the code that should be run if the value given to match fits that arm’s pattern. Rust takes the value given to match and looks through each arm’s pattern in turn. Patterns and the match construct are powerful Rust features: they let you express a variety of situations your code might encounter and they make sure you handle them all. These features will be covered in detail in Chapter 6 and Chapter 18, respectively.
match表达式由手臂组成。手臂由要匹配的模式以及在给定的match值适合该手臂的模式时应运行的代码组成。 Rust 获取给定的值进行match ,并依次查看每个手臂的模式。模式和match构造是强大的 Rust 功能:它们让您表达代码可能遇到的各种情况,并确保您能够处理所有情况。这些功能将分别在第 6 章和第 18 章中详细介绍。

Let’s walk through an example with the match expression we use here. Say that the user has guessed 50 and the randomly generated secret number this time is 38.
让我们看一下这里使用的match表达式的示例。假设用户猜中了 50,这次随机生成的秘密数字是 38。

When the code compares 50 to 38, the cmp method will return Ordering::Greater because 50 is greater than 38. The match expression gets the Ordering::Greater value and starts checking each arm’s pattern. It looks at the first arm’s pattern, Ordering::Less, and sees that the value Ordering::Greater does not match Ordering::Less, so it ignores the code in that arm and moves to the next arm. The next arm’s pattern is Ordering::Greater, which does match Ordering::Greater! The associated code in that arm will execute and print Too big! to the screen. The match expression ends after the first successful match, so it won’t look at the last arm in this scenario.
当代码比较 50 和 38 时, cmp方法将返回Ordering::Greater ,因为 50 大于 38。 match表达式获取Ordering::Greater值并开始检查每个臂的模式。它查看第一个臂的模式Ordering::Less ,并发现值Ordering::GreaterOrdering::Less不匹配,因此它忽略该臂中的代码并移至下一个臂。下一个手臂的模式是Ordering::Greater ,它与Ordering::Greater匹配!该手臂中的相关代码将执行并打印Too big!到屏幕上。 match表达式在第一次成功匹配后结束,因此在这种情况下它不会查看最后一个分支。

However, the code in Listing 2-4 won’t compile yet. Let’s try it:
但是,清单 2-4 中的代码还无法编译。我们来尝试一下:

$ cargo build Compiling libc v0.2.86 Compiling getrandom v0.2.2 Compiling cfg-if v1.0.0 Compiling ppv-lite86 v0.2.10 Compiling rand_core v0.6.2 Compiling rand_chacha v0.3.0 Compiling rand v0.8.5 Compiling guessing_game v0.1.0 (file:///projects/guessing_game) error[E0308]: mismatched types --> src/main.rs:22:21 | 22 | match guess.cmp(&secret_number) { | --- ^^^^^^^^^^^^^^ expected `&String`, found `&{integer}` | | | arguments to this method are incorrect | = note: expected reference `&String` found reference `&{integer}` note: method defined here --> /rustc/9b00956e56009bab2aa15d7bff10916599e3d6d6/library/core/src/cmp.rs:836:8 For more information about this error, try `rustc --explain E0308`. error: could not compile `guessing_game` (bin "guessing_game") due to 1 previous error

The core of the error states that there are mismatched types. Rust has a strong, static type system. However, it also has type inference. When we wrote let mut guess = String::new(), Rust was able to infer that guess should be a String and didn’t make us write the type. The secret_number, on the other hand, is a number type. A few of Rust’s number types can have a value between 1 and 100: i32, a 32-bit number; u32, an unsigned 32-bit number; i64, a 64-bit number; as well as others. Unless otherwise specified, Rust defaults to an i32, which is the type of secret_number unless you add type information elsewhere that would cause Rust to infer a different numerical type. The reason for the error is that Rust cannot compare a string and a number type.
错误的核心是类型不匹配。 Rust 拥有强大的静态类型系统。然而,它也有类型推断。当我们编写let mut guess = String::new()时,Rust 能够推断guess应该是一个String并且不会让我们编写类型。另一方面, secret_number是一种数字类型。 Rust 的一些数字类型的值可以在 1 到 100 之间: i32 ,一个 32 位数字; u32 ,无符号 32 位数字; i64 ,64 位数字;以及其他人。除非另有说明,Rust 默认为i32 ,这是secret_number的类型,除非您在其他地方添加类型信息,这会导致 Rust 推断出不同的数字类型。错误的原因是 Rust 无法比较字符串和数字类型。

Ultimately, we want to convert the String the program reads as input into a number type so we can compare it numerically to the secret number. We do so by adding this line to the main function body:
最终,我们希望将程序作为输入读取的String转换为数字类型,以便我们可以将其与秘密数字进行数字比较。我们通过将此行添加到main函数体来实现:

Filename: src/main.rs 文件名:src/main.rs

use rand::Rng; use std::cmp::Ordering; use std::io; fn main() { println!("Guess the number!"); let secret_number = rand::thread_rng().gen_range(1..=100); println!("The secret number is: {secret_number}"); println!("Please input your guess."); // --snip-- let mut guess = String::new(); io::stdin() .read_line(&mut guess) .expect("Failed to read line"); let guess: u32 = guess.trim().parse().expect("Please type a number!"); println!("You guessed: {guess}"); match guess.cmp(&secret_number) { Ordering::Less => println!("Too small!"), Ordering::Greater => println!("Too big!"), Ordering::Equal => println!("You win!"), } }

The line is: 该行是:

let guess: u32 = guess.trim().parse().expect("Please type a number!");

We create a variable named guess. But wait, doesn’t the program already have a variable named guess? It does, but helpfully Rust allows us to shadow the previous value of guess with a new one. Shadowing lets us reuse the guess variable name rather than forcing us to create two unique variables, such as guess_str and guess, for example. We’ll cover this in more detail in Chapter 3, but for now, know that this feature is often used when you want to convert a value from one type to another type.
我们创建一个名为guess变量。但是等等,程序不是已经有一个名为guess变量了吗?确实如此,但 Rust 允许我们用新的猜测值来掩盖之前的guess值,这很有帮助。阴影允许我们重用guess变量名称,而不是强迫我们创建两个唯一的变量,例如guess_strguess 。我们将在第 3 章中更详细地介绍这一点。ignore,但是现在,当您想要将值从一种类型转换为另一种类型时,通常会使用此功能。

We bind this new variable to the expression guess.trim().parse(). The guess in the expression refers to the original guess variable that contained the input as a string. The trim method on a String instance will eliminate any whitespace at the beginning and end, which we must do to be able to compare the string to the u32, which can only contain numerical data. The user must press enter to satisfy read_line and input their guess, which adds a newline character to the string. For example, if the user types 5 and presses enter, guess looks like this: 5\n. The \n represents “newline.” (On Windows, pressing enter results in a carriage return and a newline, \r\n.) The trim method eliminates \n or \r\n, resulting in just 5.
我们将这个新变量绑定到表达式guess.trim().parse() 。表达式中的guess是指包含字符串输入的原始guess变量。 String实例上的trim方法将消除开头和结尾的任何空格,我们必须这样做才能将字符串与u32进行比较,u32 只能包含数字数据。用户必须按 enter 满足read_line并输入他们的猜测,这会向字符串添加换行符。例如,如果用户输入 5 并按 enterguess看起来像这样: 5\n\n代表“换行符”。 (在 Windows 上,按 enter 结果是一个回车符和一个换行符\r\n 。) trim方法消除了\n\r\n ,结果只是5

The parse method on strings converts a string to another type. Here, we use it to convert from a string to a number. We need to tell Rust the exact number type we want by using let guess: u32. The colon (:) after guess tells Rust we’ll annotate the variable’s type. Rust has a few built-in number types; the u32 seen here is an unsigned, 32-bit integer. It’s a good default choice for a small positive number. You’ll learn about other number types in Chapter 3.
字符串的parse方法ignore 将字符串转换为另一种类型。在这里,我们使用它从字符串转换为数字。我们需要使用let guess: u32告诉 Rust 我们想要的确切数字类型。 guess后的冒号 ( :告诉 Rust 我们将注释变量的类型。 Rust 有一些内置的数字类型;这里看到的u32是一个无符号的 32 位整数。对于较小的正数,这是一个很好的默认选择。您将在第 3 章中了解其他数字类型。

Additionally, the u32 annotation in this example program and the comparison with secret_number means Rust will infer that secret_number should be a u32 as well. So now the comparison will be between two values of the same type!
此外,此示例程序中的u32注释以及与secret_number的比较意味着 Rust 将推断secret_number也应该是u32 。所以现在比较将在相同类型的两个值之间进行!

The parse method will only work on characters that can logically be converted into numbers and so can easily cause errors. If, for example, the string contained A👍%, there would be no way to convert that to a number. Because it might fail, the parse method returns a Result type, much as the read_line method does (discussed earlier in “Handling Potential Failure with Result). We’ll treat this Result the same way by using the expect method again. If parse returns an Err Result variant because it couldn’t create a number from the string, the expect call will crash the game and print the message we give it. If parse can successfully convert the string to a number, it will return the Ok variant of Result, and expect will return the number that we want from the Ok value.
parse方法仅适用于逻辑上可以转换为数字的字符,因此很容易导致错误。例如,如果字符串包含A👍% ,则无法将其转换为数字。因为它可能会失败,所以parse方法返回Result类型,就像read_line方法所做的那样(前面在“使用结果处理潜在Result忽略中讨论过)。我们将再次使用expect方法以相同的方式处理此Result 。如果parse因为无法从字符串创建数字而返回Err Result变体,则expect调用将使游戏崩溃并打印我们提供的消息。如果parse可以成功地将字符串转换为数字,它将返回ResultOk变体,并且expect将从Ok值中返回我们想要的数字。

Let’s run the program now:
现在让我们运行该程序:

$ cargo run Compiling guessing_game v0.1.0 (file:///projects/guessing_game) Finished dev [unoptimized + debuginfo] target(s) in 0.43s Running `target/debug/guessing_game` Guess the number! The secret number is: 58 Please input your guess. 76 You guessed: 76 Too big!

Nice! Even though spaces were added before the guess, the program still figured out that the user guessed 76. Run the program a few times to verify the different behavior with different kinds of input: guess the number correctly, guess a number that is too high, and guess a number that is too low.
好的!即使在猜测之前添加了空格,程序仍然发现用户猜测了 76。运行程序几次以验证不同类型输入的不同行为:猜对数字,猜数字太高,并猜测一个太低的数字。

We have most of the game working now, but the user can make only one guess. Let’s change that by adding a loop!
现在游戏的大部分功能都可以运行,但用户只能做出一种猜测。让我们通过添加一个循环来改变它!

Allowing Multiple Guesses with Looping
允许通过循环进行多次猜测

The loop keyword creates an infinite loop. We’ll add a loop to give users more chances at guessing the number:
loop关键字创建无限循环。我们将添加一个循环,让用户有更多机会猜测数字:

Filename: src/main.rs 文件名:src/main.rs

use rand::Rng; use std::cmp::Ordering; use std::io; fn main() { println!("Guess the number!"); let secret_number = rand::thread_rng().gen_range(1..=100); // --snip-- println!("The secret number is: {secret_number}"); loop { println!("Please input your guess."); // --snip-- let mut guess = String::new(); io::stdin() .read_line(&mut guess) .expect("Failed to read line"); let guess: u32 = guess.trim().parse().expect("Please type a number!"); println!("You guessed: {guess}"); match guess.cmp(&secret_number) { Ordering::Less => println!("Too small!"), Ordering::Greater => println!("Too big!"), Ordering::Equal => println!("You win!"), } } }

As you can see, we’ve moved everything from the guess input prompt onward into a loop. Be sure to indent the lines inside the loop another four spaces each and run the program again. The program will now ask for another guess forever, which actually introduces a new problem. It doesn’t seem like the user can quit!
正如您所看到的,我们已将猜测输入提示中的所有内容移至循环中。确保将循环内的行每行缩进四个空格,然后再次运行程序。程序现在将永远要求另一个猜测,这实际上引入了一个新问题。看来用户不能退出啊!

The user could always interrupt the program by using the keyboard shortcut ctrl-c. But there’s another way to escape this insatiable monster, as mentioned in the parse discussion in “Comparing the Guess to the Secret Number”: if the user enters a non-number answer, the program will crash. We can take advantage of that to allow the user to quit, as shown here:
用户始终可以使用键盘快捷键中断程序 ctrl - c 。但还有另一种方法可以逃避这个贪得无厌的怪物,正如“比较猜测与秘密数字”中的parse讨论中提到的忽略:如果用户输入非数字答案,程序将崩溃。我们可以利用这一点来允许用户退出,如下所示:

$ cargo run Compiling guessing_game v0.1.0 (file:///projects/guessing_game) Finished dev [unoptimized + debuginfo] target(s) in 1.50s Running `target/debug/guessing_game` Guess the number! The secret number is: 59 Please input your guess. 45 You guessed: 45 Too small! Please input your guess. 60 You guessed: 60 Too big! Please input your guess. 59 You guessed: 59 You win! Please input your guess. quit thread 'main' panicked at 'Please type a number!: ParseIntError { kind: InvalidDigit }', src/main.rs:28:47 note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace

Typing quit will quit the game, but as you’ll notice, so will entering any other non-number input. This is suboptimal, to say the least; we want the game to also stop when the correct number is guessed.
输入quit将退出游戏,但您会注意到,输入任何其他非数字输入也会退出游戏。至少可以说,这是次优的;我们希望当猜到正确的数字时游戏也停止。

Quitting After a Correct Guess
猜测正确后退出

Let’s program the game to quit when the user wins by adding a break statement:
让我们通过添加一个break语句来编程游戏以在用户获胜时退出:

Filename: src/main.rs 文件名:src/main.rs

use rand::Rng; use std::cmp::Ordering; use std::io; fn main() { println!("Guess the number!"); let secret_number = rand::thread_rng().gen_range(1..=100); println!("The secret number is: {secret_number}"); loop { println!("Please input your guess."); let mut guess = String::new(); io::stdin() .read_line(&mut guess) .expect("Failed to read line"); let guess: u32 = guess.trim().parse().expect("Please type a number!"); println!("You guessed: {guess}"); // --snip-- match guess.cmp(&secret_number) { Ordering::Less => println!("Too small!"), Ordering::Greater => println!("Too big!"), Ordering::Equal => { println!("You win!"); break; } } } }

Adding the break line after You win! makes the program exit the loop when the user guesses the secret number correctly. Exiting the loop also means exiting the program, because the loop is the last part of main.
You win!后添加break !当用户正确猜出秘密数字时,使程序退出循环。退出循环也意味着退出程序,因为循环是main的最后一部分。

Handling Invalid Input 处理无效输入

To further refine the game’s behavior, rather than crashing the program when the user inputs a non-number, let’s make the game ignore a non-number so the user can continue guessing. We can do that by altering the line where guess is converted from a String to a u32, as shown in Listing 2-5.
为了进一步完善游戏的行为,让游戏忽略非数字以便用户可以继续猜测,而不是在用户输入非数字时使程序崩溃。我们可以通过更改将guessString转换为u32行来实现这一点,如清单 2-5 所示。

Filename: src/main.rs 文件名:src/main.rs

use rand::Rng; use std::cmp::Ordering; use std::io; fn main() { println!("Guess the number!"); let secret_number = rand::thread_rng().gen_range(1..=100); println!("The secret number is: {secret_number}"); loop { println!("Please input your guess."); let mut guess = String::new(); // --snip-- io::stdin() .read_line(&mut guess) .expect("Failed to read line"); let guess: u32 = match guess.trim().parse() { Ok(num) => num, Err(_) => continue, }; println!("You guessed: {guess}"); // --snip-- match guess.cmp(&secret_number) { Ordering::Less => println!("Too small!"), Ordering::Greater => println!("Too big!"), Ordering::Equal => { println!("You win!"); break; } } } }

Listing 2-5: Ignoring a non-number guess and asking for another guess instead of crashing the program
示例 2-5:忽略非数字猜测并要求另一个猜测而不是使程序崩溃

We switch from an expect call to a match expression to move from crashing on an error to handling the error. Remember that parse returns a Result type and Result is an enum that has the variants Ok and Err. We’re using a match expression here, as we did with the Ordering result of the cmp method.
我们从expect调用切换到match表达式,以从因错误而崩溃转变为处理错误。请记住, parse返回Result类型,并且Result是一个具有变体OkErr枚举。我们在这里使用match表达式,就像我们对cmp方法的Ordering结果所做的那样。

If parse is able to successfully turn the string into a number, it will return an Ok value that contains the resultant number. That Ok value will match the first arm’s pattern, and the match expression will just return the num value that parse produced and put inside the Ok value. That number will end up right where we want it in the new guess variable we’re creating.
如果parse能够成功将字符串转换为数字,它将返回包含结果数字的Ok值。该Ok值将与第一个臂的模式匹配,并且match表达式将仅返回parse生成的num值并将其放入Ok值中。该数字最终将位于我们正在创建的新guess变量中所需的位置。

If parse is not able to turn the string into a number, it will return an Err value that contains more information about the error. The Err value does not match the Ok(num) pattern in the first match arm, but it does match the Err(_) pattern in the second arm. The underscore, _, is a catchall value; in this example, we’re saying we want to match all Err values, no matter what information they have inside them. So the program will execute the second arm’s code, continue, which tells the program to go to the next iteration of the loop and ask for another guess. So, effectively, the program ignores all errors that parse might encounter!
如果parse无法将字符串转换为数字,它将返回一个Err值,其中包含有关错误的更多信息。 Err值与第一个match臂中的Ok(num)模式不匹配,但与第二个匹配臂中的Err(_)模式匹配。下划线_是一个包罗万象的值;在这个例子中,我们说我们想要匹配所有Err值,无论它们里面有什么信息。因此,程序将执行第二个分支的代码continue ,它告诉程序进入loop的下一次迭代并要求进行另一次猜测。因此,实际上,程序会忽略parse可能遇到的所有错误!

Now everything in the program should work as expected. Let’s try it:
现在程序中的所有内容都应该按预期工作。我们来尝试一下:

$ cargo run Compiling guessing_game v0.1.0 (file:///projects/guessing_game) Finished dev [unoptimized + debuginfo] target(s) in 4.45s Running `target/debug/guessing_game` Guess the number! The secret number is: 61 Please input your guess. 10 You guessed: 10 Too small! Please input your guess. 99 You guessed: 99 Too big! Please input your guess. foo Please input your guess. 61 You guessed: 61 You win!

Awesome! With one tiny final tweak, we will finish the guessing game. Recall that the program is still printing the secret number. That worked well for testing, but it ruins the game. Let’s delete the println! that outputs the secret number. Listing 2-6 shows the final code.
惊人的!通过最后的一点小小的调整,我们将完成这个猜谜游戏。回想一下,程序仍在打印秘密数字。这对于测试来说效果很好,但它却破坏了游戏。让我们删除println!输出秘密数字。清单 2-6 显示了最终的代码。

Filename: src/main.rs 文件名:src/main.rs

use rand::Rng; use std::cmp::Ordering; use std::io; fn main() { println!("Guess the number!"); let secret_number = rand::thread_rng().gen_range(1..=100); loop { println!("Please input your guess."); let mut guess = String::new(); io::stdin() .read_line(&mut guess) .expect("Failed to read line"); let guess: u32 = match guess.trim().parse() { Ok(num) => num, Err(_) => continue, }; println!("You guessed: {guess}"); match guess.cmp(&secret_number) { Ordering::Less => println!("Too small!"), Ordering::Greater => println!("Too big!"), Ordering::Equal => { println!("You win!"); break; } } } }

Listing 2-6: Complete guessing game code
清单2-6:完整的猜谜游戏代码

At this point, you’ve successfully built the guessing game. Congratulations!
至此,您已经成功构建了猜谜游戏。恭喜!

Summary 概括

This project was a hands-on way to introduce you to many new Rust concepts: let, match, functions, the use of external crates, and more. In the next few chapters, you’ll learn about these concepts in more detail. Chapter 3 covers concepts that most programming languages have, such as variables, data types, and functions, and shows how to use them in Rust. Chapter 4 explores ownership, a feature that makes Rust different from other languages. Chapter 5 discusses structs and method syntax, and Chapter 6 explains how enums work.
这个项目是向您介绍许多新的 Rust 概念的实践方式: letmatch 、函数、外部板条箱的使用等等。在接下来的几章中,您将更详细地了解这些概念。第 3 章涵盖了大多数编程语言所具有的概念,例如变量、数据类型和函数,并展示了如何在 Rust 中使用它们。第 4 章探讨了所有权,这是 Rust 与其他语言不同的一个特性。第 5 章讨论结构体和方法语法,第 6 章解释枚举的工作原理。

Common Programming Concepts
常见的编程概念

This chapter covers concepts that appear in almost every programming language and how they work in Rust. Many programming languages have much in common at their core. None of the concepts presented in this chapter are unique to Rust, but we’ll discuss them in the context of Rust and explain the conventions around using these concepts.
本章涵盖了几乎所有编程语言中出现的概念以及它们在 Rust 中的工作原理。许多编程语言的核心都有很多共同点。本章中提出的概念都不是 Rust 所独有的,但我们将在 Rust 的背景下讨论它们,并解释使用这些概念的约定。

Specifically, you’ll learn about variables, basic types, functions, comments, and control flow. These foundations will be in every Rust program, and learning them early will give you a strong core to start from.
具体来说,您将了解变量、基本类型、函数、注释和控制流。这些基础知识将存在于每个 Rust 程序中,尽早学习它们将为您提供强大的核心。

Keywords 关键词

The Rust language has a set of keywords that are reserved for use by the language only, much as in other languages. Keep in mind that you cannot use these words as names of variables or functions. Most of the keywords have special meanings, and you’ll be using them to do various tasks in your Rust programs; a few have no current functionality associated with them but have been reserved for functionality that might be added to Rust in the future. You can find a list of the keywords in Appendix A.
Rust 语言有一组保留仅供该语言使用的关键字,就像其他语言一样。请记住,您不能使用这些单词作为变量或函数的名称。大多数关键字都有特殊含义,您将使用它们在 Rust 程序中执行各种任务;一些当前没有与之相关的功能,但已为将来可能添加到 Rust 的功能保留。您可以在附录 Aignore中找到关键字列表。

Variables and Mutability 变量和可变性

As mentioned in the “Storing Values with Variables” section, by default, variables are immutable. This is one of many nudges Rust gives you to write your code in a way that takes advantage of the safety and easy concurrency that Rust offers. However, you still have the option to make your variables mutable. Let’s explore how and why Rust encourages you to favor immutability and why sometimes you might want to opt out.
正如“用变量存储值”忽略部分中提到的,默认情况下,变量是不可变的。这是 Rust 为您提供的众多推动力之一,让您能够利用 Rust 提供的安全性和简单的并发性来编写代码。但是,您仍然可以选择使变量可变。让我们探讨 Rust 如何以及为什么鼓励您支持不变性,以及为什么有时您可能想要选择退出。

When a variable is immutable, once a value is bound to a name, you can’t change that value. To illustrate this, generate a new project called variables in your projects directory by using cargo new variables.
当变量是不可变的时,一旦将值绑定到名称,就无法更改该值。为了说明这一点,请使用cargo new variables项目目录中生成一个名为Variables的新项目。

Then, in your new variables directory, open src/main.rs and replace its code with the following code, which won’t compile just yet:
然后,在新的变量目录中,打开src/main.rs并将其代码替换为以下代码,该代码目前还无法编译:

Filename: src/main.rs 文件名:src/main.rs

fn main() { let x = 5; println!("The value of x is: {x}"); x = 6; println!("The value of x is: {x}"); }

Save and run the program using cargo run. You should receive an error message regarding an immutability error, as shown in this output:
使用cargo run保存并运行程序。您应该收到一条有关不变性错误的错误消息,如以下输出所示:

$ cargo run Compiling variables v0.1.0 (file:///projects/variables) error[E0384]: cannot assign twice to immutable variable `x` --> src/main.rs:4:5 | 2 | let x = 5; | - | | | first assignment to `x` | help: consider making this binding mutable: `mut x` 3 | println!("The value of x is: {x}"); 4 | x = 6; | ^^^^^ cannot assign twice to immutable variable For more information about this error, try `rustc --explain E0384`. error: could not compile `variables` (bin "variables") due to 1 previous error

This example shows how the compiler helps you find errors in your programs. Compiler errors can be frustrating, but really they only mean your program isn’t safely doing what you want it to do yet; they do not mean that you’re not a good programmer! Experienced Rustaceans still get compiler errors.
此示例显示编译器如何帮助您查找程序中的错误。编译器错误可能会令人沮丧,但实际上它们仅意味着您的程序尚未安全地执行您希望它执行的操作;它们并不意味着您不是一个好的程序员!经验丰富的 Rustaceans 仍然会遇到编译器错误。

You received the error message cannot assign twice to immutable variable `x` because you tried to assign a second value to the immutable x variable.
您收到错误消息cannot assign twice to immutable variable `x`因为您尝试为不可变x变量分配第二个值。

It’s important that we get compile-time errors when we attempt to change a value that’s designated as immutable because this very situation can lead to bugs. If one part of our code operates on the assumption that a value will never change and another part of our code changes that value, it’s possible that the first part of the code won’t do what it was designed to do. The cause of this kind of bug can be difficult to track down after the fact, especially when the second piece of code changes the value only sometimes. The Rust compiler guarantees that when you state that a value won’t change, it really won’t change, so you don’t have to keep track of it yourself. Your code is thus easier to reason through.
当我们尝试更改指定为不可变的值时,我们会遇到编译时错误,这一点很重要,因为这种情况可能会导致错误。如果我们的代码的一部分在假设值永远不会改变的情况下运行,而代码的另一部分更改了该值,则代码的第一部分可能不会执行其设计目的。事后很难追查这种错误的原因,尤其是当第二段代码有时只更改值时。 Rust 编译器保证当你声明一个值不会改变时,它确实不会改变,所以你不必自己跟踪它。因此,您的代码更容易推理。

But mutability can be very useful, and can make code more convenient to write. Although variables are immutable by default, you can make them mutable by adding mut in front of the variable name as you did in Chapter 2. Adding mut also conveys intent to future readers of the code by indicating that other parts of the code will be changing this variable’s value.
但可变性可能非常有用,并且可以使代码更方便编写。尽管默认情况下变量是不可变的,但您可以通过在变量名前面添加mut来使它们可变,就像在第 2 章中所做的ignore 一样。添加mut还通过指示代码的其他部分将更改此变量的值来向代码的未来读者传达意图。

For example, let’s change src/main.rs to the following:
例如,我们将src/main.rs更改为以下内容:

Filename: src/main.rs 文件名:src/main.rs

fn main() { let mut x = 5; println!("The value of x is: {x}"); x = 6; println!("The value of x is: {x}"); }

When we run the program now, we get this:
当我们现在运行该程序时,我们得到:

$ cargo run Compiling variables v0.1.0 (file:///projects/variables) Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.30s Running `target/debug/variables` The value of x is: 5 The value of x is: 6

We’re allowed to change the value bound to x from 5 to 6 when mut is used. Ultimately, deciding whether to use mutability or not is up to you and depends on what you think is clearest in that particular situation.
当使用mut时,我们可以将绑定到x的值从5更改为6 。最终,决定是否使用可变性取决于您,并且取决于您认为在特定情况下最清楚的内容。

Constants 常数

Like immutable variables, constants are values that are bound to a name and are not allowed to change, but there are a few differences between constants and variables.
与不可变变量一样,常量是绑定到名称且不允许更改的值,但常量和变量之间存在一些差异。

First, you aren’t allowed to use mut with constants. Constants aren’t just immutable by default—they’re always immutable. You declare constants using the const keyword instead of the let keyword, and the type of the value must be annotated. We’ll cover types and type annotations in the next section, “Data Types”, so don’t worry about the details right now. Just know that you must always annotate the type.
首先,不允许将mut与常量一起使用。常量不仅在默认情况下是不可变的,而且始终是不可变的。使用const关键字而不是let关键字声明常量,并且必须注释值的类型。我们将在下一节“数据类型”中介绍类型和类型注释,所以现在不用担心细节。只需知道您必须始终注释类型即可。

Constants can be declared in any scope, including the global scope, which makes them useful for values that many parts of code need to know about.
常量可以在任何范围内声明,包括全局范围,这使得它们对于代码的许多部分需要了解的值非常有用。

The last difference is that constants may be set only to a constant expression, not the result of a value that could only be computed at runtime.
最后一个区别是常量只能设置为常量表达式,而不是只能在运行时计算的值的结果。

Here’s an example of a constant declaration:
这是常量声明的示例:

#![allow(unused)] fn main() { const THREE_HOURS_IN_SECONDS: u32 = 60 * 60 * 3; }

The constant’s name is THREE_HOURS_IN_SECONDS and its value is set to the result of multiplying 60 (the number of seconds in a minute) by 60 (the number of minutes in an hour) by 3 (the number of hours we want to count in this program). Rust’s naming convention for constants is to use all uppercase with underscores between words. The compiler is able to evaluate a limited set of operations at compile time, which lets us choose to write out this value in a way that’s easier to understand and verify, rather than setting this constant to the value 10,800. See the Rust Reference’s section on constant evaluation for more information on what operations can be used when declaring constants.
该常量的名称为THREE_HOURS_IN_SECONDS ,其值设置为 60(一分钟的秒数)乘以 60(一小时的分钟数)乘以 3(我们要在此程序中计算的小时数) )。 Rust 的常量命名约定是全部大写,单词之间使用下划线。编译器能够在编译时评估一组有限的操作,这让我们可以选择以更容易理解和验证的方式写出该值,而不是将此常量设置为值 10,800。有关声明常量时可以使用哪些操作的更多信息,请参阅Rust 参考中有关常量求值的部分

Constants are valid for the entire time a program runs, within the scope in which they were declared. This property makes constants useful for values in your application domain that multiple parts of the program might need to know about, such as the maximum number of points any player of a game is allowed to earn, or the speed of light.
常量在程序运行的整个时间内(在声明它们的范围内)有效。此属性使常量对于程序的多个部分可能需要了解的应用程序域中的值非常有用,例如允许游戏玩家获得的最大分数或光速。

Naming hardcoded values used throughout your program as constants is useful in conveying the meaning of that value to future maintainers of the code. It also helps to have only one place in your code you would need to change if the hardcoded value needed to be updated in the future.
将整个程序中使用的硬编码值命名为常量有助于将该值的含义传达给未来的代码维护者。如果将来需要更新硬编码值,那么在代码中只需要更改一个位置也很有帮助。

Shadowing 影子

As you saw in the guessing game tutorial in Chapter 2, you can declare a new variable with the same name as a previous variable. Rustaceans say that the first variable is shadowed by the second, which means that the second variable is what the compiler will see when you use the name of the variable. In effect, the second variable overshadows the first, taking any uses of the variable name to itself until either it itself is shadowed or the scope ends. We can shadow a variable by using the same variable’s name and repeating the use of the let keyword as follows:
正如您在第 2 章中的猜谜游戏教程中看到的ignore,您可以声明一个与先前变量同名的新变量。 Rustaceans 说第一个变量被第二个变量遮蔽,这意味着当您使用变量名称时编译器将看到第二个变量。实际上,第二个变量掩盖了第一个变量,将变量名称的任何使用都归为自身,直到它本身被掩盖或作用域结束。我们可以通过使用相同的变量名称并重复使用let关键字来隐藏变量,如下所示:

Filename: src/main.rs 文件名:src/main.rs

fn main() { let x = 5; let x = x + 1; { let x = x * 2; println!("The value of x in the inner scope is: {x}"); } println!("The value of x is: {x}"); }

This program first binds x to a value of 5. Then it creates a new variable x by repeating let x =, taking the original value and adding 1 so the value of x is then 6. Then, within an inner scope created with the curly brackets, the third let statement also shadows x and creates a new variable, multiplying the previous value by 2 to give x a value of 12. When that scope is over, the inner shadowing ends and x returns to being 6. When we run this program, it will output the following:
该程序首先将x绑定到值5 。然后,它通过重复let x =来创建一个新变量x ,取原始值并加1 ,因此x的值为6 。然后,在使用大括号创建的内部作用域内,第三个let语句也隐藏x并创建一个新变量,将先前的值乘以2以使x的值为12 。当该范围结束时,内部阴影结束并且x返回到6 。当我们运行这个程序时,它会输出以下内容:

$ cargo run Compiling variables v0.1.0 (file:///projects/variables) Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.31s Running `target/debug/variables` The value of x in the inner scope is: 12 The value of x is: 6

Shadowing is different from marking a variable as mut because we’ll get a compile-time error if we accidentally try to reassign to this variable without using the let keyword. By using let, we can perform a few transformations on a value but have the variable be immutable after those transformations have been completed.
隐藏与将变量标记为mut不同,因为如果我们不小心尝试在不使用let关键字的情况下重新分配给该变量,我们将收到编译时错误。通过使用let ,我们可以对一个值执行一些转换,但在这些转换完成后变量是不可变的。

The other difference between mut and shadowing is that because we’re effectively creating a new variable when we use the let keyword again, we can change the type of the value but reuse the same name. For example, say our program asks a user to show how many spaces they want between some text by inputting space characters, and then we want to store that input as a number:
mut和 Shadowing 之间的另一个区别是,因为当我们再次使用let关键字时,我们实际上是在创建一个新变量,所以我们可以更改值的类型,但重复使用相同的名称。例如,假设我们的程序要求用户通过输入空格字符来显示他们想要在某些文本之间有多少个空格,然后我们希望将该输入存储为数字:

fn main() { let spaces = " "; let spaces = spaces.len(); }

The first spaces variable is a string type and the second spaces variable is a number type. Shadowing thus spares us from having to come up with different names, such as spaces_str and spaces_num; instead, we can reuse the simpler spaces name. However, if we try to use mut for this, as shown here, we’ll get a compile-time error:
第一个spaces变量是字符串类型,第二个spaces变量是数字类型。因此,阴影使我们不必想出不同的名称,例如spaces_strspaces_num ;相反,我们可以重复使用更简单的spaces名称。但是,如果我们尝试使用mut来实现此目的,如下所示,我们将收到编译时错误:

fn main() { let mut spaces = " "; spaces = spaces.len(); }

The error says we’re not allowed to mutate a variable’s type:
该错误表明我们不允许改变变量的类型:

$ cargo run Compiling variables v0.1.0 (file:///projects/variables) error[E0308]: mismatched types --> src/main.rs:3:14 | 2 | let mut spaces = " "; | ----- expected due to this value 3 | spaces = spaces.len(); | ^^^^^^^^^^^^ expected `&str`, found `usize` For more information about this error, try `rustc --explain E0308`. error: could not compile `variables` (bin "variables") due to 1 previous error

Now that we’ve explored how variables work, let’s look at more data types they can have.
现在我们已经探讨了变量的工作原理,让我们看看它们可以拥有的更多数据类型。

Data Types 数据类型

Every value in Rust is of a certain data type, which tells Rust what kind of data is being specified so it knows how to work with that data. We’ll look at two data type subsets: scalar and compound.
Rust 中的每个值都有特定的数据类型,它告诉 Rust 正在指定哪种数据,以便它知道如何处理该数据。我们将研究两种数据类型子集:标量和复合。

Keep in mind that Rust is a statically typed language, which means that it must know the types of all variables at compile time. The compiler can usually infer what type we want to use based on the value and how we use it. In cases when many types are possible, such as when we converted a String to a numeric type using parse in the “Comparing the Guess to the Secret Number” section in Chapter 2, we must add a type annotation, like this:
请记住,Rust 是一种静态类型语言,这意味着它必须在编译时知道所有变量的类型。编译器通常可以根据值以及我们如何使用它来推断我们想要使用什么类型。如果可能有多种类型,例如当我们在第 2 章的“比较猜测与秘密数字”忽略部分中使用parseString转换为数字类型时,我们必须添加类型注释,如下所示:

#![allow(unused)] fn main() { let guess: u32 = "42".parse().expect("Not a number!"); }

If we don’t add the : u32 type annotation shown in the preceding code, Rust will display the following error, which means the compiler needs more information from us to know which type we want to use:
如果我们不添加前面代码中所示的: u32类型注释,Rust 将显示以下错误,这意味着编译器需要我们提供更多信息才能知道我们要使用哪种类型:

$ cargo build Compiling no_type_annotations v0.1.0 (file:///projects/no_type_annotations) error[E0284]: type annotations needed --> src/main.rs:2:9 | 2 | let guess = "42".parse().expect("Not a number!"); | ^^^^^ ----- type must be known at this point | = note: cannot satisfy `<_ as FromStr>::Err == _` help: consider giving `guess` an explicit type | 2 | let guess: /* Type */ = "42".parse().expect("Not a number!"); | ++++++++++++ For more information about this error, try `rustc --explain E0284`. error: could not compile `no_type_annotations` (bin "no_type_annotations") due to 1 previous error

You’ll see different type annotations for other data types.
您将看到其他数据类型的不同类型注释。

Scalar Types 标量类型

A scalar type represents a single value. Rust has four primary scalar types: integers, floating-point numbers, Booleans, and characters. You may recognize these from other programming languages. Let’s jump into how they work in Rust.
标量类型表示单个值。 Rust 有四种主要标量类型:整数、浮点数、布尔值和字符。您可能会从其他编程语言中识别出这些。让我们来看看它们在 Rust 中是如何工作的。

Integer Types 整数类型

An integer is a number without a fractional component. We used one integer type in Chapter 2, the u32 type. This type declaration indicates that the value it’s associated with should be an unsigned integer (signed integer types start with i instead of u) that takes up 32 bits of space. Table 3-1 shows the built-in integer types in Rust. We can use any of these variants to declare the type of an integer value.
整数是没有小数部分的数字。我们在第 2 章中使用了一种整数类型,即u32类型。此类型声明指示与其关联的值应该是占用 32 位空间的无符号整数(有符号整数类型以i而不是u开头)。表 3-1 显示了 Rust 中的内置整数类型。我们可以使用任何这些变体来声明整数值的类型。

Table 3-1: Integer Types in Rust
表 3-1:Rust 中的整数类型

Length 长度Signed 签名Unsigned 未签名
8-bit 8位i8u8
16-bit 16位i16u16
32-bit 32位i32u32
64-bit 64位i64u64
128-bit 128位i128u128
arch isizeusize

Each variant can be either signed or unsigned and has an explicit size. Signed and unsigned refer to whether it’s possible for the number to be negative—in other words, whether the number needs to have a sign with it (signed) or whether it will only ever be positive and can therefore be represented without a sign (unsigned). It’s like writing numbers on paper: when the sign matters, a number is shown with a plus sign or a minus sign; however, when it’s safe to assume the number is positive, it’s shown with no sign. Signed numbers are stored using two’s complement representation.
每个变体可以是有符号的或无符号的,并且具有明确的大小。有符号无符号是指数字是否可能为负数,换句话说,数字是否需要带有符号(有符号),或者它是否只能为正数,因此可以在没有符号的情况下表示(无符号) )。这就像在纸上写数字:当符号重要时,数字会显示加号或减号;当符号重要时,数字会显示为加号或减号;但是,当可以安全地假设该数字为正数时,它会不显示任何符号。有符号数使用二进制补码忽略表示法来存储。

Each signed variant can store numbers from -(2n - 1) to 2n - 1 - 1 inclusive, where n is the number of bits that variant uses. So an i8 can store numbers from -(27) to 27 - 1, which equals -128 to 127. Unsigned variants can store numbers from 0 to 2n - 1, so a u8 can store numbers from 0 to 28 - 1, which equals 0 to 255.
每个有符号变体可以存储从 -(2 n - 1 ) 到 2 n - 1 - 1 (含)的数字,其中n是变体使用的位数。因此i8可以存储从 -(2 7 ) 到 2 7 - 1 的数字,等于 -128 到 127。无符号变体可以存储从 0 到 2 n - 1 的数字,因此u8可以存储从 0 到 2 8 - 的数字1,等于 0 到 255。

Additionally, the isize and usize types depend on the architecture of the computer your program is running on, which is denoted in the table as “arch”: 64 bits if you’re on a 64-bit architecture and 32 bits if you’re on a 32-bit architecture.
此外, isizeusize类型取决于程序运行所在计算机的体系结构,在表中表示为“arch”:如果使用 64 位体系结构,则为 64 位;如果使用 64 位体系结构,则为 32 位在 32 位架构上。

You can write integer literals in any of the forms shown in Table 3-2. Note that number literals that can be multiple numeric types allow a type suffix, such as 57u8, to designate the type. Number literals can also use _ as a visual separator to make the number easier to read, such as 1_000, which will have the same value as if you had specified 1000.
您可以采用表 3-2 中所示的任何形式编写整数文字。请注意,可以是多个数字类型的数字文字允许使用类型后缀(例如57u8 )来指定类型。数字文字还可以使用_作为视觉分隔符,以使数字更易于阅读,例如1_000 ,它的值与您指定的1000相同。

Table 3-2: Integer Literals in Rust
表 3-2:Rust 中的整数文字

Number literals 数字字面量Example 例子
Decimal 十进制98_222
Hex 十六进制0xff
Octal 八进制0o77
Binary 二进制0b1111_0000
Byte (u8 only)
字节(仅限u8
b'A'

So how do you know which type of integer to use? If you’re unsure, Rust’s defaults are generally good places to start: integer types default to i32. The primary situation in which you’d use isize or usize is when indexing some sort of collection.
那么你如何知道要使用哪种类型的整数呢?如果您不确定,Rust 的默认值通常是一个不错的起点:整数类型默认为i32 。使用isizeusize的主要情况是对某种集合建立索引时。

Integer Overflow 整数溢出

Let’s say you have a variable of type u8 that can hold values between 0 and 255. If you try to change the variable to a value outside that range, such as 256, integer overflow will occur, which can result in one of two behaviors. When you’re compiling in debug mode, Rust includes checks for integer overflow that cause your program to panic at runtime if this behavior occurs. Rust uses the term panicking when a program exits with an error; we’ll discuss panics in more depth in the “Unrecoverable Errors with panic! section in Chapter 9.
假设您有一个u8类型的变量,它可以保存 0 到 255 之间的值。如果您尝试将该变量更改为该范围之外的值(例如 256),则会发生整数溢出,这可能会导致以下两种行为之一。当您在调试模式下进行编译时,Rust 会检查整数溢出,如果发生这种行为,则会导致程序在运行时出现恐慌。当程序因错误退出时,Rust 使用术语“恐慌” 。我们将在“不可恢复的恐慌错误panic!中更深入地讨论恐慌。 忽略第 9 章中的部分。

When you’re compiling in release mode with the --release flag, Rust does not include checks for integer overflow that cause panics. Instead, if overflow occurs, Rust performs two’s complement wrapping. In short, values greater than the maximum value the type can hold “wrap around” to the minimum of the values the type can hold. In the case of a u8, the value 256 becomes 0, the value 257 becomes 1, and so on. The program won’t panic, but the variable will have a value that probably isn’t what you were expecting it to have. Relying on integer overflow’s wrapping behavior is considered an error.
当您使用--release标志在发布模式下进行编译时,Rust包括对导致恐慌的整数溢出的检查。相反,如果发生溢出,Rust 会执行补码换行。简而言之,大于该类型可以容纳的最大值的值“环绕”到该类型可以容纳的最小值。在u8的情况下,值256变为0,值257变为1,依此类推。程序不会出现恐慌,但变量的值可能不是您期望的值。依赖整数溢出的包装行为被视为错误。

To explicitly handle the possibility of overflow, you can use these families of methods provided by the standard library for primitive numeric types:
要显式处理溢出的可能性,您可以使用标准库为原始数字类型提供的以下一系列方法:

  • Wrap in all modes with the wrapping_* methods, such as wrapping_add.
    使用wrapping_*方法包装所有模式,例如wrapping_add
  • Return the None value if there is overflow with the checked_* methods.
    如果checked_*方法存在溢出,则返回None值。
  • Return the value and a boolean indicating whether there was overflow with the overflowing_* methods.
    返回值和一个布尔值,指示overflowing_*方法是否存在溢出。
  • Saturate at the value’s minimum or maximum values with the saturating_* methods.
    使用saturating_*方法在值的最小值或最大值处饱和。

Floating-Point Types 浮点类型

Rust also has two primitive types for floating-point numbers, which are numbers with decimal points. Rust’s floating-point types are f32 and f64, which are 32 bits and 64 bits in size, respectively. The default type is f64 because on modern CPUs, it’s roughly the same speed as f32 but is capable of more precision. All floating-point types are signed.
Rust 还有两种浮点数的原始类型,即带小数点的数字。 Rust 的浮点类型是f32f64 ,它们的大小分别是 32 位和 64 位。默认类型是f64因为在现代 CPU 上,它的速度与f32大致相同,但精度更高。所有浮点类型都有符号。

Here’s an example that shows floating-point numbers in action:
下面是一个显示浮点数实际操作的示例:

Filename: src/main.rs 文件名:src/main.rs

fn main() { let x = 2.0; // f64 let y: f32 = 3.0; // f32 }

Floating-point numbers are represented according to the IEEE-754 standard. The f32 type is a single-precision float, and f64 has double precision.
浮点数根据 IEEE-754 标准表示。 f32类型是单精度浮点数, f64是双精度。

Numeric Operations 数值运算

Rust supports the basic mathematical operations you’d expect for all the number types: addition, subtraction, multiplication, division, and remainder. Integer division truncates toward zero to the nearest integer. The following code shows how you’d use each numeric operation in a let statement:
Rust 支持您期望的所有数字类型的基本数学运算:加法、减法、乘法、除法和余数。整数除法将零截断为最接近的整数。以下代码显示了如何在let语句中使用每个数字运算:

Filename: src/main.rs 文件名:src/main.rs

fn main() { // addition let sum = 5 + 10; // subtraction let difference = 95.5 - 4.3; // multiplication let product = 4 * 30; // division let quotient = 56.7 / 32.2; let truncated = -5 / 3; // Results in -1 // remainder let remainder = 43 % 5; }

Each expression in these statements uses a mathematical operator and evaluates to a single value, which is then bound to a variable. Appendix B contains a list of all operators that Rust provides.
这些语句中的每个表达式都使用数学运算符并计算为单个值,然后将其绑定到变量。附录 B忽略包含 Rust 提供的所有运算符的列表。

The Boolean Type 布尔类型

As in most other programming languages, a Boolean type in Rust has two possible values: true and false. Booleans are one byte in size. The Boolean type in Rust is specified using bool. For example:
与大多数其他编程语言一样,Rust 中的布尔类型有两个可能的值: truefalse 。布尔值的大小为一字节。 Rust 中的布尔类型是使用bool指定的。例如:

Filename: src/main.rs 文件名:src/main.rs

fn main() { let t = true; let f: bool = false; // with explicit type annotation }

The main way to use Boolean values is through conditionals, such as an if expression. We’ll cover how if expressions work in Rust in the “Control Flow” section.
使用布尔值的主要方法是通过条件,例如if表达式。我们将在“控制流”忽略部分介绍if表达式在 Rust 中如何工作。

The Character Type 角色类型

Rust’s char type is the language’s most primitive alphabetic type. Here are some examples of declaring char values:
Rust 的char类型是该语言最原始的字母类型。以下是声明char值的一些示例:

Filename: src/main.rs 文件名:src/main.rs

fn main() { let c = 'z'; let z: char = 'ℤ'; // with explicit type annotation let heart_eyed_cat = '😻'; }

Note that we specify char literals with single quotes, as opposed to string literals, which use double quotes. Rust’s char type is four bytes in size and represents a Unicode Scalar Value, which means it can represent a lot more than just ASCII. Accented letters; Chinese, Japanese, and Korean characters; emoji; and zero-width spaces are all valid char values in Rust. Unicode Scalar Values range from U+0000 to U+D7FF and U+E000 to U+10FFFF inclusive. However, a “character” isn’t really a concept in Unicode, so your human intuition for what a “character” is may not match up with what a char is in Rust. We’ll discuss this topic in detail in “Storing UTF-8 Encoded Text with Strings” in Chapter 8.
请注意,我们使用单引号指定char文字,而不是使用双引号的字符串文字。 Rust 的char类型大小为 4 个字节,表示 Unicode 标量值,这意味着它可以表示的不仅仅是 ASCII。重音字母;中文、日文、韩文字符;表情符号;和零宽度空格都是 Rust 中的有效char值。 Unicode 标量值范围从U+0000U+D7FF以及U+E000U+10FFFF (含)。然而,“字符”并不是 Unicode 中真正的概念,因此您对“字符”的人类直觉可能与 Rust 中的char不相符。我们将在第 8 章的“用字符串存储 UTF-8 编码文本”中详细讨论这个主题。

Compound Types 复合类型

Compound types can group multiple values into one type. Rust has two primitive compound types: tuples and arrays.
复合类型可以将多个值分组为一种类型。 Rust 有两种原始复合类型:元组和数组。

The Tuple Type 元组类型

A tuple is a general way of grouping together a number of values with a variety of types into one compound type. Tuples have a fixed length: once declared, they cannot grow or shrink in size.
元组是将具有多种类型的多个值组合在一起形成一个复合类型的通用方法。元组有固定的长度:一旦声明,它们的大小就不能增长或缩小。

We create a tuple by writing a comma-separated list of values inside parentheses. Each position in the tuple has a type, and the types of the different values in the tuple don’t have to be the same. We’ve added optional type annotations in this example:
我们通过在括号内写入逗号分隔的值列表来创建一个元组。元组中的每个位置都有一个类型,并且元组中不同值的类型不必相同。我们在此示例中添加了可选的类型注释:

Filename: src/main.rs 文件名:src/main.rs

fn main() { let tup: (i32, f64, u8) = (500, 6.4, 1); }

The variable tup binds to the entire tuple because a tuple is considered a single compound element. To get the individual values out of a tuple, we can use pattern matching to destructure a tuple value, like this:
变量tup绑定到整个元组,因为元组被视为单个复合元素。为了从元组中获取单个值,我们可以使用模式匹配来解构元组值,如下所示:

Filename: src/main.rs 文件名:src/main.rs

fn main() { let tup = (500, 6.4, 1); let (x, y, z) = tup; println!("The value of y is: {y}"); }

This program first creates a tuple and binds it to the variable tup. It then uses a pattern with let to take tup and turn it into three separate variables, x, y, and z. This is called destructuring because it breaks the single tuple into three parts. Finally, the program prints the value of y, which is 6.4.
该程序首先创建一个元组并将其绑定到变量tup 。然后,它使用带有let模式来获取tup并将其转换为三个单独的变量xyz 。这称为解构,因为它将单个元组分解为三个部分。最后,程序打印y的值,即6.4

We can also access a tuple element directly by using a period (.) followed by the index of the value we want to access. For example:
我们还可以通过使用句点 ( . ) 后跟要访问的值的索引来直接访问元组元素。例如:

Filename: src/main.rs 文件名:src/main.rs

fn main() { let x: (i32, f64, u8) = (500, 6.4, 1); let five_hundred = x.0; let six_point_four = x.1; let one = x.2; }

This program creates the tuple x and then accesses each element of the tuple using their respective indices. As with most programming languages, the first index in a tuple is 0.
该程序创建元组x ,然后使用各自的索引访问元组的每个元素。与大多数编程语言一样,元组中的第一个索引是 0。

The tuple without any values has a special name, unit. This value and its corresponding type are both written () and represent an empty value or an empty return type. Expressions implicitly return the unit value if they don’t return any other value.
没有任何值的元组有一个特殊的名称, unit 。这个值和它对应的类型都写成() ,表示一个空值或者一个空返回类型。如果表达式不返回任何其他值,则它们隐式返回单位值。

The Array Type 数组类型

Another way to have a collection of multiple values is with an array. Unlike a tuple, every element of an array must have the same type. Unlike arrays in some other languages, arrays in Rust have a fixed length.
拥有多个值的集合的另一种方法是使用数组。与元组不同,数组的每个元素必须具有相同的类型。与其他一些语言中的数组不同,Rust 中的数组具有固定长度。

We write the values in an array as a comma-separated list inside square brackets:
我们将数组中的值写为方括号内的逗号分隔列表:

Filename: src/main.rs 文件名:src/main.rs

fn main() { let a = [1, 2, 3, 4, 5]; }

Arrays are useful when you want your data allocated on the stack rather than the heap (we will discuss the stack and the heap more in Chapter 4) or when you want to ensure you always have a fixed number of elements. An array isn’t as flexible as the vector type, though. A vector is a similar collection type provided by the standard library that is allowed to grow or shrink in size. If you’re unsure whether to use an array or a vector, chances are you should use a vector. Chapter 8 discusses vectors in more detail.
当您希望将数据分配在堆栈而不是堆上(我们将在第 4 章忽略中更多地讨论堆栈和堆)或者当您希望确保始终拥有固定数量的元素时,数组非常有用。不过,数组不如向量类型灵活。矢量是标准库提供的类似集合类型,允许大小增长或缩小。如果您不确定是使用数组还是向量,那么您很可能应该使用向量。第 8 章忽略更详细地讨论向量。

However, arrays are more useful when you know the number of elements will not need to change. For example, if you were using the names of the month in a program, you would probably use an array rather than a vector because you know it will always contain 12 elements:
但是,当您知道元素数量不需要更改时,数组会更有用。例如,如果您在程序中使用月份名称,您可能会使用数组而不是向量,因为您知道它始终包含 12 个元素:

#![allow(unused)] fn main() { let months = ["January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December"]; }

You write an array’s type using square brackets with the type of each element, a semicolon, and then the number of elements in the array, like so:
您可以使用方括号、每个元素的类型、分号以及数组中元素的数量来编写数组的类型,如下所示:

#![allow(unused)] fn main() { let a: [i32; 5] = [1, 2, 3, 4, 5]; }

Here, i32 is the type of each element. After the semicolon, the number 5 indicates the array contains five elements.
这里, i32是每个元素的类型。分号后面的数字5表示数组包含五个元素。

You can also initialize an array to contain the same value for each element by specifying the initial value, followed by a semicolon, and then the length of the array in square brackets, as shown here:
您还可以通过指定初始值、后跟分号、然后在方括号中指定数组的长度来初始化数组,使其每个元素包含相同的值,如下所示:

#![allow(unused)] fn main() { let a = [3; 5]; }

The array named a will contain 5 elements that will all be set to the value 3 initially. This is the same as writing let a = [3, 3, 3, 3, 3]; but in a more concise way.
名为a数组将包含5元素,最初全部设置为值3 。这与编写let a = [3, 3, 3, 3, 3];但以更简洁的方式。

Accessing Array Elements 访问数组元素

An array is a single chunk of memory of a known, fixed size that can be allocated on the stack. You can access elements of an array using indexing, like this:
数组是可以在堆栈上分配的已知固定大小的单个内存块。您可以使用索引访问数组的元素,如下所示:

Filename: src/main.rs 文件名:src/main.rs

fn main() { let a = [1, 2, 3, 4, 5]; let first = a[0]; let second = a[1]; }

In this example, the variable named first will get the value 1 because that is the value at index [0] in the array. The variable named second will get the value 2 from index [1] in the array.
在此示例中,名为first的变量将获得值1因为这是数组中索引[0]处的值。名为second变量将从数组中的索引[1]获取值2

Invalid Array Element Access
无效的数组元素访问

Let’s see what happens if you try to access an element of an array that is past the end of the array. Say you run this code, similar to the guessing game in Chapter 2, to get an array index from the user:
让我们看看如果尝试访问超出数组末尾的数组元素会发生什么。假设你运行这段代码,类似于第 2 章中的猜谜游戏,从用户那里获取数组索引:

Filename: src/main.rs 文件名:src/main.rs

use std::io; fn main() { let a = [1, 2, 3, 4, 5]; println!("Please enter an array index."); let mut index = String::new(); io::stdin() .read_line(&mut index) .expect("Failed to read line"); let index: usize = index .trim() .parse() .expect("Index entered was not a number"); let element = a[index]; println!("The value of the element at index {index} is: {element}"); }

This code compiles successfully. If you run this code using cargo run and enter 0, 1, 2, 3, or 4, the program will print out the corresponding value at that index in the array. If you instead enter a number past the end of the array, such as 10, you’ll see output like this:
这段代码编译成功。如果您使用cargo run运行此代码并输入01234 ,程序将打印出数组中该索引处的相应值。如果您输入超出数组末尾的数字,例如10 ,您将看到如下输出:

thread 'main' panicked at src/main.rs:19:19: index out of bounds: the len is 5 but the index is 10 note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace

The program resulted in a runtime error at the point of using an invalid value in the indexing operation. The program exited with an error message and didn’t execute the final println! statement. When you attempt to access an element using indexing, Rust will check that the index you’ve specified is less than the array length. If the index is greater than or equal to the length, Rust will panic. This check has to happen at runtime, especially in this case, because the compiler can’t possibly know what value a user will enter when they run the code later.
该程序在索引操作中使用无效值时导致运行时错误。程序退出并显示错误消息,并且没有执行最终的println!陈述。当您尝试使用索引访问元素时,Rust 将检查您指定的索引是否小于数组长度。如果索引大于或等于长度,Rust 就会出现恐慌。此检查必须在运行时进行,尤其是在这种情况下,因为编译器不可能知道用户稍后运行代码时将输入什么值。

This is an example of Rust’s memory safety principles in action. In many low-level languages, this kind of check is not done, and when you provide an incorrect index, invalid memory can be accessed. Rust protects you against this kind of error by immediately exiting instead of allowing the memory access and continuing. Chapter 9 discusses more of Rust’s error handling and how you can write readable, safe code that neither panics nor allows invalid memory access.
这是 Rust 内存安全原则实际应用的一个例子。在许多低级语言中,没有进行这种检查,并且当您提供不正确的索引时,可以访问无效的内存。 Rust 通过立即退出而不是允许内存访问并继续来保护您免受此类错误的影响。第 9 章详细讨论了 Rust 的错误处理以及如何编写既不会发生恐慌也不会允许无效内存访问的可读、安全的代码。

Functions 功能

Functions are prevalent in Rust code. You’ve already seen one of the most important functions in the language: the main function, which is the entry point of many programs. You’ve also seen the fn keyword, which allows you to declare new functions.
函数在 Rust 代码中很常见。您已经看到了该语言中最重要的函数之一: main函数,它是许多程序的入口点。您还看到了fn关键字,它允许您声明新函数。

Rust code uses snake case as the conventional style for function and variable names, in which all letters are lowercase and underscores separate words. Here’s a program that contains an example function definition:
Rust 代码使用蛇形命名法作为函数和变量名称的常规样式,其中所有字母均为小写,并为单独的单词添加下划线。这是一个包含示例函数定义的程序:

Filename: src/main.rs 文件名:src/main.rs

fn main() { println!("Hello, world!"); another_function(); } fn another_function() { println!("Another function."); }

We define a function in Rust by entering fn followed by a function name and a set of parentheses. The curly brackets tell the compiler where the function body begins and ends.
我们通过输入fn后跟函数名和一组括号来定义 Rust 中的函数。大括号告诉编译器函数体的开始和结束位置。

We can call any function we’ve defined by entering its name followed by a set of parentheses. Because another_function is defined in the program, it can be called from inside the main function. Note that we defined another_function after the main function in the source code; we could have defined it before as well. Rust doesn’t care where you define your functions, only that they’re defined somewhere in a scope that can be seen by the caller.
我们可以通过输入其名称后跟一组括号来调用我们定义的任何函数。因为another_function是在程序中定义的,所以可以从main函数内部调用它。注意,我们在源代码中的main函数之后定义了another_function ;我们之前也可以定义它。 Rust 并不关心你在哪里定义函数,只关心它们是在调用者可以看到的范围内的某个地方定义的。

Let’s start a new binary project named functions to explore functions further. Place the another_function example in src/main.rs and run it. You should see the following output:
让我们启动一个名为Functions 的新二进制项目来进一步探索函数。将another_function示例放入src/main.rs并运行它。您应该看到以下输出:

$ cargo run Compiling functions v0.1.0 (file:///projects/functions) Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.28s Running `target/debug/functions` Hello, world! Another function.

The lines execute in the order in which they appear in the main function. First the “Hello, world!” message prints, and then another_function is called and its message is printed.
这些行按照它们在main函数中出现的顺序执行。首先是“你好,世界!”打印消息,然后调用another_function并打印其消息。

Parameters 参数

We can define functions to have parameters, which are special variables that are part of a function’s signature. When a function has parameters, you can provide it with concrete values for those parameters. Technically, the concrete values are called arguments, but in casual conversation, people tend to use the words parameter and argument interchangeably for either the variables in a function’s definition or the concrete values passed in when you call a function.
我们可以定义具有参数的函数,这些参数是作为函数签名一部分的特殊变量。当函数有参数时,您可以为其提供这些参数的具体值。从技术上讲,具体值称为参数,但在日常对话中,人们倾向于交替使用参数参数这两个词来表示函数定义中的变量或调用函数时传入的具体值。

In this version of another_function we add a parameter:
在这个版本的another_function中,我们添加了一个参数:

Filename: src/main.rs 文件名:src/main.rs

fn main() { another_function(5); } fn another_function(x: i32) { println!("The value of x is: {x}"); }

Try running this program; you should get the following output:
尝试运行这个程序;你应该得到以下输出:

$ cargo run Compiling functions v0.1.0 (file:///projects/functions) Finished `dev` profile [unoptimized + debuginfo] target(s) in 1.21s Running `target/debug/functions` The value of x is: 5

The declaration of another_function has one parameter named x. The type of x is specified as i32. When we pass 5 in to another_function, the println! macro puts 5 where the pair of curly brackets containing x was in the format string.
another_function的声明有一个名为x参数。 x的类型指定为i32 。当我们将5传递给another_function时, println!宏将5放入格式字符串中包含x的大括号对中。

In function signatures, you must declare the type of each parameter. This is a deliberate decision in Rust’s design: requiring type annotations in function definitions means the compiler almost never needs you to use them elsewhere in the code to figure out what type you mean. The compiler is also able to give more helpful error messages if it knows what types the function expects.
在函数签名中,必须声明每个参数的类型。这是 Rust 设计中经过深思熟虑的决定:在函数定义中要求类型注释意味着编译器几乎不需要您在代码中的其他地方使用它们来弄清楚您所指的类型。如果编译器知道函数需要什么类型,它还能够给出更有用的错误消息。

When defining multiple parameters, separate the parameter declarations with commas, like this:
定义多个参数时,用逗号分隔参数声明,如下所示:

Filename: src/main.rs 文件名:src/main.rs

fn main() { print_labeled_measurement(5, 'h'); } fn print_labeled_measurement(value: i32, unit_label: char) { println!("The measurement is: {value}{unit_label}"); }

This example creates a function named print_labeled_measurement with two parameters. The first parameter is named value and is an i32. The second is named unit_label and is type char. The function then prints text containing both the value and the unit_label.
此示例创建一个名为print_labeled_measurement的函数,该函数具有两个参数。第一个参数名为value ,是一个i32 。第二个名为unit_label ,类型为char 。然后该函数打印包含valueunit_label文本。

Let’s try running this code. Replace the program currently in your functions project’s src/main.rs file with the preceding example and run it using cargo run:
让我们尝试运行这段代码。将函数项目的src/main.rs文件中当前的程序替换为前面的示例,并使用cargo run运行它:

$ cargo run Compiling functions v0.1.0 (file:///projects/functions) Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.31s Running `target/debug/functions` The measurement is: 5h

Because we called the function with 5 as the value for value and 'h' as the value for unit_label, the program output contains those values.
因为我们使用5作为value的值、 'h'作为unit_label的值来调用该函数,所以程序输出包含这些值。

Statements and Expressions
陈述和表达式

Function bodies are made up of a series of statements optionally ending in an expression. So far, the functions we’ve covered haven’t included an ending expression, but you have seen an expression as part of a statement. Because Rust is an expression-based language, this is an important distinction to understand. Other languages don’t have the same distinctions, so let’s look at what statements and expressions are and how their differences affect the bodies of functions.
函数体由一系列可选地以表达式结尾的语句组成。到目前为止,我们介绍的函数尚未包含结束表达式,但您已经看到表达式是语句的一部分。因为 Rust 是一种基于表达式的语言,所以这是一个需要理解的重要区别。其他语言没有相同的区别,所以让我们看看什么是语句和表达式以及它们的差异如何影响函数体。

  • Statements are instructions that perform some action and do not return a value.
    语句是执行某些操作但不返回值的指令。
  • Expressions evaluate to a resultant value. Let’s look at some examples.
    表达式计算结果值。让我们看一些例子。

We’ve actually already used statements and expressions. Creating a variable and assigning a value to it with the let keyword is a statement. In Listing 3-1, let y = 6; is a statement.
我们实际上已经使用过语句和表达式。创建变量并使用let关键字为其赋值是一条语句。在清单 3-1 中, let y = 6;是一个声明。

Filename: src/main.rs 文件名:src/main.rs

fn main() { let y = 6; }

Listing 3-1: A main function declaration containing one statement
清单 3-1:包含一条语句的main函数声明

Function definitions are also statements; the entire preceding example is a statement in itself.
函数定义也是语句;前面的整个例子本身就是一个陈述。

Statements do not return values. Therefore, you can’t assign a let statement to another variable, as the following code tries to do; you’ll get an error:
语句不返回值。因此,您不能将let语句分配给另一个变量,如以下代码尝试执行的操作;你会得到一个错误:

Filename: src/main.rs 文件名:src/main.rs

fn main() { let x = (let y = 6); }

When you run this program, the error you’ll get looks like this:
当您运行该程序时,您将得到如下所示的错误:

$ cargo run Compiling functions v0.1.0 (file:///projects/functions) error: expected expression, found `let` statement --> src/main.rs:2:14 | 2 | let x = (let y = 6); | ^^^ | = note: only supported directly in conditions of `if` and `while` expressions warning: unnecessary parentheses around assigned value --> src/main.rs:2:13 | 2 | let x = (let y = 6); | ^ ^ | = note: `#[warn(unused_parens)]` on by default help: remove these parentheses | 2 - let x = (let y = 6); 2 + let x = let y = 6; | warning: `functions` (bin "functions") generated 1 warning error: could not compile `functions` (bin "functions") due to 1 previous error; 1 warning emitted

The let y = 6 statement does not return a value, so there isn’t anything for x to bind to. This is different from what happens in other languages, such as C and Ruby, where the assignment returns the value of the assignment. In those languages, you can write x = y = 6 and have both x and y have the value 6; that is not the case in Rust.
let y = 6语句不返回值,因此x没有任何可绑定的内容。这与其他语言(例如 C 和 Ruby)中发生的情况不同,在其他语言中,赋值返回赋值的值。在这些语言中,您可以编写x = y = 6并使xy值为6 ; Rust 中的情况并非如此。

Expressions evaluate to a value and make up most of the rest of the code that you’ll write in Rust. Consider a math operation, such as 5 + 6, which is an expression that evaluates to the value 11. Expressions can be part of statements: in Listing 3-1, the 6 in the statement let y = 6; is an expression that evaluates to the value 6. Calling a function is an expression. Calling a macro is an expression. A new scope block created with curly brackets is an expression, for example:
表达式求值并构成您将在 Rust 中编写的其余代码的大部分。考虑一个数学运算,例如5 + 6 ,它是一个计算结果为11表达式。表达式可以是语句的一部分:在清单 3-1 中,语句中的6 let y = 6;是一个计算结果为值6表达式。调用函数就是一个表达式。调用宏是一个表达式。使用大括号创建的新作用域块是一个表达式,例如:

Filename: src/main.rs 文件名:src/main.rs

fn main() { let y = { let x = 3; x + 1 }; println!("The value of y is: {y}"); }

This expression: 这个表达式:

{ let x = 3; x + 1 }

is a block that, in this case, evaluates to 4. That value gets bound to y as part of the let statement. Note that the x + 1 line doesn’t have a semicolon at the end, which is unlike most of the lines you’ve seen so far. Expressions do not include ending semicolons. If you add a semicolon to the end of an expression, you turn it into a statement, and it will then not return a value. Keep this in mind as you explore function return values and expressions next.
是一个块,在本例中,其计算结果为4 。该值作为let语句的一部分绑定到y 。请注意, x + 1行末尾没有分号,这与您目前看到的大多数行不同。表达式不包含结束分号。如果在表达式末尾添加分号,则将其转换为语句,并且它不会返回值。当您接下来探索函数返回值和表达式时,请记住这一点。

Functions with Return Values
有返回值的函数

Functions can return values to the code that calls them. We don’t name return values, but we must declare their type after an arrow (->). In Rust, the return value of the function is synonymous with the value of the final expression in the block of the body of a function. You can return early from a function by using the return keyword and specifying a value, but most functions return the last expression implicitly. Here’s an example of a function that returns a value:
函数可以将值返回给调用它们的代码。我们不命名返回值,但必须在箭头( -> )后面声明它们的类型。在 Rust 中,函数的返回值与函数体块中最终表达式的值同义。您可以通过使用return关键字并指定一个值来提前从函数返回,但大多数函数都会隐式返回最后一个表达式。下面是一个返回值的函数示例:

Filename: src/main.rs 文件名:src/main.rs

fn five() -> i32 { 5 } fn main() { let x = five(); println!("The value of x is: {x}"); }

There are no function calls, macros, or even let statements in the five function—just the number 5 by itself. That’s a perfectly valid function in Rust. Note that the function’s return type is specified too, as -> i32. Try running this code; the output should look like this:
five函数中没有函数调用、宏,甚至let语句,只有数字5本身。这是 Rust 中一个完全有效的函数。请注意,该函数的返回类型也被指定为-> i32 。尝试运行这段代码;输出应如下所示:

$ cargo run Compiling functions v0.1.0 (file:///projects/functions) Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.30s Running `target/debug/functions` The value of x is: 5

The 5 in five is the function’s return value, which is why the return type is i32. Let’s examine this in more detail. There are two important bits: first, the line let x = five(); shows that we’re using the return value of a function to initialize a variable. Because the function five returns a 5, that line is the same as the following:
five5是函数的返回值,这就是返回类型为i32原因。让我们更详细地研究一下这一点。有两个重要的位:首先,行let x = five();表明我们正在使用函数的返回值来初始化变量。因为函数five返回5 ,所以该行与以下内容相同:

#![allow(unused)] fn main() { let x = 5; }

Second, the five function has no parameters and defines the type of the return value, but the body of the function is a lonely 5 with no semicolon because it’s an expression whose value we want to return.
其次, five函数没有参数并定义了返回值的类型,但函数体是一个没有分号的孤独的5因为它是一个我们想要返回其值的表达式。

Let’s look at another example:
让我们看另一个例子:

Filename: src/main.rs 文件名:src/main.rs

fn main() { let x = plus_one(5); println!("The value of x is: {x}"); } fn plus_one(x: i32) -> i32 { x + 1 }

Running this code will print The value of x is: 6. But if we place a semicolon at the end of the line containing x + 1, changing it from an expression to a statement, we’ll get an error:
运行此代码将打印The value of x is: 6 。但是如果我们在包含x + 1行末尾放置一个分号,将其从表达式更改为语句,我们将收到错误:

Filename: src/main.rs 文件名:src/main.rs

fn main() { let x = plus_one(5); println!("The value of x is: {x}"); } fn plus_one(x: i32) -> i32 { x + 1; }

Compiling this code produces an error, as follows:
编译此代码会产生错误,如下所示:

$ cargo run Compiling functions v0.1.0 (file:///projects/functions) error[E0308]: mismatched types --> src/main.rs:7:24 | 7 | fn plus_one(x: i32) -> i32 { | -------- ^^^ expected `i32`, found `()` | | | implicitly returns `()` as its body has no tail or `return` expression 8 | x + 1; | - help: remove this semicolon to return this value For more information about this error, try `rustc --explain E0308`. error: could not compile `functions` (bin "functions") due to 1 previous error

The main error message, mismatched types, reveals the core issue with this code. The definition of the function plus_one says that it will return an i32, but statements don’t evaluate to a value, which is expressed by (), the unit type. Therefore, nothing is returned, which contradicts the function definition and results in an error. In this output, Rust provides a message to possibly help rectify this issue: it suggests removing the semicolon, which would fix the error.
主要的错误消息, mismatched types ,揭示了这段代码的核心问题。函数plus_one的定义表示它将返回i32 ,但语句不会求值,该值由单位类型()表示。因此,不会返回任何内容,这与函数定义相矛盾并导致错误。在此输出中,Rust 提供了一条消息,可能有助于纠正此问题:它建议删除分号,这将修复错误。

Comments 评论

All programmers strive to make their code easy to understand, but sometimes extra explanation is warranted. In these cases, programmers leave comments in their source code that the compiler will ignore but people reading the source code may find useful.
所有程序员都努力使他们的代码易于理解,但有时需要额外的解释。在这些情况下,程序员在源代码中留下注释,编译器将忽略这些注释,但阅读源代码的人可能会发现有用。

Here’s a simple comment: 这是一个简单的评论:

#![allow(unused)] fn main() { // hello, world }

In Rust, the idiomatic comment style starts a comment with two slashes, and the comment continues until the end of the line. For comments that extend beyond a single line, you’ll need to include // on each line, like this:
在 Rust 中,惯用的注释风格以两个斜杠开始注释,并且注释一直持续到行尾。对于超出单行的注释,您需要在每一行中包含// ,如下所示:

#![allow(unused)] fn main() { // So we’re doing something complicated here, long enough that we need // multiple lines of comments to do it! Whew! Hopefully, this comment will // explain what’s going on. }

Comments can also be placed at the end of lines containing code:
注释也可以放置在包含代码的行的末尾:

Filename: src/main.rs 文件名:src/main.rs

fn main() { let lucky_number = 7; // I’m feeling lucky today }

But you’ll more often see them used in this format, with the comment on a separate line above the code it’s annotating:
但您会更经常看到它们以这种格式使用,注释位于其注释的代码上方的单独行上:

Filename: src/main.rs 文件名:src/main.rs

fn main() { // I’m feeling lucky today let lucky_number = 7; }

Rust also has another kind of comment, documentation comments, which we’ll discuss in the “Publishing a Crate to Crates.io” section of Chapter 14.
Rust 还有另一种注释,即文档注释,我们将在第 14 章的“将 Crate 发布到 Crates.io”忽略部分中讨论。

Control Flow 控制流程

The ability to run some code depending on whether a condition is true and to run some code repeatedly while a condition is true are basic building blocks in most programming languages. The most common constructs that let you control the flow of execution of Rust code are if expressions and loops.
根据条件是否为true来运行某些代码以及在条件为true时重复运行某些代码的能力是大多数编程语言中的基本构建块。让您控制 Rust 代码执行流程的最常见结构是if表达式和循环。

if Expressions
if表达式

An if expression allows you to branch your code depending on conditions. You provide a condition and then state, “If this condition is met, run this block of code. If the condition is not met, do not run this block of code.”
if表达式允许您根据条件分支代码。您提供一个条件,然后声明:“如果满足此条件,则运行此代码块。如果不满足条件,则不要运行该代码块。”

Create a new project called branches in your projects directory to explore the if expression. In the src/main.rs file, input the following:
项目目录中创建一个名为Branches的新项目来探索if表达式。在src/main.rs文件中,输入以下内容:

Filename: src/main.rs 文件名:src/main.rs

fn main() { let number = 3; if number < 5 { println!("condition was true"); } else { println!("condition was false"); } }

All if expressions start with the keyword if, followed by a condition. In this case, the condition checks whether or not the variable number has a value less than 5. We place the block of code to execute if the condition is true immediately after the condition inside curly brackets. Blocks of code associated with the conditions in if expressions are sometimes called arms, just like the arms in match expressions that we discussed in the “Comparing the Guess to the Secret Number” section of Chapter 2.
所有if表达式都以关键字if开头,后跟条件。在本例中,条件检查变量number的值是否小于 5。如果条件为true ,我们将要执行的代码块放在大括号内的条件之后。 if表达式中的条件相关的代码块有时称为Arms ,就像我们在第 2 章的“比较猜测与秘密数字”忽略部分中讨论的match表达式中的 Arms 一样。

Optionally, we can also include an else expression, which we chose to do here, to give the program an alternative block of code to execute should the condition evaluate to false. If you don’t provide an else expression and the condition is false, the program will just skip the if block and move on to the next bit of code.
或者,我们还可以包含一个else表达式,我们在这里选择这样做,以便在条件评估为false时为程序提供一个要执行的替代代码块。如果您不提供else表达式并且条件为false ,则程序将跳过if块并继续执行下一段代码。

Try running this code; you should see the following output:
尝试运行这段代码;您应该看到以下输出:

$ cargo run Compiling branches v0.1.0 (file:///projects/branches) Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.31s Running `target/debug/branches` condition was true

Let’s try changing the value of number to a value that makes the condition false to see what happens:
让我们尝试将number的值更改为使条件为false值,看看会发生什么:

fn main() { let number = 7; if number < 5 { println!("condition was true"); } else { println!("condition was false"); } }

Run the program again, and look at the output:
再次运行程序,查看输出:

$ cargo run Compiling branches v0.1.0 (file:///projects/branches) Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.31s Running `target/debug/branches` condition was false

It’s also worth noting that the condition in this code must be a bool. If the condition isn’t a bool, we’ll get an error. For example, try running the following code:
还值得注意的是,此代码中的条件必须bool 。如果条件不是bool ,我们会收到错误。例如,尝试运行以下代码:

Filename: src/main.rs 文件名:src/main.rs

fn main() { let number = 3; if number { println!("number was three"); } }

The if condition evaluates to a value of 3 this time, and Rust throws an error:
这次if条件的计算结果为3 ,Rust 抛出错误:

$ cargo run Compiling branches v0.1.0 (file:///projects/branches) error[E0308]: mismatched types --> src/main.rs:4:8 | 4 | if number { | ^^^^^^ expected `bool`, found integer For more information about this error, try `rustc --explain E0308`. error: could not compile `branches` (bin "branches") due to 1 previous error

The error indicates that Rust expected a bool but got an integer. Unlike languages such as Ruby and JavaScript, Rust will not automatically try to convert non-Boolean types to a Boolean. You must be explicit and always provide if with a Boolean as its condition. If we want the if code block to run only when a number is not equal to 0, for example, we can change the if expression to the following:
该错误表明 Rust 期望是bool ,但得到的是整数。与 Ruby 和 JavaScript 等语言不同,Rust 不会自动尝试将非布尔类型转换为布尔类型。您必须明确并始终提供以布尔值作为条件的if 。例如,如果我们希望if代码块仅在数字不等于0时运行,我们可以将if表达式更改为以下内容:

Filename: src/main.rs 文件名:src/main.rs

fn main() { let number = 3; if number != 0 { println!("number was something other than zero"); } }

Running this code will print number was something other than zero.
运行此代码将打印number was something other than zero

Handling Multiple Conditions with else if
使用else if处理多个条件

You can use multiple conditions by combining if and else in an else if expression. For example:
您可以通过在else if表达式中组合ifelse来使用多个条件。例如:

Filename: src/main.rs 文件名:src/main.rs

fn main() { let number = 6; if number % 4 == 0 { println!("number is divisible by 4"); } else if number % 3 == 0 { println!("number is divisible by 3"); } else if number % 2 == 0 { println!("number is divisible by 2"); } else { println!("number is not divisible by 4, 3, or 2"); } }

This program has four possible paths it can take. After running it, you should see the following output:
该程序有四种可能的路径。运行后,您应该看到以下输出:

$ cargo run Compiling branches v0.1.0 (file:///projects/branches) Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.31s Running `target/debug/branches` number is divisible by 3

When this program executes, it checks each if expression in turn and executes the first body for which the condition evaluates to true. Note that even though 6 is divisible by 2, we don’t see the output number is divisible by 2, nor do we see the number is not divisible by 4, 3, or 2 text from the else block. That’s because Rust only executes the block for the first true condition, and once it finds one, it doesn’t even check the rest.
当该程序执行时,它依次检查每个if表达式并执行条件评估为true第一个主体。请注意,即使 6 可以被 2 整除,我们也看不到输出number is divisible by 2 ,也没有看到该数字不能被else块中的number is not divisible by 4, 3, or 2 。这是因为 Rust 只执行第一个true条件的块,一旦找到一个,它甚至不会检查其余的。

Using too many else if expressions can clutter your code, so if you have more than one, you might want to refactor your code. Chapter 6 describes a powerful Rust branching construct called match for these cases.
使用太多else if表达式会使您的代码变得混乱,因此如果您有多个 else if 表达式,您可能需要重构您的代码。第 6 章描述了一个强大的 Rust 分支结构,称为match ,适用于这些情况。

Using if in a let Statement
let语句中使用if

Because if is an expression, we can use it on the right side of a let statement to assign the outcome to a variable, as in Listing 3-2.
因为if是一个表达式,所以我们可以在let语句的右侧使用它来将结果分配给变量,如清单 3-2 所示。

Filename: src/main.rs 文件名:src/main.rs

fn main() { let condition = true; let number = if condition { 5 } else { 6 }; println!("The value of number is: {number}"); }

Listing 3-2: Assigning the result of an if expression to a variable
清单 3-2:将if表达式的结果分配给变量

The number variable will be bound to a value based on the outcome of the if expression. Run this code to see what happens:
number变量将根据if表达式的结果绑定到一个值。运行此代码看看会发生什么:

$ cargo run Compiling branches v0.1.0 (file:///projects/branches) Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.30s Running `target/debug/branches` The value of number is: 5

Remember that blocks of code evaluate to the last expression in them, and numbers by themselves are also expressions. In this case, the value of the whole if expression depends on which block of code executes. This means the values that have the potential to be results from each arm of the if must be the same type; in Listing 3-2, the results of both the if arm and the else arm were i32 integers. If the types are mismatched, as in the following example, we’ll get an error:
请记住,代码块的计算结果是其中的最后一个表达式,数字本身也是表达式。在这种情况下,整个if表达式的值取决于执行哪个代码块。这意味着if的每个分支可能产生的值必须是相同的类型;在清单 3-2 中, if臂和else臂的结果都是i32整数。如果类型不匹配,如下例所示,我们将收到错误:

Filename: src/main.rs 文件名:src/main.rs

fn main() { let condition = true; let number = if condition { 5 } else { "six" }; println!("The value of number is: {number}"); }

When we try to compile this code, we’ll get an error. The if and else arms have value types that are incompatible, and Rust indicates exactly where to find the problem in the program:
当我们尝试编译此代码时,我们会收到错误。 ifelse分支具有不兼容的值类型,Rust 准确指出了在程序中查找问题的位置:

$ cargo run Compiling branches v0.1.0 (file:///projects/branches) error[E0308]: `if` and `else` have incompatible types --> src/main.rs:4:44 | 4 | let number = if condition { 5 } else { "six" }; | - ^^^^^ expected integer, found `&str` | | | expected because of this For more information about this error, try `rustc --explain E0308`. error: could not compile `branches` (bin "branches") due to 1 previous error

The expression in the if block evaluates to an integer, and the expression in the else block evaluates to a string. This won’t work because variables must have a single type, and Rust needs to know at compile time what type the number variable is, definitively. Knowing the type of number lets the compiler verify the type is valid everywhere we use number. Rust wouldn’t be able to do that if the type of number was only determined at runtime; the compiler would be more complex and would make fewer guarantees about the code if it had to keep track of multiple hypothetical types for any variable.
if块中的表达式计算结果为整数, else块中的表达式计算结果为字符串。这是行不通的,因为变量必须具有单一类型,并且 Rust 需要在编译时明确知道number变量是什么类型。了解number的类型可以让编译器验证该类型在我们使用number任何地方都有效。如果number类型仅在运行时确定,Rust 将无法做到这一点;如果编译器必须跟踪任何变量的多个假设类型,编译器将会更加复杂,并且对代码的保证也会更少。

Repetition with Loops 循环重复

It’s often useful to execute a block of code more than once. For this task, Rust provides several loops, which will run through the code inside the loop body to the end and then start immediately back at the beginning. To experiment with loops, let’s make a new project called loops.
多次执行一段代码通常很有用。对于这个任务,Rust 提供了几个循环,这些循环将运行循环体内的代码直到结束,然后立即从头开始。为了试验循环,让我们创建一个名为Loops的新项目。

Rust has three kinds of loops: loop, while, and for. Let’s try each one.
Rust 具有三种循环: loopwhilefor 。让我们逐一尝试一下。

Repeating Code with loop
loop重复代码

The loop keyword tells Rust to execute a block of code over and over again forever or until you explicitly tell it to stop.
loop关键字告诉 Rust 永远一遍又一遍地执行代码块,或者直到你明确告诉它停止为止。

As an example, change the src/main.rs file in your loops directory to look like this:
例如,将循环目录中的src/main.rs文件更改为如下所示:

Filename: src/main.rs 文件名:src/main.rs

fn main() { loop { println!("again!"); } }

When we run this program, we’ll see again! printed over and over continuously until we stop the program manually. Most terminals support the keyboard shortcut ctrl-c to interrupt a program that is stuck in a continual loop. Give it a try:
当我们运行这个程序时,我们会again!不断地打印,直到我们手动停止程序。大多数终端都支持键盘快捷键 ctrl - c 中断陷入连续循环的程序。尝试一下:

$ cargo run Compiling loops v0.1.0 (file:///projects/loops) Finished dev [unoptimized + debuginfo] target(s) in 0.29s Running `target/debug/loops` again! again! again! again! ^Cagain!

The symbol ^C represents where you pressed ctrl-c. You may or may not see the word again! printed after the ^C, depending on where the code was in the loop when it received the interrupt signal.
符号^C代表您按下的位置 ctrl - c 。您可能会也可能不会again!^C之后打印,具体取决于收到中断信号时代码在循环中的位置。

Fortunately, Rust also provides a way to break out of a loop using code. You can place the break keyword within the loop to tell the program when to stop executing the loop. Recall that we did this in the guessing game in the “Quitting After a Correct Guess” section of Chapter 2 to exit the program when the user won the game by guessing the correct number.
幸运的是,Rust 还提供了一种使用代码打破循环的方法。您可以在循环中放置break关键字来告诉程序何时停止执行循环。回想一下,我们在第 2 章“猜对后退出”忽略部分的猜谜游戏中这样做了,当用户通过猜对数字赢得游戏时退出程序。

We also used continue in the guessing game, which in a loop tells the program to skip over any remaining code in this iteration of the loop and go to the next iteration.
我们还在猜谜游戏中使用了continue ,它在循环中告诉程序跳过本次循环迭代中的任何剩余代码并进入下一次迭代。

Returning Values from Loops
从循环返回值

One of the uses of a loop is to retry an operation you know might fail, such as checking whether a thread has completed its job. You might also need to pass the result of that operation out of the loop to the rest of your code. To do this, you can add the value you want returned after the break expression you use to stop the loop; that value will be returned out of the loop so you can use it, as shown here:
loop的用途之一是重试您知道可能会失败的操作,例如检查线程是否已完成其作业。您可能还需要将该操作的结果从循环传递到代码的其余部分。为此,您可以在用于停止循环的break表达式之后添加您想要返回的值;该值将从循环中返回,以便您可以使用它,如下所示:

fn main() { let mut counter = 0; let result = loop { counter += 1; if counter == 10 { break counter * 2; } }; println!("The result is {result}"); }

Before the loop, we declare a variable named counter and initialize it to 0. Then we declare a variable named result to hold the value returned from the loop. On every iteration of the loop, we add 1 to the counter variable, and then check whether the counter is equal to 10. When it is, we use the break keyword with the value counter * 2. After the loop, we use a semicolon to end the statement that assigns the value to result. Finally, we print the value in result, which in this case is 20.
在循环之前,我们声明一个名为counter变量并将其初始化为0 。然后我们声明一个名为result的变量来保存循环返回的值。在循环的每次迭代中,我们将counter变量加1 ,然后检查counter是否等于10 。如果是这样,我们使用带有值counter * 2 break关键字。循环之后,我们使用分号来结束将值赋给result的语句。最后,我们打印result中的值,在本例中为20

You can also return from inside a loop. While break only exits the current loop, return always exits the current function.
您也可以从循环内部returnbreak仅退出当前循环,而return始终退出当前函数。

Loop Labels to Disambiguate Between Multiple Loops
用于消除多个循环之间歧义的循环标签

If you have loops within loops, break and continue apply to the innermost loop at that point. You can optionally specify a loop label on a loop that you can then use with break or continue to specify that those keywords apply to the labeled loop instead of the innermost loop. Loop labels must begin with a single quote. Here’s an example with two nested loops:
如果循环内有循环,则breakcontinue应用于该点的最内层循环。您可以选择在循环上指定循环标签,然后将其与break一起使用,或continue指定这些关键字应用于带标签的循环而不是最内层循环。循环标签必须以单引号开头。这是一个包含两个嵌套循环的示例:

fn main() { let mut count = 0; 'counting_up: loop { println!("count = {count}"); let mut remaining = 10; loop { println!("remaining = {remaining}"); if remaining == 9 { break; } if count == 2 { break 'counting_up; } remaining -= 1; } count += 1; } println!("End count = {count}"); }

The outer loop has the label 'counting_up, and it will count up from 0 to 2. The inner loop without a label counts down from 10 to 9. The first break that doesn’t specify a label will exit the inner loop only. The break 'counting_up; statement will exit the outer loop. This code prints:
外循环有标签'counting_up ,它将从 0 到 2 递增计数。没有标签的内循环从 10 到 9 递减计数。第一个不指定标签的break将仅退出内循环。 break 'counting_up;语句将退出外循环。此代码打印:

$ cargo run Compiling loops v0.1.0 (file:///projects/loops) Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.58s Running `target/debug/loops` count = 0 remaining = 10 remaining = 9 count = 1 remaining = 10 remaining = 9 count = 2 remaining = 10 End count = 2

Conditional Loops with while
while条件循环

A program will often need to evaluate a condition within a loop. While the condition is true, the loop runs. When the condition ceases to be true, the program calls break, stopping the loop. It’s possible to implement behavior like this using a combination of loop, if, else, and break; you could try that now in a program, if you’d like. However, this pattern is so common that Rust has a built-in language construct for it, called a while loop. In Listing 3-3, we use while to loop the program three times, counting down each time, and then, after the loop, print a message and exit.
程序通常需要评估循环内的条件。当条件为true时,循环运行。当条件不再为true时,程序调用break来停止循环。可以使用loopifelsebreak的组合来实现这样的行为;如果您愿意,您现在可以在程序中尝试一下。然而,这种模式非常常见,以至于 Rust 有一个内置的语言构造,称为while循环。在清单 3-3 中,我们使用while循环程序三次,每次倒计时,然后在循环结束后打印一条消息并退出。

Filename: src/main.rs 文件名:src/main.rs

fn main() { let mut number = 3; while number != 0 { println!("{number}!"); number -= 1; } println!("LIFTOFF!!!"); }

Listing 3-3: Using a while loop to run code while a condition holds true
示例 3-3:使用while循环在条件成立时运行代码

This construct eliminates a lot of nesting that would be necessary if you used loop, if, else, and break, and it’s clearer. While a condition evaluates to true, the code runs; otherwise, it exits the loop.
这种结构消除了使用loopifelsebreak时所必需的大量嵌套,而且更加清晰。当条件评估为true时,代码将运行;否则,它退出循环。

Looping Through a Collection with for
使用for循环遍历集合

You can also use the while construct to loop over the elements of a collection, such as an array. For example, the loop in Listing 3-4 prints each element in the array a.
您还可以使用while构造来循环集合(例如数组)的元素。例如,清单 3-4 中的循环打印数组a中的每个元素。

Filename: src/main.rs 文件名:src/main.rs

fn main() { let a = [10, 20, 30, 40, 50]; let mut index = 0; while index < 5 { println!("the value is: {}", a[index]); index += 1; } }

Listing 3-4: Looping through each element of a collection using a while loop
示例 3-4:使用while循环遍历集合的每个元素

Here, the code counts up through the elements in the array. It starts at index 0, and then loops until it reaches the final index in the array (that is, when index < 5 is no longer true). Running this code will print every element in the array:
此处,代码对数组中的元素进行向上计数。它从索引0开始,然后循环直到到达数组中的最终索引(即,当index < 5不再为true时)。运行此代码将打印数组中的每个元素:

$ cargo run Compiling loops v0.1.0 (file:///projects/loops) Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.32s Running `target/debug/loops` the value is: 10 the value is: 20 the value is: 30 the value is: 40 the value is: 50

All five array values appear in the terminal, as expected. Even though index will reach a value of 5 at some point, the loop stops executing before trying to fetch a sixth value from the array.
正如预期的那样,所有五个数组值都出现在终端中。即使index在某个时刻将达到值5 ,循环也会在尝试从数组中获取第六个值之前停止执行。

However, this approach is error prone; we could cause the program to panic if the index value or test condition is incorrect. For example, if you changed the definition of the a array to have four elements but forgot to update the condition to while index < 4, the code would panic. It’s also slow, because the compiler adds runtime code to perform the conditional check of whether the index is within the bounds of the array on every iteration through the loop.
然而,这种方法很容易出错;如果索引值或测试条件不正确,我们可能会导致程序出现恐慌。例如,如果您将a数组的定义更改为包含四个元素,但忘记将条件更新为while index < 4 ,则代码将会出现混乱。它也很慢,因为编译器添加了运行时代码来在循环的每次迭代中执行索引是否在数组范围内的条件检查。

As a more concise alternative, you can use a for loop and execute some code for each item in a collection. A for loop looks like the code in Listing 3-5.
作为更简洁的替代方案,您可以使用for循环并为集合中的每个项目执行一些代码。 for循环类似于清单 3-5 中的代码。

Filename: src/main.rs 文件名:src/main.rs

fn main() { let a = [10, 20, 30, 40, 50]; for element in a { println!("the value is: {element}"); } }

Listing 3-5: Looping through each element of a collection using a for loop
示例 3-5:使用for循环遍历集合的每个元素

When we run this code, we’ll see the same output as in Listing 3-4. More importantly, we’ve now increased the safety of the code and eliminated the chance of bugs that might result from going beyond the end of the array or not going far enough and missing some items.
当我们运行这段代码时,我们将看到与清单 3-4 相同的输出。更重要的是,我们现在提高了代码的安全性,并消除了由于超出数组末尾或不够远而丢失某些项目而可能导致错误的可能性。

Using the for loop, you wouldn’t need to remember to change any other code if you changed the number of values in the array, as you would with the method used in Listing 3-4.
使用for循环,如果您更改了数组中的值的数量,则无需记住更改任何其他代码,就像使用清单 3-4 中使用的方法一样。

The safety and conciseness of for loops make them the most commonly used loop construct in Rust. Even in situations in which you want to run some code a certain number of times, as in the countdown example that used a while loop in Listing 3-3, most Rustaceans would use a for loop. The way to do that would be to use a Range, provided by the standard library, which generates all numbers in sequence starting from one number and ending before another number.
for循环的安全性和简洁性使其成为 Rust 中最常用的循环结构。即使在您想要运行某些代码一定次数的情况下(如清单 3-3 中使用while循环的倒计时示例),大多数 Rustaceans 也会使用for循环。做到这一点的方法是使用标准库提供的Range ,它按顺序生成从一个数字开始到另一个数字之前结束的所有数字。

Here’s what the countdown would look like using a for loop and another method we’ve not yet talked about, rev, to reverse the range:
这是使用for循环和我们尚未讨论的另一种方法rev来反转范围的倒计时:

Filename: src/main.rs 文件名:src/main.rs

fn main() { for number in (1..4).rev() { println!("{number}!"); } println!("LIFTOFF!!!"); }

This code is a bit nicer, isn’t it?
这段代码好一点,不是吗?

Summary 概括

You made it! This was a sizable chapter: you learned about variables, scalar and compound data types, functions, comments, if expressions, and loops! To practice with the concepts discussed in this chapter, try building programs to do the following:
你成功了!这是一个相当大的章节:您了解了变量、标量和复合数据类型、函数、注释、 if表达式和循环!要练习本章讨论的概念,请尝试构建程序来执行以下操作:

  • Convert temperatures between Fahrenheit and Celsius.
    在华氏度和摄氏度之间转换温度。
  • Generate the nth Fibonacci number.
    生成第 n 个斐波那契数。
  • Print the lyrics to the Christmas carol “The Twelve Days of Christmas,” taking advantage of the repetition in the song.
    利用歌曲中的重复内容打印圣诞颂歌“圣诞节的十二天”的歌词。

When you’re ready to move on, we’ll talk about a concept in Rust that doesn’t commonly exist in other programming languages: ownership.
当您准备好继续前进时,我们将讨论 Rust 中的一个在其他编程语言中常见的概念:所有权。

Understanding Ownership 了解所有权

Ownership is Rust’s most unique feature and has deep implications for the rest of the language. It enables Rust to make memory safety guarantees without needing a garbage collector, so it’s important to understand how ownership works. In this chapter, we’ll talk about ownership as well as several related features: borrowing, slices, and how Rust lays data out in memory.
所有权是 Rust 最独特的功能,对语言的其他部分有着深远的影响。它使 Rust 能够在不需要垃圾收集器的情况下保证内存安全,因此了解所有权的工作原理非常重要。在本章中,我们将讨论所有权以及几个相关功能:借用、切片以及 Rust 如何在内存中布置数据。

What Is Ownership? 什么是所有权?

Ownership is a set of rules that govern how a Rust program manages memory. All programs have to manage the way they use a computer’s memory while running. Some languages have garbage collection that regularly looks for no-longer-used memory as the program runs; in other languages, the programmer must explicitly allocate and free the memory. Rust uses a third approach: memory is managed through a system of ownership with a set of rules that the compiler checks. If any of the rules are violated, the program won’t compile. None of the features of ownership will slow down your program while it’s running.
所有权是一组规则,用于控制 Rust 程序如何管理内存。所有程序都必须管理它们在运行时使用计算机内存的方式。有些语言具有垃圾收集功能,会在程序运行时定期查找不再使用的内存;在其他语言中,程序员必须显式分配和释放内存。 Rust 使用第三种方法:通过所有权系统和编译器检查的一组规则来管理内存。如果违反任何规则,程序将无法编译。所有权的任何功能都不会减慢程序运行的速度。

Because ownership is a new concept for many programmers, it does take some time to get used to. The good news is that the more experienced you become with Rust and the rules of the ownership system, the easier you’ll find it to naturally develop code that is safe and efficient. Keep at it!
因为所有权对于许多程序员来说是一个新概念,所以确实需要一些时间来适应。好消息是,您对 Rust 和所有权系统规则的经验越丰富,您就越容易自然地开发出安全高效的代码。坚持下去!

When you understand ownership, you’ll have a solid foundation for understanding the features that make Rust unique. In this chapter, you’ll learn ownership by working through some examples that focus on a very common data structure: strings.
当您了解所有权时,您将为了解 Rust 独特的功能奠定坚实的基础。在本章中,您将通过一些示例来学习所有权,这些示例重点关注非常常见的数据结构:字符串。

The Stack and the Heap
栈和堆

Many programming languages don’t require you to think about the stack and the heap very often. But in a systems programming language like Rust, whether a value is on the stack or the heap affects how the language behaves and why you have to make certain decisions. Parts of ownership will be described in relation to the stack and the heap later in this chapter, so here is a brief explanation in preparation.
许多编程语言并不要求您经常考虑堆栈和堆。但在像 Rust 这样的系统编程语言中,值是在堆栈上还是在堆上会影响语言的行为方式以及为什么必须做出某些决定。本章稍后将描述与堆栈和堆相关的所有权部分,因此这里是准备中的简要说明。

Both the stack and the heap are parts of memory available to your code to use at runtime, but they are structured in different ways. The stack stores values in the order it gets them and removes the values in the opposite order. This is referred to as last in, first out. Think of a stack of plates: when you add more plates, you put them on top of the pile, and when you need a plate, you take one off the top. Adding or removing plates from the middle or bottom wouldn’t work as well! Adding data is called pushing onto the stack, and removing data is called popping off the stack. All data stored on the stack must have a known, fixed size. Data with an unknown size at compile time or a size that might change must be stored on the heap instead.
堆栈和堆都是可供代码在运行时使用的内存部分,但它们的结构方式不同。堆栈按照获取值的顺序存储值,并按照相反的顺序删除值。这称为后进先出。想象一叠盘子:当你添加更多盘子时,你把它们放在一堆盘子的顶部,当你需要一个盘子时,你从上面拿一个。从中间或底部添加或删除板也不起作用!添加数据称为压入堆栈,删除数据称为从堆栈弹出。存储在堆栈上的所有数据都必须具有已知的固定大小。编译时大小未知或大小可能更改的数据必须存储在堆上。

The heap is less organized: when you put data on the heap, you request a certain amount of space. The memory allocator finds an empty spot in the heap that is big enough, marks it as being in use, and returns a pointer, which is the address of that location. This process is called allocating on the heap and is sometimes abbreviated as just allocating (pushing values onto the stack is not considered allocating). Because the pointer to the heap is a known, fixed size, you can store the pointer on the stack, but when you want the actual data, you must follow the pointer. Think of being seated at a restaurant. When you enter, you state the number of people in your group, and the host finds an empty table that fits everyone and leads you there. If someone in your group comes late, they can ask where you’ve been seated to find you.
堆的组织性较差:当您将数据放入堆上时,您会请求一定量的空间。内存分配器在堆中找到一个足够大的空位,将其标记为正在使用,并返回一个指针,它是该位置的地址。这个过程称为在堆上分配,有时缩写为分配(将值压入堆栈不被视为分配)。因为指向堆的指针是已知的、固定大小,所以您可以将指针存储在堆栈上,但是当您需要实际数据时,必须跟随指针。想象一下坐在一家餐馆里。当您进入时,请说出您的团体人数,然后主人会找到一张适合每个人的空桌子并带您前往那里。如果您的团队中有人迟到,他们可以询问您坐在哪里以便找到您。

Pushing to the stack is faster than allocating on the heap because the allocator never has to search for a place to store new data; that location is always at the top of the stack. Comparatively, allocating space on the heap requires more work because the allocator must first find a big enough space to hold the data and then perform bookkeeping to prepare for the next allocation.
压入堆栈比在堆上分配更快,因为分配器永远不需要搜索存储新数据的位置;该位置始终位于堆栈的顶部。相比之下,在堆上分配空间需要更多的工作,因为分配器必须首先找到足够大的空间来容纳数据,然后进行簿记,为下一次分配做准备。

Accessing data in the heap is slower than accessing data on the stack because you have to follow a pointer to get there. Contemporary processors are faster if they jump around less in memory. Continuing the analogy, consider a server at a restaurant taking orders from many tables. It’s most efficient to get all the orders at one table before moving on to the next table. Taking an order from table A, then an order from table B, then one from A again, and then one from B again would be a much slower process. By the same token, a processor can do its job better if it works on data that’s close to other data (as it is on the stack) rather than farther away (as it can be on the heap).
访问堆中的数据比访问堆栈中的数据慢,因为您必须遵循指针才能到达那里。如果现代处理器在内存中的跳跃次数更少,那么它们的速度就会更快。继续类比,考虑餐厅的服务员从许多桌子上点菜。在转到下一张桌子之前先在一张桌子上获得所有订单是最有效的。从 A 表中获取订单,然后从 B 表中获取订单,然后再次从 A 中获取订单,然后再次从 B 中获取订单,这将是一个慢得多的过程。出于同样的原因,如果处理器处理靠近其他数据(因为它在堆栈上)而不是较远的数据(因为它可以在堆上)的数据,那么它可以更好地完成工作。

When your code calls a function, the values passed into the function (including, potentially, pointers to data on the heap) and the function’s local variables get pushed onto the stack. When the function is over, those values get popped off the stack.
当您的代码调用函数时,传递给函数的值(可能包括指向堆上数据的指针)和函数的局部变量被推送到堆栈上。当函数结束时,这些值将从堆栈中弹出。

Keeping track of what parts of code are using what data on the heap, minimizing the amount of duplicate data on the heap, and cleaning up unused data on the heap so you don’t run out of space are all problems that ownership addresses. Once you understand ownership, you won’t need to think about the stack and the heap very often, but knowing that the main purpose of ownership is to manage heap data can help explain why it works the way it does.
跟踪代码的哪些部分正在使用堆上的哪些数据、最大限度地减少堆上的重复数据量以及清理堆上未使用的数据以免耗尽空间,这些都是所有权解决的问题。一旦理解了所有权,您就不需要经常考虑堆栈和堆,但是知道所有权的主要目的是管理堆数据可以帮助解释为什么它会这样工作。

Ownership Rules 所有权规则

First, let’s take a look at the ownership rules. Keep these rules in mind as we work through the examples that illustrate them:
首先,我们来看看所有权规则。当我们通过示例来说明这些规则时,请记住这些规则:

  • Each value in Rust has an owner.
    Rust 中的每个值都有一个所有者
  • There can only be one owner at a time.
    一次只能有一位所有者。
  • When the owner goes out of scope, the value will be dropped.
    当所有者超出范围时,该值将被删除。

Variable Scope 变量范围

Now that we’re past basic Rust syntax, we won’t include all the fn main() { code in examples, so if you’re following along, make sure to put the following examples inside a main function manually. As a result, our examples will be a bit more concise, letting us focus on the actual details rather than boilerplate code.
现在我们已经了解了基本的 Rust 语法,我们不会在示例中包含所有fn main() {代码,因此如果您正在遵循,请确保手动将以下示例放入main函数中。因此,我们的示例将更加简洁,让我们专注于实际细节而不是样板代码。

As a first example of ownership, we’ll look at the scope of some variables. A scope is the range within a program for which an item is valid. Take the following variable:
作为所有权的第一个示例,我们将了解一些变量的范围。范围是程序内某项有效的范围。取以下变量:

#![allow(unused)] fn main() { let s = "hello"; }

The variable s refers to a string literal, where the value of the string is hardcoded into the text of our program. The variable is valid from the point at which it’s declared until the end of the current scope. Listing 4-1 shows a program with comments annotating where the variable s would be valid.
变量s指的是字符串文字,其中字符串的值被硬编码到我们程序的文本中。该变量从声明之日起一直有效,直至当前作用域结束。清单 4-1 显示了一个带有注释的程序,注释了变量s的有效位置。

fn main() { { // s is not valid here, it’s not yet declared let s = "hello"; // s is valid from this point forward // do stuff with s } // this scope is now over, and s is no longer valid }

Listing 4-1: A variable and the scope in which it is valid
示例 4-1:变量及其有效范围

In other words, there are two important points in time here:
换句话说,这里有两个重要的时间点:

  • When s comes into scope, it is valid.
    s进入作用域时,它是有效的。
  • It remains valid until it goes out of scope.
    它一直有效,直到超出范围。

At this point, the relationship between scopes and when variables are valid is similar to that in other programming languages. Now we’ll build on top of this understanding by introducing the String type.
此时,作用域和变量何时有效之间的关系与其他编程语言中的类似。现在我们将通过介绍String类型来建立在这种理解的基础上。

The String Type
String类型

To illustrate the rules of ownership, we need a data type that is more complex than those we covered in the “Data Types” section of Chapter 3. The types covered previously are of a known size, can be stored on the stack and popped off the stack when their scope is over, and can be quickly and trivially copied to make a new, independent instance if another part of code needs to use the same value in a different scope. But we want to look at data that is stored on the heap and explore how Rust knows when to clean up that data, and the String type is a great example.
为了说明所有权规则,我们需要一个比第 3 章“数据类型”忽略部分中介绍的数据类型更复杂的数据类型。前面介绍的类型具有已知的大小,可以存储在堆栈上并弹出当它们的作用域结束时,它们会从堆栈中退出,并且如果代码的另一部分需要在不同的作用域中使用相同的值,则可以快速而简单地复制以创建新的独立实例。但我们想要查看存储在堆上的数据并探索 Rust 如何知道何时清理这些数据,而String类型就是一个很好的例子。

We’ll concentrate on the parts of String that relate to ownership. These aspects also apply to other complex data types, whether they are provided by the standard library or created by you. We’ll discuss String in more depth in Chapter 8.
我们将重点关注String中与所有权相关的部分。这些方面也适用于其他复杂数据类型,无论它们是由标准库提供还是由您创建。我们将在第 8 章中更深入地讨论String

We’ve already seen string literals, where a string value is hardcoded into our program. String literals are convenient, but they aren’t suitable for every situation in which we may want to use text. One reason is that they’re immutable. Another is that not every string value can be known when we write our code: for example, what if we want to take user input and store it? For these situations, Rust has a second string type, String. This type manages data allocated on the heap and as such is able to store an amount of text that is unknown to us at compile time. You can create a String from a string literal using the from function, like so:
我们已经见过字符串文字,其中字符串值被硬编码到我们的程序中。字符串文字很方便,但它们并不适合我们可能想要使用文本的所有情况。原因之一是它们是不可变的。另一个问题是,当我们编写代码时,并不是每个字符串值都是已知的:例如,如果我们想要获取用户输入并存储它怎么办?对于这些情况,Rust 有第二种字符串类型: String 。这种类型管理在堆上分配的数据,因此能够存储编译时我们未知的大量文本。您可以使用from函数从字符串文字创建String ,如下所示:

#![allow(unused)] fn main() { let s = String::from("hello"); }

The double colon :: operator allows us to namespace this particular from function under the String type rather than using some sort of name like string_from. We’ll discuss this syntax more in the “Method Syntax” section of Chapter 5, and when we talk about namespacing with modules in “Paths for Referring to an Item in the Module Tree” in Chapter 7.
双冒号::运算符允许我们在String类型下命名这个特定的from函数,而不是使用某种名称,如string_from 。我们将在第 5 章的“方法语法”忽略部分中更多地讨论此语法,并且当我们在第 7 章的“引用模块树中的项目的路径”中讨论模块的命名空间时,忽略。

This kind of string can be mutated:
这种字符串可以改变:

fn main() { let mut s = String::from("hello"); s.push_str(", world!"); // push_str() appends a literal to a String println!("{s}"); // This will print `hello, world!` }

So, what’s the difference here? Why can String be mutated but literals cannot? The difference is in how these two types deal with memory.
那么,这里有什么区别呢?为什么String可以改变而文字却不能?区别在于这两种类型如何处理内存。

Memory and Allocation 内存和分配

In the case of a string literal, we know the contents at compile time, so the text is hardcoded directly into the final executable. This is why string literals are fast and efficient. But these properties only come from the string literal’s immutability. Unfortunately, we can’t put a blob of memory into the binary for each piece of text whose size is unknown at compile time and whose size might change while running the program.
对于字符串文字,我们在编译时知道内容,因此文本被直接硬编码到最终的可执行文件中。这就是字符串文字快速且高效的原因。但这些属性仅来自字符串文字的不变性。不幸的是,我们无法为每个文本块放入二进制内存,这些文本块的大小在编译时未知,并且在运行程序时其大小可能会发生变化。

With the String type, in order to support a mutable, growable piece of text, we need to allocate an amount of memory on the heap, unknown at compile time, to hold the contents. This means:
对于String类型,为了支持可变、可增长的文本片段,我们需要在堆上分配一定量的内存(在编译时未知)来保存内容。这意味着:

  • The memory must be requested from the memory allocator at runtime.
    必须在运行时向内存分配器请求内存。
  • We need a way of returning this memory to the allocator when we’re done with our String.
    当我们使用完String后,我们需要一种将内存返回给分配器的方法。

That first part is done by us: when we call String::from, its implementation requests the memory it needs. This is pretty much universal in programming languages.
第一部分是由我们完成的:当我们调用String::from时,它的实现会请求它所需的内存。这在编程语言中几乎是通用的。

However, the second part is different. In languages with a garbage collector (GC), the GC keeps track of and cleans up memory that isn’t being used anymore, and we don’t need to think about it. In most languages without a GC, it’s our responsibility to identify when memory is no longer being used and to call code to explicitly free it, just as we did to request it. Doing this correctly has historically been a difficult programming problem. If we forget, we’ll waste memory. If we do it too early, we’ll have an invalid variable. If we do it twice, that’s a bug too. We need to pair exactly one allocate with exactly one free.
然而,第二部分则不同。在带有垃圾收集器(GC)的语言中,GC 会跟踪并清理不再使用的内存,我们不需要考虑它。在大多数没有 GC 的语言中,我们有责任确定内存何时不再被使用,并调用代码来显式释放它,就像我们请求它一样。历史上,正确执行此操作一直是一个困难的编程问题。如果我们忘记了,我们就会浪费记忆。如果我们做得太早,我们就会得到一个无效的变量。如果我们这样做两次,这也是一个错误。我们需要将一个allocate与一个free配对。

Rust takes a different path: the memory is automatically returned once the variable that owns it goes out of scope. Here’s a version of our scope example from Listing 4-1 using a String instead of a string literal:
Rust 采用了不同的路径:一旦拥有内存的变量超出范围,内存就会自动返回。下面是清单 4-1 中的作用域示例的一个版本,使用了String而不是字符串文字:

fn main() { { let s = String::from("hello"); // s is valid from this point forward // do stuff with s } // this scope is now over, and s is no // longer valid }

There is a natural point at which we can return the memory our String needs to the allocator: when s goes out of scope. When a variable goes out of scope, Rust calls a special function for us. This function is called drop, and it’s where the author of String can put the code to return the memory. Rust calls drop automatically at the closing curly bracket.
有一个自然的点,我们可以将String所需的内存返回给分配器:当s超出范围时。当变量超出范围时,Rust 会为我们调用一个特殊的函数。这个函数称为dropString的作者可以在其中放置返回内存的代码。 Rust 调用会自动drop右大括号处。

Note: In C++, this pattern of deallocating resources at the end of an item’s lifetime is sometimes called Resource Acquisition Is Initialization (RAII). The drop function in Rust will be familiar to you if you’ve used RAII patterns.
注意:在 C++ 中,这种在项目生命周期结束时释放资源的模式有时称为资源获取即初始化 (RAII) 。如果您使用过 RAII 模式,那么您会熟悉 Rust 中的drop函数。

This pattern has a profound impact on the way Rust code is written. It may seem simple right now, but the behavior of code can be unexpected in more complicated situations when we want to have multiple variables use the data we’ve allocated on the heap. Let’s explore some of those situations now.
这种模式对 Rust 代码的编写方式产生了深远的影响。现在看起来可能很简单,但在更复杂的情况下,当我们想让多个变量使用我们在堆上分配的数据时,代码的行为可能会出乎意料。现在让我们探讨其中的一些情况。

Variables and Data Interacting with Move
与 Move 交互的变量和数据

Multiple variables can interact with the same data in different ways in Rust. Let’s look at an example using an integer in Listing 4-2.
在 Rust 中,多个变量可以以不同的方式与相同的数据交互。让我们看一下清单 4-2 中使用整数的示例。

fn main() { let x = 5; let y = x; }

Listing 4-2: Assigning the integer value of variable x to y
示例 4-2:将变量x的整数值赋给y

We can probably guess what this is doing: “bind the value 5 to x; then make a copy of the value in x and bind it to y.” We now have two variables, x and y, and both equal 5. This is indeed what is happening, because integers are simple values with a known, fixed size, and these two 5 values are pushed onto the stack.
我们大概可以猜到这是在做什么:“将值5绑定到x ;然后复制x中的值并将其绑定到y 。”我们现在有两个变量xy ,并且都等于5 。这确实是正在发生的事情,因为整数是具有已知固定大小的简单值,并且这两个5值被压入堆栈。

Now let’s look at the String version:
现在让我们看看String版本:

fn main() { let s1 = String::from("hello"); let s2 = s1; }

This looks very similar, so we might assume that the way it works would be the same: that is, the second line would make a copy of the value in s1 and bind it to s2. But this isn’t quite what happens.
这看起来非常相似,因此我们可以假设它的工作方式是相同的:也就是说,第二行将复制s1中的值并将其绑定到s2 。但实际情况并非如此。

Take a look at Figure 4-1 to see what is happening to String under the covers. A String is made up of three parts, shown on the left: a pointer to the memory that holds the contents of the string, a length, and a capacity. This group of data is stored on the stack. On the right is the memory on the heap that holds the contents.
看一下图 4-1,看看String到底发生了什么。 String由三部分组成,如左图所示:指向保存字符串内容的内存的指针、长度和容量。这组数据存放在栈中。右侧是堆上保存内容的内存。

Two tables: the first table contains the representation of s1 on the
stack, consisting of its length (5), capacity (5), and a pointer to the first
value in the second table. The second table contains the representation of the
string data on the heap, byte by byte.

Figure 4-1: Representation in memory of a String holding the value "hello" bound to s1
图 4-1:保存绑定到s1"hello"String在内存中的表示

The length is how much memory, in bytes, the contents of the String are currently using. The capacity is the total amount of memory, in bytes, that the String has received from the allocator. The difference between length and capacity matters, but not in this context, so for now, it’s fine to ignore the capacity.
长度是String的内容当前使用的内存量(以字节为单位)。容量是String从分配器接收的内存总量(以字节为单位)。长度和容量之间的差异很重要,但在这种情况下并不重要,所以现在可以忽略容量。

When we assign s1 to s2, the String data is copied, meaning we copy the pointer, the length, and the capacity that are on the stack. We do not copy the data on the heap that the pointer refers to. In other words, the data representation in memory looks like Figure 4-2.
当我们将s1分配给s2时, String数据被复制,这意味着我们复制堆栈上的指针、长度和容量。我们不会复制指针引用的堆上的数据。换句话说,内存中的数据表示如图 4-2 所示。

Three tables: tables s1 and s2 representing those strings on the
stack, respectively, and both pointing to the same string data on the heap.

Figure 4-2: Representation in memory of the variable s2 that has a copy of the pointer, length, and capacity of s1
图 4-2:变量s2在内存中的表示,它具有s1的指针、长度和容量的副本

The representation does not look like Figure 4-3, which is what memory would look like if Rust instead copied the heap data as well. If Rust did this, the operation s2 = s1 could be very expensive in terms of runtime performance if the data on the heap were large.
该表示形式与图 4-3不同,如果 Rust 也复制堆数据,内存就会是什么样子。如果 Rust 这样做,如果堆上的数据很大,则操作s2 = s1在运行时性能方面可能会非常昂贵。

Four tables: two tables representing the stack data for s1 and s2,
and each points to its own copy of string data on the heap.

Figure 4-3: Another possibility for what s2 = s1 might do if Rust copied the heap data as well
图 4-3:如果 Rust 也复制了堆数据,则s2 = s1可能会执行的另一种可能性

Earlier, we said that when a variable goes out of scope, Rust automatically calls the drop function and cleans up the heap memory for that variable. But Figure 4-2 shows both data pointers pointing to the same location. This is a problem: when s2 and s1 go out of scope, they will both try to free the same memory. This is known as a double free error and is one of the memory safety bugs we mentioned previously. Freeing memory twice can lead to memory corruption, which can potentially lead to security vulnerabilities.
之前,我们说过,当变量超出范围时,Rust 会自动调用drop函数并清理该变量的堆内存。但图 4-2 显示两个数据指针都指向同一位置。这是一个问题:当s2s1超出范围时,它们都会尝试释放相同的内存。这称为双重释放错误,是我们之前提到的内存安全错误之一。两次释放内存可能会导致内存损坏,从而可能导致安全漏洞。

To ensure memory safety, after the line let s2 = s1;, Rust considers s1 as no longer valid. Therefore, Rust doesn’t need to free anything when s1 goes out of scope. Check out what happens when you try to use s1 after s2 is created; it won’t work:
为了保证内存安全,在该行之后let s2 = s1; ,Rust 认为s1不再有效。因此,当s1超出范围时,Rust 不需要释放任何东西。查看创建s2后尝试使用s1时会发生什么情况;它不会工作:

fn main() { let s1 = String::from("hello"); let s2 = s1; println!("{s1}, world!"); }

You’ll get an error like this because Rust prevents you from using the invalidated reference:
你会得到这样的错误,因为 Rust 阻止你使用无效的引用:

$ cargo run Compiling ownership v0.1.0 (file:///projects/ownership) error[E0382]: borrow of moved value: `s1` --> src/main.rs:5:15 | 2 | let s1 = String::from("hello"); | -- move occurs because `s1` has type `String`, which does not implement the `Copy` trait 3 | let s2 = s1; | -- value moved here 4 | 5 | println!("{s1}, world!"); | ^^^^ value borrowed here after move | = note: this error originates in the macro `$crate::format_args_nl` which comes from the expansion of the macro `println` (in Nightly builds, run with -Z macro-backtrace for more info) help: consider cloning the value if the performance cost is acceptable | 3 | let s2 = s1.clone(); | ++++++++ For more information about this error, try `rustc --explain E0382`. error: could not compile `ownership` (bin "ownership") due to 1 previous error

If you’ve heard the terms shallow copy and deep copy while working with other languages, the concept of copying the pointer, length, and capacity without copying the data probably sounds like making a shallow copy. But because Rust also invalidates the first variable, instead of being called a shallow copy, it’s known as a move. In this example, we would say that s1 was moved into s2. So, what actually happens is shown in Figure 4-4.
如果您在使用其他语言时听说过浅表复制深表复制这两个术语,那么复制指针、长度和容量而不复制数据的概念可能听起来像是浅表复制。但因为 Rust 也会使第一个变量无效,所以它不被称为浅拷贝,而是被称为move 。在此示例中,我们会说s1移至s2中。因此,实际发生的情况如图 4-4 所示。

Three tables: tables s1 and s2 representing those strings on the
stack, respectively, and both pointing to the same string data on the heap.
Table s1 is grayed out be-cause s1 is no longer valid; only s2 can be used to
access the heap data.

Figure 4-4: Representation in memory after s1 has been invalidated
图 4-4: s1失效后内存中的表示

That solves our problem! With only s2 valid, when it goes out of scope it alone will free the memory, and we’re done.
这解决了我们的问题!只有s2有效,当它超出范围时,它就会单独释放内存,我们就完成了。

In addition, there’s a design choice that’s implied by this: Rust will never automatically create “deep” copies of your data. Therefore, any automatic copying can be assumed to be inexpensive in terms of runtime performance.
此外,这还暗示着一个设计选择:Rust 永远不会自动创建数据的“深层”副本。因此,就运行时性能而言,任何自动复制都可以被认为是廉价的。

Variables and Data Interacting with Clone
与克隆交互的变量和数据

If we do want to deeply copy the heap data of the String, not just the stack data, we can use a common method called clone. We’ll discuss method syntax in Chapter 5, but because methods are a common feature in many programming languages, you’ve probably seen them before.
如果我们确实想要深度复制String的堆数据,而不仅仅是堆栈数据,我们可以使用一个称为clone的常用方法。我们将在第 5 章中讨论方法语法,但由于方法是许多编程语言的常见功能,因此您以前可能已经见过它们。

Here’s an example of the clone method in action:
以下是clone方法的实际应用示例:

fn main() { let s1 = String::from("hello"); let s2 = s1.clone(); println!("s1 = {s1}, s2 = {s2}"); }

This works just fine and explicitly produces the behavior shown in Figure 4-3, where the heap data does get copied.
这工作得很好,并显式地产生如图 4-3 所示的行为,其中堆数据确实被复制。

When you see a call to clone, you know that some arbitrary code is being executed and that code may be expensive. It’s a visual indicator that something different is going on.
当您看到对clone的调用时,您就知道正在执行一些任意代码,并且该代码可能很昂贵。这是一个视觉指示器,表明正在发生不同的事情。

Stack-Only Data: Copy 仅堆栈数据:复制

There’s another wrinkle we haven’t talked about yet. This code using integers—part of which was shown in Listing 4-2—works and is valid:
还有另一个问题我们还没有讨论过。这段使用整数的代码(部分代码如清单 4-2 所示)可以工作并且有效:

fn main() { let x = 5; let y = x; println!("x = {x}, y = {y}"); }

But this code seems to contradict what we just learned: we don’t have a call to clone, but x is still valid and wasn’t moved into y.
但这段代码似乎与我们刚刚了解到的内容相矛盾:我们没有调用clone ,但x仍然有效并且没有移动到y中。

The reason is that types such as integers that have a known size at compile time are stored entirely on the stack, so copies of the actual values are quick to make. That means there’s no reason we would want to prevent x from being valid after we create the variable y. In other words, there’s no difference between deep and shallow copying here, so calling clone wouldn’t do anything different from the usual shallow copying, and we can leave it out.
原因是在编译时具有已知大小的整数等类型完全存储在堆栈中,因此可以快速创建实际值的副本。这意味着我们没有理由在创建变量y后阻止x有效。换句话说,这里的深拷贝和浅拷贝没有区别,所以调用clone不会做任何与通常的浅拷贝不同的事情,我们可以省略它。

Rust has a special annotation called the Copy trait that we can place on types that are stored on the stack, as integers are (we’ll talk more about traits in Chapter 10). If a type implements the Copy trait, variables that use it do not move, but rather are trivially copied, making them still valid after assignment to another variable.
Rust 有一个特殊的注释,称为Copy特征,我们可以将其放置在存储在堆栈中的类型上,就像整数一样(我们将在第 10 章中详细讨论忽略特征)。如果某个类型实现了Copy特征,则使用它的变量不会移动,而是会被简单地复制,从而使它们在分配给另一个变量后仍然有效。

Rust won’t let us annotate a type with Copy if the type, or any of its parts, has implemented the Drop trait. If the type needs something special to happen when the value goes out of scope and we add the Copy annotation to that type, we’ll get a compile-time error. To learn about how to add the Copy annotation to your type to implement the trait, see “Derivable Traits” in Appendix C.
如果类型或其任何部分实现了Drop特征,Rust 不会让我们用Copy注释类型。如果当值超出范围时类型需要发生一些特殊的事情,并且我们向该类型添加Copy注释,我们将收到编译时错误。要了解如何将Copy注释添加到您的类型以实现特征,请参阅附录 C 中的“可导出特征”忽略。

So, what types implement the Copy trait? You can check the documentation for the given type to be sure, but as a general rule, any group of simple scalar values can implement Copy, and nothing that requires allocation or is some form of resource can implement Copy. Here are some of the types that implement Copy:
那么,哪些类型实现了Copy特征呢?您可以检查给定类型的文档来确定,但作为一般规则,任何一组简单标量值都可以实现Copy ,并且任何需要分配或某种形式的资源都可以实现Copy 。以下是一些实现Copy的类型:

  • All the integer types, such as u32.
    所有整数类型,例如u32
  • The Boolean type, bool, with values true and false.
    布尔类型bool ,值为truefalse
  • All the floating-point types, such as f64.
    所有浮点类型,例如f64
  • The character type, char.
    字符类型char
  • Tuples, if they only contain types that also implement Copy. For example, (i32, i32) implements Copy, but (i32, String) does not.
    元组,如果它们仅包含也实现Copy的类型。例如, (i32, i32)实现Copy ,但(i32, String)没有。

Ownership and Functions 所有权和职能

The mechanics of passing a value to a function are similar to those when assigning a value to a variable. Passing a variable to a function will move or copy, just as assignment does. Listing 4-3 has an example with some annotations showing where variables go into and out of scope.
将值传递给函数的机制与将值分配给变量时的机制类似。将变量传递给函数将会移动或复制,就像赋值一样。清单 4-3 有一个示例,其中一些注释显示了变量进入和超出范围的位置。

Filename: src/main.rs 文件名:src/main.rs

fn main() { let s = String::from("hello"); // s comes into scope takes_ownership(s); // s's value moves into the function... // ... and so is no longer valid here let x = 5; // x comes into scope makes_copy(x); // x would move into the function, // but i32 is Copy, so it's okay to still // use x afterward } // Here, x goes out of scope, then s. But because s's value was moved, nothing // special happens. fn takes_ownership(some_string: String) { // some_string comes into scope println!("{some_string}"); } // Here, some_string goes out of scope and `drop` is called. The backing // memory is freed. fn makes_copy(some_integer: i32) { // some_integer comes into scope println!("{some_integer}"); } // Here, some_integer goes out of scope. Nothing special happens.

Listing 4-3: Functions with ownership and scope annotated
清单 4-3:带有注释的所有权和作用域的函数

If we tried to use s after the call to takes_ownership, Rust would throw a compile-time error. These static checks protect us from mistakes. Try adding code to main that uses s and x to see where you can use them and where the ownership rules prevent you from doing so.
如果我们在调用takes_ownership之后尝试使用s ,Rust 会抛出编译时错误。这些静态检查可以保护我们免受错误的影响。尝试将使用sx代码添加到main中,看看可以在哪里使用它们以及所有权规则在哪里禁止您这样做。

Return Values and Scope 返回值和范围

Returning values can also transfer ownership. Listing 4-4 shows an example of a function that returns some value, with similar annotations as those in Listing 4-3.
返回值也可以转移所有权。清单 4-4 显示了一个返回某个值的函数示例,其注释与清单 4-3 中的注释类似。

Filename: src/main.rs 文件名:src/main.rs

fn main() { let s1 = gives_ownership(); // gives_ownership moves its return // value into s1 let s2 = String::from("hello"); // s2 comes into scope let s3 = takes_and_gives_back(s2); // s2 is moved into // takes_and_gives_back, which also // moves its return value into s3 } // Here, s3 goes out of scope and is dropped. s2 was moved, so nothing // happens. s1 goes out of scope and is dropped. fn gives_ownership() -> String { // gives_ownership will move its // return value into the function // that calls it let some_string = String::from("yours"); // some_string comes into scope some_string // some_string is returned and // moves out to the calling // function } // This function takes a String and returns one fn takes_and_gives_back(a_string: String) -> String { // a_string comes into // scope a_string // a_string is returned and moves out to the calling function }

Listing 4-4: Transferring ownership of return values
示例 4-4:转移返回值的所有权

The ownership of a variable follows the same pattern every time: assigning a value to another variable moves it. When a variable that includes data on the heap goes out of scope, the value will be cleaned up by drop unless ownership of the data has been moved to another variable.
变量的所有权每次都遵循相同的模式:将值分配给另一个变量会移动它。当包含堆上数据的变量超出范围时,该值将被drop清理,除非数据的所有权已移至另一个变量。

While this works, taking ownership and then returning ownership with every function is a bit tedious. What if we want to let a function use a value but not take ownership? It’s quite annoying that anything we pass in also needs to be passed back if we want to use it again, in addition to any data resulting from the body of the function that we might want to return as well.
虽然这可行,但获取所有权然后返回每个函数的所有权有点乏味。如果我们想让一个函数使用一个值但不获取所有权怎么办?非常烦人的是,如果我们想再次使用我们传入的任何内容,除了我们可能想要返回的函数体产生的任何数据之外,还需要传回它。

Rust does let us return multiple values using a tuple, as shown in Listing 4-5.
Rust 确实允许我们使用元组返回多个值,如清单 4-5 所示。

Filename: src/main.rs 文件名:src/main.rs

fn main() { let s1 = String::from("hello"); let (s2, len) = calculate_length(s1); println!("The length of '{s2}' is {len}."); } fn calculate_length(s: String) -> (String, usize) { let length = s.len(); // len() returns the length of a String (s, length) }

Listing 4-5: Returning ownership of parameters
示例 4-5:返回参数的所有权

But this is too much ceremony and a lot of work for a concept that should be common. Luckily for us, Rust has a feature for using a value without transferring ownership, called references.
但对于一个应该通用的概念来说,这太过仪式和大量工作。对我们来说幸运的是,Rust 有一个使用值而不转移所有权的功能,称为引用

References and Borrowing 参考文献和借用

The issue with the tuple code in Listing 4-5 is that we have to return the String to the calling function so we can still use the String after the call to calculate_length, because the String was moved into calculate_length. Instead, we can provide a reference to the String value. A reference is like a pointer in that it’s an address we can follow to access the data stored at that address; that data is owned by some other variable. Unlike a pointer, a reference is guaranteed to point to a valid value of a particular type for the life of that reference.
清单 4-5 中元组代码的问题在于,我们必须将String返回给调用函数,这样我们在调用calculate_length之后仍然可以使用该String ,因为该String已移至calculate_length中。相反,我们可以提供对String值的引用。引用就像一个指针,我们可以根据它来访问存储在该地址的数据;该数据由其他一些变量拥有。与指针不同,引用保证在该引用的生命周期内指向特定类型的有效值。

Here is how you would define and use a calculate_length function that has a reference to an object as a parameter instead of taking ownership of the value:
以下是定义和使用calculate_length函数的方法,该函数将对对象的引用作为参数,而不是获取值的所有权:

Filename: src/main.rs 文件名:src/main.rs

fn main() { let s1 = String::from("hello"); let len = calculate_length(&s1); println!("The length of '{s1}' is {len}."); } fn calculate_length(s: &String) -> usize { s.len() }

First, notice that all the tuple code in the variable declaration and the function return value is gone. Second, note that we pass &s1 into calculate_length and, in its definition, we take &String rather than String. These ampersands represent references, and they allow you to refer to some value without taking ownership of it. Figure 4-5 depicts this concept.
首先,请注意变量声明和函数返回值中的所有元组代码都消失了。其次,请注意,我们将&s1传递给calculate_length ,并且在其定义中,我们采用&String而不是String 。这些 & 符号代表引用,它们允许您引用某些值而不获取它的所有权。图 4-5 描述了这个概念。

Three tables: the table for s contains only a pointer to the table
for s1. The table for s1 contains the stack data for s1 and points to the
string data on the heap.

Figure 4-5: A diagram of &String s pointing at String s1
图 4-5: &String s指向String s1的示意图

Note: The opposite of referencing by using & is dereferencing, which is accomplished with the dereference operator, *. We’ll see some uses of the dereference operator in Chapter 8 and discuss details of dereferencing in Chapter 15.
注意:与使用&引用相反的是取消引用,这是通过取消引用运算符*完成的。我们将在第 8 章中看到解引用运算符的一些用法,并在第 15 章中讨论解引用的细节。

Let’s take a closer look at the function call here:
让我们仔细看看这里的函数调用:

fn main() { let s1 = String::from("hello"); let len = calculate_length(&s1); println!("The length of '{s1}' is {len}."); } fn calculate_length(s: &String) -> usize { s.len() }

The &s1 syntax lets us create a reference that refers to the value of s1 but does not own it. Because it does not own it, the value it points to will not be dropped when the reference stops being used.
&s1语法允许我们创建一个引用s1的值但不拥有它。因为它不拥有它,所以当引用停止使用时,它指向的值不会被删除。

Likewise, the signature of the function uses & to indicate that the type of the parameter s is a reference. Let’s add some explanatory annotations:
同样,函数的签名使用&来指示参数s的类型是引用。让我们添加一些解释性注释:

fn main() { let s1 = String::from("hello"); let len = calculate_length(&s1); println!("The length of '{s1}' is {len}."); } fn calculate_length(s: &String) -> usize { // s is a reference to a String s.len() } // Here, s goes out of scope. But because it does not have ownership of what // it refers to, it is not dropped.

The scope in which the variable s is valid is the same as any function parameter’s scope, but the value pointed to by the reference is not dropped when s stops being used, because s doesn’t have ownership. When functions have references as parameters instead of the actual values, we won’t need to return the values in order to give back ownership, because we never had ownership.
变量s有效范围与任何函数参数的范围相同,但当s停止使用时,引用指向的值不会被删除,因为s没有所有权。当函数将引用而不是实际值作为参数时,我们不需要返回值来归还所有权,因为我们从未拥有所有权。

We call the action of creating a reference borrowing. As in real life, if a person owns something, you can borrow it from them. When you’re done, you have to give it back. You don’t own it.
我们将创建引用的操作称为借用。就像在现实生活中一样,如果一个人拥有某样东西,你可以向他们借用。当你完成后,你必须把它还给你。你不拥有它。

So, what happens if we try to modify something we’re borrowing? Try the code in Listing 4-6. Spoiler alert: it doesn’t work!
那么,如果我们尝试修改借用的东西会发生什么?尝试清单 4-6 中的代码。剧透警告:这不起作用!

Filename: src/main.rs 文件名:src/main.rs

fn main() { let s = String::from("hello"); change(&s); } fn change(some_string: &String) { some_string.push_str(", world"); }

Listing 4-6: Attempting to modify a borrowed value
示例 4-6:尝试修改借用的值

Here’s the error: 这是错误:

$ cargo run Compiling ownership v0.1.0 (file:///projects/ownership) error[E0596]: cannot borrow `*some_string` as mutable, as it is behind a `&` reference --> src/main.rs:8:5 | 8 | some_string.push_str(", world"); | ^^^^^^^^^^^ `some_string` is a `&` reference, so the data it refers to cannot be borrowed as mutable | help: consider changing this to be a mutable reference | 7 | fn change(some_string: &mut String) { | +++ For more information about this error, try `rustc --explain E0596`. error: could not compile `ownership` (bin "ownership") due to 1 previous error

Just as variables are immutable by default, so are references. We’re not allowed to modify something we have a reference to.
正如变量在默认情况下是不可变的一样,引用也是如此。我们不允许修改我们所引用的内容。

Mutable References 可变引用

We can fix the code from Listing 4-6 to allow us to modify a borrowed value with just a few small tweaks that use, instead, a mutable reference:
我们可以修复清单 4-6 中的代码,只需要进行一些小调整即可修改借用的值,而这些调整使用的是可变引用

Filename: src/main.rs 文件名:src/main.rs

fn main() { let mut s = String::from("hello"); change(&mut s); } fn change(some_string: &mut String) { some_string.push_str(", world"); }

First we change s to be mut. Then we create a mutable reference with &mut s where we call the change function, and update the function signature to accept a mutable reference with some_string: &mut String. This makes it very clear that the change function will mutate the value it borrows.
首先我们将s更改为mut 。然后,我们使用&mut s创建一个可变引用,在其中调用change函数,并更新函数签名以接受使用some_string: &mut String可变引用。这非常清楚地表明, change函数将改变它借用的值。

Mutable references have one big restriction: if you have a mutable reference to a value, you can have no other references to that value. This code that attempts to create two mutable references to s will fail:
可变引用有一个很大的限制:如果您有一个对某个值的可变引用,则不能有对该值的其他引用。尝试创建两个对s的可变引用的代码将失败:

Filename: src/main.rs 文件名:src/main.rs

fn main() { let mut s = String::from("hello"); let r1 = &mut s; let r2 = &mut s; println!("{}, {}", r1, r2); }

Here’s the error: 这是错误:

$ cargo run Compiling ownership v0.1.0 (file:///projects/ownership) error[E0499]: cannot borrow `s` as mutable more than once at a time --> src/main.rs:5:14 | 4 | let r1 = &mut s; | ------ first mutable borrow occurs here 5 | let r2 = &mut s; | ^^^^^^ second mutable borrow occurs here 6 | 7 | println!("{}, {}", r1, r2); | -- first borrow later used here For more information about this error, try `rustc --explain E0499`. error: could not compile `ownership` (bin "ownership") due to 1 previous error

This error says that this code is invalid because we cannot borrow s as mutable more than once at a time. The first mutable borrow is in r1 and must last until it’s used in the println!, but between the creation of that mutable reference and its usage, we tried to create another mutable reference in r2 that borrows the same data as r1.
此错误表明此代码无效,因为我们一次不能多次借用s作为可变对象。第一个可变借用位于r1中,并且必须持续到在println! ,但在创建该可变引用及其使用之间,我们尝试在r2中创建另一个可变引用,借用与r1相同的数据。

The restriction preventing multiple mutable references to the same data at the same time allows for mutation but in a very controlled fashion. It’s something that new Rustaceans struggle with because most languages let you mutate whenever you’d like. The benefit of having this restriction is that Rust can prevent data races at compile time. A data race is similar to a race condition and happens when these three behaviors occur:
防止同时对同一数据进行多个可变引用的限制允许突变,但以非常受控的方式进行。这是新的 Rustaceans 所面临的问题,因为大多数语言都允许你随时进行变异。具有此限制的好处是 Rust 可以防止编译时的数据竞争。数据竞争与竞争条件类似,当发生以下三种行为时就会发生:

  • Two or more pointers access the same data at the same time.
    两个或多个指针同时访问相同的数据。
  • At least one of the pointers is being used to write to the data.
    至少有一个指针用于写入数据。
  • There’s no mechanism being used to synchronize access to the data.
    没有使用任何机制来同步对数据的访问。

Data races cause undefined behavior and can be difficult to diagnose and fix when you’re trying to track them down at runtime; Rust prevents this problem by refusing to compile code with data races!
数据争用会导致未定义的行为,并且当您尝试在运行时追踪数据争用时,可能很难诊断和修复; Rust 通过拒绝编译带有数据竞争的代码来防止这个问题!

As always, we can use curly brackets to create a new scope, allowing for multiple mutable references, just not simultaneous ones:
与往常一样,我们可以使用大括号创建一个新范围,允许多个可变引用,但不能同时引用:

fn main() { let mut s = String::from("hello"); { let r1 = &mut s; } // r1 goes out of scope here, so we can make a new reference with no problems. let r2 = &mut s; }

Rust enforces a similar rule for combining mutable and immutable references. This code results in an error:
Rust 强制执行类似的规则来组合可变和不可变引用。此代码会导致错误:

fn main() { let mut s = String::from("hello"); let r1 = &s; // no problem let r2 = &s; // no problem let r3 = &mut s; // BIG PROBLEM println!("{}, {}, and {}", r1, r2, r3); }

Here’s the error: 这是错误:

$ cargo run Compiling ownership v0.1.0 (file:///projects/ownership) error[E0502]: cannot borrow `s` as mutable because it is also borrowed as immutable --> src/main.rs:6:14 | 4 | let r1 = &s; // no problem | -- immutable borrow occurs here 5 | let r2 = &s; // no problem 6 | let r3 = &mut s; // BIG PROBLEM | ^^^^^^ mutable borrow occurs here 7 | 8 | println!("{}, {}, and {}", r1, r2, r3); | -- immutable borrow later used here For more information about this error, try `rustc --explain E0502`. error: could not compile `ownership` (bin "ownership") due to 1 previous error

Whew! We also cannot have a mutable reference while we have an immutable one to the same value.
哇!当我们拥有相同值的不可变引用时,我们不能拥有可变引用。

Users of an immutable reference don’t expect the value to suddenly change out from under them! However, multiple immutable references are allowed because no one who is just reading the data has the ability to affect anyone else’s reading of the data.
不可变引用的用户不会期望其值会突然发生变化!然而,多个不可变引用是允许的,因为仅仅读取数据的人没有能力影响其他人对数据的读取。

Note that a reference’s scope starts from where it is introduced and continues through the last time that reference is used. For instance, this code will compile because the last usage of the immutable references, the println!, occurs before the mutable reference is introduced:
请注意,引用的范围从引入它的地方开始,一直持续到上次使用该引用时为止。例如,这段代码将会编译,因为最后一次使用了不可变引用println! ,发生在引入可变引用之前:

fn main() { let mut s = String::from("hello"); let r1 = &s; // no problem let r2 = &s; // no problem println!("{r1} and {r2}"); // variables r1 and r2 will not be used after this point let r3 = &mut s; // no problem println!("{r3}"); }

The scopes of the immutable references r1 and r2 end after the println! where they are last used, which is before the mutable reference r3 is created. These scopes don’t overlap, so this code is allowed: the compiler can tell that the reference is no longer being used at a point before the end of the scope.
不可变引用r1r2的范围在println!之后结束。它们最后一次使用的位置是在创建可变引用r3之前。这些作用域不重叠,因此允许使用此代码:编译器可以判断在作用域结束之前的某个点不再使用该引用。

Even though borrowing errors may be frustrating at times, remember that it’s the Rust compiler pointing out a potential bug early (at compile time rather than at runtime) and showing you exactly where the problem is. Then you don’t have to track down why your data isn’t what you thought it was.
尽管借用错误有时可能会令人沮丧,但请记住,Rust 编译器会尽早(在编译时而不是运行时)指出潜在的错误,并向您准确显示问题所在。这样您就不必追查为什么您的数据与您想象的不同。

Dangling References 悬空引用

In languages with pointers, it’s easy to erroneously create a dangling pointer—a pointer that references a location in memory that may have been given to someone else—by freeing some memory while preserving a pointer to that memory. In Rust, by contrast, the compiler guarantees that references will never be dangling references: if you have a reference to some data, the compiler will ensure that the data will not go out of scope before the reference to the data does.
在带有指针的语言中,通过释放一些内存同时保留指向该内存的指针,很容易错误地创建悬空指针(引用内存中可能已分配给其他人的位置的指针)。相比之下,在 Rust 中,编译器保证引用永远不会是悬空引用:如果您引用了某些数据,编译器将确保数据不会在数据引用超出范围之前超出范围。

Let’s try to create a dangling reference to see how Rust prevents them with a compile-time error:
让我们尝试创建一个悬空引用,看看 Rust 如何通过编译时错误来防止它们:

Filename: src/main.rs 文件名:src/main.rs

fn main() { let reference_to_nothing = dangle(); } fn dangle() -> &String { let s = String::from("hello"); &s }

Here’s the error: 这是错误:

$ cargo run Compiling ownership v0.1.0 (file:///projects/ownership) error[E0106]: missing lifetime specifier --> src/main.rs:5:16 | 5 | fn dangle() -> &String { | ^ expected named lifetime parameter | = help: this function's return type contains a borrowed value, but there is no value for it to be borrowed from help: consider using the `'static` lifetime, but this is uncommon unless you're returning a borrowed value from a `const` or a `static` | 5 | fn dangle() -> &'static String { | +++++++ help: instead, you are more likely to want to return an owned value | 5 - fn dangle() -> &String { 5 + fn dangle() -> String { | error[E0515]: cannot return reference to local variable `s` --> src/main.rs:8:5 | 8 | &s | ^^ returns a reference to data owned by the current function Some errors have detailed explanations: E0106, E0515. For more information about an error, try `rustc --explain E0106`. error: could not compile `ownership` (bin "ownership") due to 2 previous errors

This error message refers to a feature we haven’t covered yet: lifetimes. We’ll discuss lifetimes in detail in Chapter 10. But, if you disregard the parts about lifetimes, the message does contain the key to why this code is a problem:
此错误消息涉及我们尚未介绍的功能:生命周期。我们将在第 10 章中详细讨论生命周期。但是,如果您忽略有关生命周期的部分,该消息确实包含了为什么此代码存在问题的关键:

this function's return type contains a borrowed value, but there is no value for it to be borrowed from

Let’s take a closer look at exactly what’s happening at each stage of our dangle code:
让我们仔细看看dangle代码的每个阶段到底发生了什么:

Filename: src/main.rs 文件名:src/main.rs

fn main() { let reference_to_nothing = dangle(); } fn dangle() -> &String { // dangle returns a reference to a String let s = String::from("hello"); // s is a new String &s // we return a reference to the String, s } // Here, s goes out of scope, and is dropped. Its memory goes away. // Danger!

Because s is created inside dangle, when the code of dangle is finished, s will be deallocated. But we tried to return a reference to it. That means this reference would be pointing to an invalid String. That’s no good! Rust won’t let us do this.
因为s是在dangle内部创建的,所以当dangle的代码完成时, s将被释放。但我们试图返回对它的引用。这意味着此引用将指向无效的String 。这样可不行啊! Rust 不会让我们这样做。

The solution here is to return the String directly:
这里的解决方案是直接返回String

fn main() { let string = no_dangle(); } fn no_dangle() -> String { let s = String::from("hello"); s }

This works without any problems. Ownership is moved out, and nothing is deallocated.
这工作没有任何问题。所有权被移出,并且没有任何内容被释放。

The Rules of References 参考文献规则

Let’s recap what we’ve discussed about references:
让我们回顾一下我们讨论过的关于参考文献的内容:

  • At any given time, you can have either one mutable reference or any number of immutable references.
    在任何给定时间,您可以拥有一个可变引用任意数量的不可变引用。
  • References must always be valid.
    参考文献必须始终有效。

Next, we’ll look at a different kind of reference: slices.
接下来,我们将看看另一种不同的引用:切片。

The Slice Type 切片类型

Slices let you reference a contiguous sequence of elements in a collection rather than the whole collection. A slice is a kind of reference, so it does not have ownership.
切片允许您引用集合中连续的元素序列,而不是整个集合。切片是一种引用,因此它没有所有权。

Here’s a small programming problem: write a function that takes a string of words separated by spaces and returns the first word it finds in that string. If the function doesn’t find a space in the string, the whole string must be one word, so the entire string should be returned.
这是一个小编程问题:编写一个函数,该函数接受由空格分隔的单词字符串,并返回在该字符串中找到的第一个单词。如果函数在字符串中没有找到空格,则整个字符串必须是一个单词,因此应返回整个字符串。

Let’s work through how we’d write the signature of this function without using slices, to understand the problem that slices will solve:
让我们看看如何在不使用切片的情况下编写该函数的签名,以了解切片将解决的​​问题:

fn first_word(s: &String) -> ?

The first_word function has a &String as a parameter. We don’t want ownership, so this is fine. But what should we return? We don’t really have a way to talk about part of a string. However, we could return the index of the end of the word, indicated by a space. Let’s try that, as shown in Listing 4-7.
first_word函数有一个&String作为参数。我们不需要所有权,所以这很好。但我们应该返回什么?我们确实没有办法谈论字符串的一部分。但是,我们可以返回单词末尾的索引,以空格表示。让我们尝试一下,如清单 4-7 所示。

Filename: src/main.rs 文件名:src/main.rs

fn first_word(s: &String) -> usize { let bytes = s.as_bytes(); for (i, &item) in bytes.iter().enumerate() { if item == b' ' { return i; } } s.len() } fn main() {}

Listing 4-7: The first_word function that returns a byte index value into the String parameter
示例 4-7: first_word函数将字节索引值返回到String参数中

Because we need to go through the String element by element and check whether a value is a space, we’ll convert our String to an array of bytes using the as_bytes method.
因为我们需要逐个元素地遍历String并检查值是否为空格,所以我们将使用as_bytes方法将String转换为字节数组。

fn first_word(s: &String) -> usize { let bytes = s.as_bytes(); for (i, &item) in bytes.iter().enumerate() { if item == b' ' { return i; } } s.len() } fn main() {}

Next, we create an iterator over the array of bytes using the iter method:
接下来,我们使用iter方法在字节数组上创建一个迭代器:

fn first_word(s: &String) -> usize { let bytes = s.as_bytes(); for (i, &item) in bytes.iter().enumerate() { if item == b' ' { return i; } } s.len() } fn main() {}

We’ll discuss iterators in more detail in Chapter 13. For now, know that iter is a method that returns each element in a collection and that enumerate wraps the result of iter and returns each element as part of a tuple instead. The first element of the tuple returned from enumerate is the index, and the second element is a reference to the element. This is a bit more convenient than calculating the index ourselves.
我们将在第 13 章中更详细地讨论迭代器。现在,我们知道iter是一个返回集合中每个元素的方法,并且enumerate包装了iter的结果并将每个元素作为元组的一部分返回。 enumerate返回的元组的第一个元素是索引,第二个元素是对该元素的引用。这比我们自己计算指数方便一点。

Because the enumerate method returns a tuple, we can use patterns to destructure that tuple. We’ll be discussing patterns more in Chapter 6. In the for loop, we specify a pattern that has i for the index in the tuple and &item for the single byte in the tuple. Because we get a reference to the element from .iter().enumerate(), we use & in the pattern.
因为enumerate方法返回一个元组,所以我们可以使用模式来解构该元组。我们将在第 6 章中更多地讨论忽略模式。在for循环中,我们指定一个模式,其中i为元组中的索引, &item为元组中的单个字节。因为我们从.iter().enumerate()获取对元素的引用,所以我们在模式中使用&

Inside the for loop, we search for the byte that represents the space by using the byte literal syntax. If we find a space, we return the position. Otherwise, we return the length of the string by using s.len().
for循环内,我们使用字节文字语法搜索表示空间的字节。如果我们找到一个空间,我们就会返回该位置。否则,我们使用s.len()返回字符串的长度。

fn first_word(s: &String) -> usize { let bytes = s.as_bytes(); for (i, &item) in bytes.iter().enumerate() { if item == b' ' { return i; } } s.len() } fn main() {}

We now have a way to find out the index of the end of the first word in the string, but there’s a problem. We’re returning a usize on its own, but it’s only a meaningful number in the context of the &String. In other words, because it’s a separate value from the String, there’s no guarantee that it will still be valid in the future. Consider the program in Listing 4-8 that uses the first_word function from Listing 4-7.
我们现在有办法找出字符串中第一个单词末尾的索引,但是有一个问题。我们自己返回一个usize ,但它只是&String上下文中的一个有意义的数字。换句话说,因为它是与String不同的值,所以不能保证它在将来仍然有效。考虑清单 4-8 中的程序,它使用清单 4-7 中的first_word函数。

Filename: src/main.rs 文件名:src/main.rs

fn first_word(s: &String) -> usize { let bytes = s.as_bytes(); for (i, &item) in bytes.iter().enumerate() { if item == b' ' { return i; } } s.len() } fn main() { let mut s = String::from("hello world"); let word = first_word(&s); // word will get the value 5 s.clear(); // this empties the String, making it equal to "" // word still has the value 5 here, but there's no more string that // we could meaningfully use the value 5 with. word is now totally invalid! }

Listing 4-8: Storing the result from calling the first_word function and then changing the String contents
示例 4-8:存储调用first_word函数然后更改String内容的结果

This program compiles without any errors and would also do so if we used word after calling s.clear(). Because word isn’t connected to the state of s at all, word still contains the value 5. We could use that value 5 with the variable s to try to extract the first word out, but this would be a bug because the contents of s have changed since we saved 5 in word.
该程序编译时没有任何错误,如果我们在调用s.clear()后使用word也会这样做。因为word根本没有连接到s的状态,所以word仍然包含值5 。我们可以使用值5和变量s来尝试提取第一个单词,但这将是一个错误,因为自从我们在word中保存5以来s的内容已经发生了变化。

Having to worry about the index in word getting out of sync with the data in s is tedious and error prone! Managing these indices is even more brittle if we write a second_word function. Its signature would have to look like this:
不得不担心word中的索引与s中的数据不同步是乏味且容易出错的!如果我们编写second_word函数,管理这些索引会更加脆弱。它的签名必须如下所示:

fn second_word(s: &String) -> (usize, usize) {

Now we’re tracking a starting and an ending index, and we have even more values that were calculated from data in a particular state but aren’t tied to that state at all. We have three unrelated variables floating around that need to be kept in sync.
现在我们正在跟踪开始结束索引,并且我们有更多的值是根据特定状态的数据计算出来的,但根本与该状态无关。我们有三个不相关的变量需要保持同步。

Luckily, Rust has a solution to this problem: string slices.
幸运的是,Rust 有一个解决这个问题的方法:字符串切片。

String Slices 字符串切片

A string slice is a reference to part of a String, and it looks like this:
字符串切片是对String的一部分的引用,它看起来像这样:

fn main() { let s = String::from("hello world"); let hello = &s[0..5]; let world = &s[6..11]; }

Rather than a reference to the entire String, hello is a reference to a portion of the String, specified in the extra [0..5] bit. We create slices using a range within brackets by specifying [starting_index..ending_index], where starting_index is the first position in the slice and ending_index is one more than the last position in the slice. Internally, the slice data structure stores the starting position and the length of the slice, which corresponds to ending_index minus starting_index. So, in the case of let world = &s[6..11];, world would be a slice that contains a pointer to the byte at index 6 of s with a length value of 5.
hello不是对整个String的引用,而是对String的一部分的引用,在额外的[0..5]位中指定。我们通过指定[starting_index..ending_index]使用括号内的范围创建切片,其中starting_index是切片中的第一个位置, ending_index比切片中的最后一个位置多一个。在内部,切片数据结构存储切片的起始位置和长度,对应于ending_index减去starting_index 。因此,在let world = &s[6..11];的情况下, world将是一个切片,其中包含指向s索引 6 处的字节的指针,长度值为5

Figure 4-6 shows this in a diagram.
图 4-6 以图表形式展示了这一点。

Three tables: a table representing the stack data of s, which points
to the byte at index 0 in a table of the string data "hello world" on
the heap. The third table rep-resents the stack data of the slice world, which
has a length value of 5 and points to byte 6 of the heap data table.

Figure 4-6: String slice referring to part of a String
图 4-6:引用String的一部分的字符串切片

With Rust’s .. range syntax, if you want to start at index 0, you can drop the value before the two periods. In other words, these are equal:
使用 Rust 的.. range 语法,如果您想从索引 0 开始,则可以删除两个句点之前的值。换句话说,它们是相等的:

#![allow(unused)] fn main() { let s = String::from("hello"); let slice = &s[0..2]; let slice = &s[..2]; }

By the same token, if your slice includes the last byte of the String, you can drop the trailing number. That means these are equal:
同样,如果您的切片包含String的最后一个字节,则可以删除尾随数字。这意味着它们是相等的:

#![allow(unused)] fn main() { let s = String::from("hello"); let len = s.len(); let slice = &s[3..len]; let slice = &s[3..]; }

You can also drop both values to take a slice of the entire string. So these are equal:
您还可以删除这两个值以获取整个字符串的一部分。所以这些是相等的:

#![allow(unused)] fn main() { let s = String::from("hello"); let len = s.len(); let slice = &s[0..len]; let slice = &s[..]; }

Note: String slice range indices must occur at valid UTF-8 character boundaries. If you attempt to create a string slice in the middle of a multibyte character, your program will exit with an error. For the purposes of introducing string slices, we are assuming ASCII only in this section; a more thorough discussion of UTF-8 handling is in the “Storing UTF-8 Encoded Text with Strings” section of Chapter 8.
注意:字符串切片范围索引必须出现在有效的 UTF-8 字符边界处。如果您尝试在多字节字符的中间创建字符串切片,您的程序将错误退出。为了介绍字符串切片,我们在本节中仅假设 ASCII;关于 UTF-8 处理的更全面的讨论在第 8 章的“用字符串存储 UTF-8 编码文本”忽略部分。

With all this information in mind, let’s rewrite first_word to return a slice. The type that signifies “string slice” is written as &str:
考虑到所有这些信息,让我们重写first_word以返回一个切片。表示“字符串切片”的类型写为&str

Filename: src/main.rs 文件名:src/main.rs

fn first_word(s: &String) -> &str { let bytes = s.as_bytes(); for (i, &item) in bytes.iter().enumerate() { if item == b' ' { return &s[0..i]; } } &s[..] } fn main() {}

We get the index for the end of the word the same way we did in Listing 4-7, by looking for the first occurrence of a space. When we find a space, we return a string slice using the start of the string and the index of the space as the starting and ending indices.
我们以与清单 4-7 中相同的方式获取单词结尾的索引,即查找第一次出现的空格。当我们找到一个空格时,我们使用字符串的开头和空格的索引作为开始和结束索引返回一个字符串切片。

Now when we call first_word, we get back a single value that is tied to the underlying data. The value is made up of a reference to the starting point of the slice and the number of elements in the slice.
现在,当我们调用first_word时,我们会返回一个与底层数据相关的单个值。该值由对切片起始点的引用和切片中元素的数量组成。

Returning a slice would also work for a second_word function:
返回切片也适用于second_word函数:

fn second_word(s: &String) -> &str {

We now have a straightforward API that’s much harder to mess up because the compiler will ensure the references into the String remain valid. Remember the bug in the program in Listing 4-8, when we got the index to the end of the first word but then cleared the string so our index was invalid? That code was logically incorrect but didn’t show any immediate errors. The problems would show up later if we kept trying to use the first word index with an emptied string. Slices make this bug impossible and let us know we have a problem with our code much sooner. Using the slice version of first_word will throw a compile-time error:
我们现在有了一个简单的 API,更难弄乱,因为编译器将确保对String引用保持有效。还记得清单 4-8 中程序中的错误吗?当时我们获得了第一个单词末尾的索引,但随后清除了字符串,因此我们的索引无效了?该代码在逻辑上不正确,但没有显示任何直接错误。如果我们继续尝试将第一个单词索引与空字符串一起使用,问题稍后就会出现。切片使这个错误不可能发生,并让我们更快地知道我们的代码有问题。使用first_word的切片版本将引发编译时错误:

Filename: src/main.rs 文件名:src/main.rs

fn first_word(s: &String) -> &str { let bytes = s.as_bytes(); for (i, &item) in bytes.iter().enumerate() { if item == b' ' { return &s[0..i]; } } &s[..] } fn main() { let mut s = String::from("hello world"); let word = first_word(&s); s.clear(); // error! println!("the first word is: {word}"); }

Here’s the compiler error:
这是编译器错误:

$ cargo run Compiling ownership v0.1.0 (file:///projects/ownership) error[E0502]: cannot borrow `s` as mutable because it is also borrowed as immutable --> src/main.rs:18:5 | 16 | let word = first_word(&s); | -- immutable borrow occurs here 17 | 18 | s.clear(); // error! | ^^^^^^^^^ mutable borrow occurs here 19 | 20 | println!("the first word is: {word}"); | ------ immutable borrow later used here For more information about this error, try `rustc --explain E0502`. error: could not compile `ownership` (bin "ownership") due to 1 previous error

Recall from the borrowing rules that if we have an immutable reference to something, we cannot also take a mutable reference. Because clear needs to truncate the String, it needs to get a mutable reference. The println! after the call to clear uses the reference in word, so the immutable reference must still be active at that point. Rust disallows the mutable reference in clear and the immutable reference in word from existing at the same time, and compilation fails. Not only has Rust made our API easier to use, but it has also eliminated an entire class of errors at compile time!
回想一下借用规则,如果我们对某个东西有一个不可变的引用,我们就不能同时使用一个可变的引用。因为clear需要截断String ,所以它需要获得一个可变引用。 println!在调用clear之后使用word中的引用,因此不可变引用此时必须仍然处于活动状态。 Rust不允许clear中的可变引用和word中的不可变引用同时存在,并且编译失败。 Rust 不仅使我们的 API 更易于使用,而且还消除了编译时的一整类错误!

String Literals as Slices
字符串文字作为切片

Recall that we talked about string literals being stored inside the binary. Now that we know about slices, we can properly understand string literals:
回想一下,我们讨论过存储在二进制文件中的字符串文字。现在我们了解了切片,我们可以正确理解字符串文字:

#![allow(unused)] fn main() { let s = "Hello, world!"; }

The type of s here is &str: it’s a slice pointing to that specific point of the binary. This is also why string literals are immutable; &str is an immutable reference.
这里s的类型是&str :它是一个指向二进制文件的特定点的切片。这也是字符串文字不可变的原因; &str是一个不可变的引用。

String Slices as Parameters
字符串切片作为参数

Knowing that you can take slices of literals and String values leads us to one more improvement on first_word, and that’s its signature:
知道你可以获取文字和String值的切片,这使我们对first_word进行了进一步的改进,这就是它的签名:

fn first_word(s: &String) -> &str {

A more experienced Rustacean would write the signature shown in Listing 4-9 instead because it allows us to use the same function on both &String values and &str values.
更有经验的 Rustacean 会编写清单 4-9 中所示的签名,因为它允许我们对&String值和&str值使用相同的函数。

fn first_word(s: &str) -> &str { let bytes = s.as_bytes(); for (i, &item) in bytes.iter().enumerate() { if item == b' ' { return &s[0..i]; } } &s[..] } fn main() { let my_string = String::from("hello world"); // `first_word` works on slices of `String`s, whether partial or whole let word = first_word(&my_string[0..6]); let word = first_word(&my_string[..]); // `first_word` also works on references to `String`s, which are equivalent // to whole slices of `String`s let word = first_word(&my_string); let my_string_literal = "hello world"; // `first_word` works on slices of string literals, whether partial or whole let word = first_word(&my_string_literal[0..6]); let word = first_word(&my_string_literal[..]); // Because string literals *are* string slices already, // this works too, without the slice syntax! let word = first_word(my_string_literal); }

Listing 4-9: Improving the first_word function by using a string slice for the type of the s parameter
示例 4-9:通过使用字符串切片作为s参数的类型来改进first_word函数

If we have a string slice, we can pass that directly. If we have a String, we can pass a slice of the String or a reference to the String. This flexibility takes advantage of deref coercions, a feature we will cover in the “Implicit Deref Coercions with Functions and Methods” section of Chapter 15.
如果我们有一个字符串切片,我们可以直接传递它。如果我们有一个String ,我们可以传递String的切片或对String引用。这种灵活性利用了deref 强制转换,我们将在第 15 章的“使用函数和方法进行隐式 Deref 强制转换”忽略部分中介绍该功能。

Defining a function to take a string slice instead of a reference to a String makes our API more general and useful without losing any functionality:
定义一个函数来获取字符串切片而不是对String的引用,使我们的 API 更加通用和有用,而不会丢失任何功能:

Filename: src/main.rs 文件名:src/main.rs

fn first_word(s: &str) -> &str { let bytes = s.as_bytes(); for (i, &item) in bytes.iter().enumerate() { if item == b' ' { return &s[0..i]; } } &s[..] } fn main() { let my_string = String::from("hello world"); // `first_word` works on slices of `String`s, whether partial or whole let word = first_word(&my_string[0..6]); let word = first_word(&my_string[..]); // `first_word` also works on references to `String`s, which are equivalent // to whole slices of `String`s let word = first_word(&my_string); let my_string_literal = "hello world"; // `first_word` works on slices of string literals, whether partial or whole let word = first_word(&my_string_literal[0..6]); let word = first_word(&my_string_literal[..]); // Because string literals *are* string slices already, // this works too, without the slice syntax! let word = first_word(my_string_literal); }

Other Slices 其他切片

String slices, as you might imagine, are specific to strings. But there’s a more general slice type too. Consider this array:
正如您可能想象的那样,字符串切片特定于字符串。但还有更通用的切片类型。考虑这个数组:

#![allow(unused)] fn main() { let a = [1, 2, 3, 4, 5]; }

Just as we might want to refer to part of a string, we might want to refer to part of an array. We’d do so like this:
正如我们可能想要引用字符串的一部分一样,我们可能想要引用数组的一部分。我们会这样做:

#![allow(unused)] fn main() { let a = [1, 2, 3, 4, 5]; let slice = &a[1..3]; assert_eq!(slice, &[2, 3]); }

This slice has the type &[i32]. It works the same way as string slices do, by storing a reference to the first element and a length. You’ll use this kind of slice for all sorts of other collections. We’ll discuss these collections in detail when we talk about vectors in Chapter 8.
该切片的类型为&[i32] 。它的工作方式与字符串切片相同,通过存储对第一个元素的引用和长度。您将把这种切片用于各种其他集合。当我们在第 8 章讨论向量时,我们将详细讨论这些集合。

Summary 概括

The concepts of ownership, borrowing, and slices ensure memory safety in Rust programs at compile time. The Rust language gives you control over your memory usage in the same way as other systems programming languages, but having the owner of data automatically clean up that data when the owner goes out of scope means you don’t have to write and debug extra code to get this control.
所有权、借用和切片的概念确保 Rust 程序在编译时的内存安全。 Rust 语言让您能够以与其他系统编程语言相同的方式控制内存使用情况,但是当数据所有者超出范围时,让数据所有者自动清理该数据意味着您无需编写和调试额外的代码来获得这个控制权。

Ownership affects how lots of other parts of Rust work, so we’ll talk about these concepts further throughout the rest of the book. Let’s move on to Chapter 5 and look at grouping pieces of data together in a struct.
所有权会影响 Rust 许多其他部分的工作方式,因此我们将在本书的其余部分进一步讨论这些概念。让我们继续第 5 章,看看如何将数据片段分组到一个struct中。

Using Structs to Structure Related Data
使用结构体构建相关数据

A struct, or structure, is a custom data type that lets you package together and name multiple related values that make up a meaningful group. If you’re familiar with an object-oriented language, a struct is like an object’s data attributes. In this chapter, we’ll compare and contrast tuples with structs to build on what you already know and demonstrate when structs are a better way to group data.
structStructure是一种自定义数据类型,可让您将多个相关值打包并命名,从而组成一个有意义的组。如果您熟悉面向对象的语言,结构就像对象的数据属性。在本章中,我们将在您已知的基础上对元组与结构进行比较和对比,并演示结构何时是更好的数据分组方式。

We’ll demonstrate how to define and instantiate structs. We’ll discuss how to define associated functions, especially the kind of associated functions called methods, to specify behavior associated with a struct type. Structs and enums (discussed in Chapter 6) are the building blocks for creating new types in your program’s domain to take full advantage of Rust’s compile-time type checking.
我们将演示如何定义和实例化结构。我们将讨论如何定义关联函数,特别是称为方法的关联函数,以指定与结构类型关联的行为。结构体和枚举(在第 6 章中讨论)是在程序域中创建新类型的构建块,以充分利用 Rust 的编译时类型检查。

Defining and Instantiating Structs
定义和实例化结构

Structs are similar to tuples, discussed in “The Tuple Type” section, in that both hold multiple related values. Like tuples, the pieces of a struct can be different types. Unlike with tuples, in a struct you’ll name each piece of data so it’s clear what the values mean. Adding these names means that structs are more flexible than tuples: you don’t have to rely on the order of the data to specify or access the values of an instance.
结构体与元组类似(在“元组类型”忽略部分中讨论),因为两者都保存多个相关值。与元组一样,结构体的各个部分可以是不同的类型。与元组不同,在结构体中,您将为每条数据命名,以便清楚地了解这些值的含义。添加这些名称意味着结构比元组更​​灵活:您不必依赖数据的顺序来指定或访问实例的值。

To define a struct, we enter the keyword struct and name the entire struct. A struct’s name should describe the significance of the pieces of data being grouped together. Then, inside curly brackets, we define the names and types of the pieces of data, which we call fields. For example, Listing 5-1 shows a struct that stores information about a user account.
要定义结构,我们输入关键字struct并命名整个结构。结构体的名称应该描述分组在一起的数据片段的重要性。然后,在大括号内,我们定义数据块的名称和类型,我们将其称为fields 。例如,清单 5-1 显示了一个存储有关用户帐户信息的结构体。

Filename: src/main.rs 文件名:src/main.rs

struct User { active: bool, username: String, email: String, sign_in_count: u64, } fn main() {}

Listing 5-1: A User struct definition
清单 5-1: User结构体定义

To use a struct after we’ve defined it, we create an instance of that struct by specifying concrete values for each of the fields. We create an instance by stating the name of the struct and then add curly brackets containing key: value pairs, where the keys are the names of the fields and the values are the data we want to store in those fields. We don’t have to specify the fields in the same order in which we declared them in the struct. In other words, the struct definition is like a general template for the type, and instances fill in that template with particular data to create values of the type. For example, we can declare a particular user as shown in Listing 5-2.
要在定义结构后使用它,我们通过为每个字段指定具体值来创建该结构的实例。我们通过声明结构体的名称来创建一个实例,然后添加包含键:值对的大括号,其中键是字段的名称,值是我们要存储在这些字段中的数据。我们不必按照在结构中声明字段的顺序指定字段。换句话说,结构定义就像该类型的通用模板,实例使用特定数据填充该模板以创建该类型的值。例如,我们可以声明一个特定的用户,如清单 5-2 所示。

Filename: src/main.rs 文件名:src/main.rs

struct User { active: bool, username: String, email: String, sign_in_count: u64, } fn main() { let user1 = User { active: true, username: String::from("someusername123"), email: String::from("someone@example.com"), sign_in_count: 1, }; }

Listing 5-2: Creating an instance of the User struct
清单 5-2:创建User结构体的实例

To get a specific value from a struct, we use dot notation. For example, to access this user’s email address, we use user1.email. If the instance is mutable, we can change a value by using the dot notation and assigning into a particular field. Listing 5-3 shows how to change the value in the email field of a mutable User instance.
为了从结构中获取特定值,我们使用点表示法。例如,要访问该用户的电子邮件地址,我们使用user1.email 。如果实例是可变的,我们可以通过使用点符号并分配到特定字段来更改值。清单 5-3 显示了如何更改可变User实例的email字段中的值。

Filename: src/main.rs 文件名:src/main.rs

struct User { active: bool, username: String, email: String, sign_in_count: u64, } fn main() { let mut user1 = User { active: true, username: String::from("someusername123"), email: String::from("someone@example.com"), sign_in_count: 1, }; user1.email = String::from("anotheremail@example.com"); }

Listing 5-3: Changing the value in the email field of a User instance
清单 5-3:更改User实例的email字段中的值

Note that the entire instance must be mutable; Rust doesn’t allow us to mark only certain fields as mutable. As with any expression, we can construct a new instance of the struct as the last expression in the function body to implicitly return that new instance.
请注意,整个实例必须是可变的; Rust 不允许我们仅将某些字段标记为可变。与任何表达式一样,我们可以构造结构体的新实例作为函数体中的最后一个表达式,以隐式返回该新实例。

Listing 5-4 shows a build_user function that returns a User instance with the given email and username. The active field gets the value of true, and the sign_in_count gets a value of 1.
清单 5-4 显示了一个build_user函数,它返回具有给定电子邮件和用户名的User实例。 active字段的值为truesign_in_count的值为1

Filename: src/main.rs 文件名:src/main.rs

struct User { active: bool, username: String, email: String, sign_in_count: u64, } fn build_user(email: String, username: String) -> User { User { active: true, username: username, email: email, sign_in_count: 1, } } fn main() { let user1 = build_user( String::from("someone@example.com"), String::from("someusername123"), ); }

Listing 5-4: A build_user function that takes an email and username and returns a User instance
示例 5-4:一个build_user函数,它接受电子邮件和用户名并返回User实例

It makes sense to name the function parameters with the same name as the struct fields, but having to repeat the email and username field names and variables is a bit tedious. If the struct had more fields, repeating each name would get even more annoying. Luckily, there’s a convenient shorthand!
使用与结构字段相同的名称来命名函数参数是有意义的,但必须重复emailusername段名称和变量有点乏味。如果结构体有更多字段,重复每个名称会变得更加烦人。幸运的是,有一个方便的速记法!

Using the Field Init Shorthand
使用 Field Init 简写

Because the parameter names and the struct field names are exactly the same in Listing 5-4, we can use the field init shorthand syntax to rewrite build_user so it behaves exactly the same but doesn’t have the repetition of username and email, as shown in Listing 5-5.
由于清单 5-4 中的参数名称和结构体字段名称完全相同,因此我们可以使用field init 简写语法来重写build_user ,使其行为完全相同,但不会重复usernameemail ,如图所示如清单 5-5 所示。

Filename: src/main.rs 文件名:src/main.rs

struct User { active: bool, username: String, email: String, sign_in_count: u64, } fn build_user(email: String, username: String) -> User { User { active: true, username, email, sign_in_count: 1, } } fn main() { let user1 = build_user( String::from("someone@example.com"), String::from("someusername123"), ); }

Listing 5-5: A build_user function that uses field init shorthand because the username and email parameters have the same name as struct fields
示例 5-5: build_user函数使用 field init 简写,因为usernameemail参数与结构体字段同名

Here, we’re creating a new instance of the User struct, which has a field named email. We want to set the email field’s value to the value in the email parameter of the build_user function. Because the email field and the email parameter have the same name, we only need to write email rather than email: email.
在这里,我们创建User结构的一个新实例,其中有一个名为email字段。我们希望将email字段的值设置为build_user函数的email参数中的值。因为email字段和email参数同名,所以我们只需要写email而不是email: email

Creating Instances from Other Instances with Struct Update Syntax
使用结构更新语法从其他实例创建实例

It’s often useful to create a new instance of a struct that includes most of the values from another instance, but changes some. You can do this using struct update syntax.
创建一个结构体的新实例通常很有用,该实例包含另一个实例的大部分值,但更改了一些值。您可以使用结构更新语法来完成此操作。

First, in Listing 5-6 we show how to create a new User instance in user2 regularly, without the update syntax. We set a new value for email but otherwise use the same values from user1 that we created in Listing 5-2.
首先,在清单 5-6 中,我们展示了如何定期在user2中创建一个新的User实例,而不使用 update 语法。我们为email设置了一个新值,但在其他方面使用我们在清单 5-2 中创建的user1中的相同值。

Filename: src/main.rs 文件名:src/main.rs

struct User { active: bool, username: String, email: String, sign_in_count: u64, } fn main() { // --snip-- let user1 = User { email: String::from("someone@example.com"), username: String::from("someusername123"), active: true, sign_in_count: 1, }; let user2 = User { active: user1.active, username: user1.username, email: String::from("another@example.com"), sign_in_count: user1.sign_in_count, }; }

Listing 5-6: Creating a new User instance using all but one of the values from user1
清单 5-6:使用user1中除一个值之外的所有值创建一个新的User实例

Using struct update syntax, we can achieve the same effect with less code, as shown in Listing 5-7. The syntax .. specifies that the remaining fields not explicitly set should have the same value as the fields in the given instance.
使用struct update语法,我们可以用更少的代码实现相同的效果,如清单5-7所示。语法..指定未显式设置的其余字段应具有与给定实例中的字段相同的值。

Filename: src/main.rs 文件名:src/main.rs

struct User { active: bool, username: String, email: String, sign_in_count: u64, } fn main() { // --snip-- let user1 = User { email: String::from("someone@example.com"), username: String::from("someusername123"), active: true, sign_in_count: 1, }; let user2 = User { email: String::from("another@example.com"), ..user1 }; }

Listing 5-7: Using struct update syntax to set a new email value for a User instance but to use the rest of the values from user1
示例 5-7:使用 struct update 语法为User实例设置新的email值,但使用user1中的其余值

The code in Listing 5-7 also creates an instance in user2 that has a different value for email but has the same values for the username, active, and sign_in_count fields from user1. The ..user1 must come last to specify that any remaining fields should get their values from the corresponding fields in user1, but we can choose to specify values for as many fields as we want in any order, regardless of the order of the fields in the struct’s definition.
清单 5-7 中的代码还在user2中创建了一个实例,该实例具有不同的email值,但与user1usernameactivesign_in_count字段具有相同的值。 ..user1必须放在最后,以指定任何剩余字段应从user1中的相应字段获取其值,但我们可以选择以任何顺序为任意数量的字段指定值,而不管中字段的顺序如何结构体的定义。

Note that the struct update syntax uses = like an assignment; this is because it moves the data, just as we saw in the “Variables and Data Interacting with Move” section. In this example, we can no longer use user1 as a whole after creating user2 because the String in the username field of user1 was moved into user2. If we had given user2 new String values for both email and username, and thus only used the active and sign_in_count values from user1, then user1 would still be valid after creating user2. Both active and sign_in_count are types that implement the Copy trait, so the behavior we discussed in the “Stack-Only Data: Copy” section would apply.
请注意,结构体更新语法使用=就像赋值一样;这是因为它移动了数据,正如我们在“与移动交互的变量和数据”忽略部分中看到的那样。在这个例子中,我们在创建user2之后就不能再使用user1了,因为user1username字段中的String被移到了user2中。如果我们为user2提供了emailusername的新String值,因此只使用了user1中的activesign_in_count值,那么user1在创建user2后仍然有效。 activesign_in_count都是实现Copy特征的类型,因此我们在“仅堆栈数据:复制”忽略部分中讨论的行为将适用。

Using Tuple Structs Without Named Fields to Create Different Types
使用没有命名字段的元组结构来创建不同的类型

Rust also supports structs that look similar to tuples, called tuple structs. Tuple structs have the added meaning the struct name provides but don’t have names associated with their fields; rather, they just have the types of the fields. Tuple structs are useful when you want to give the whole tuple a name and make the tuple a different type from other tuples, and when naming each field as in a regular struct would be verbose or redundant.
Rust 还支持看起来类似于元组的结构,称为元组结构。元组结构具有结构名称提供的附加含义,但没有与其字段关联的名称;相反,它们只有字段的类型。当您想要为整个元组指定一个名称并使该元组与其他元组具有不同的类型,并且像在常规结构中那样命名每个字段时会很冗长或多余时,元组结构非常有用。

To define a tuple struct, start with the struct keyword and the struct name followed by the types in the tuple. For example, here we define and use two tuple structs named Color and Point:
要定义元组结构体,请从struct关键字和结构体名称开始,后跟元组中的类型。例如,这里我们定义并使用两个名为ColorPoint的元组结构:

Filename: src/main.rs 文件名:src/main.rs

struct Color(i32, i32, i32); struct Point(i32, i32, i32); fn main() { let black = Color(0, 0, 0); let origin = Point(0, 0, 0); }

Note that the black and origin values are different types because they’re instances of different tuple structs. Each struct you define is its own type, even though the fields within the struct might have the same types. For example, a function that takes a parameter of type Color cannot take a Point as an argument, even though both types are made up of three i32 values. Otherwise, tuple struct instances are similar to tuples in that you can destructure them into their individual pieces, and you can use a . followed by the index to access an individual value.
请注意, blackorigin值是不同的类型,因为它们是不同元组结构的实例。您定义的每个结构都是其自己的类型,即使结构中的字段可能具有相同的类型。例如,采用Color类型参数的函数不能采用Point作为参数,即使这两种类型均由三个i32值组成。否则,元组结构实例与元组类似,您可以将它们解构为单独的部分,并且可以使用.后跟索引以访问单个值。

Unit-Like Structs Without Any Fields
没有任何字段的类似单元的结构

You can also define structs that don’t have any fields! These are called unit-like structs because they behave similarly to (), the unit type that we mentioned in “The Tuple Type” section. Unit-like structs can be useful when you need to implement a trait on some type but don’t have any data that you want to store in the type itself. We’ll discuss traits in Chapter 10. Here’s an example of declaring and instantiating a unit struct named AlwaysEqual:
您还可以定义没有任何字段的结构!这些被称为类单元结构,因为它们的行为类似于我们在“元组类型”忽略部分中提到的单元类型() 。当您需要在某种类型上实现特征但没有想要在类型本身中存储任何数据时,类似单元的结构会很有用。我们将在第 10 章中讨论特征。下面是声明和实例化名为AlwaysEqual的单元结构的示例:

Filename: src/main.rs 文件名:src/main.rs

struct AlwaysEqual; fn main() { let subject = AlwaysEqual; }

To define AlwaysEqual, we use the struct keyword, the name we want, and then a semicolon. No need for curly brackets or parentheses! Then we can get an instance of AlwaysEqual in the subject variable in a similar way: using the name we defined, without any curly brackets or parentheses. Imagine that later we’ll implement behavior for this type such that every instance of AlwaysEqual is always equal to every instance of any other type, perhaps to have a known result for testing purposes. We wouldn’t need any data to implement that behavior! You’ll see in Chapter 10 how to define traits and implement them on any type, including unit-like structs.
为了定义AlwaysEqual ,我们使用struct关键字、我们想要的名称,然后是分号。不需要大括号或圆括号!然后我们可以以类似的方式在subject变量中获取AlwaysEqual的实例:使用我们定义的名称,不带任何大括号或圆括号。想象一下,稍后我们将实现此类型的行为,以便AlwaysEqual的每个实例始终等于任何其他类型的每个实例,也许为了测试目的而获得已知结果。我们不需要任何数据来实现该行为!您将在第 10 章中看到如何定义特征并在任何类型(包括类似单元的结构)上实现它们。

Ownership of Struct Data 结构数据的所有权

In the User struct definition in Listing 5-1, we used the owned String type rather than the &str string slice type. This is a deliberate choice because we want each instance of this struct to own all of its data and for that data to be valid for as long as the entire struct is valid.
在清单 5-1 的User结构体定义中,我们使用了拥有的String类型而不是&str字符串切片类型。这是一个经过深思熟虑的选择,因为我们希望该结构的每个实例都拥有其所有数据,并且只要整个结构有效,该数据就有效。

It’s also possible for structs to store references to data owned by something else, but to do so requires the use of lifetimes, a Rust feature that we’ll discuss in Chapter 10. Lifetimes ensure that the data referenced by a struct is valid for as long as the struct is. Let’s say you try to store a reference in a struct without specifying lifetimes, like the following; this won’t work:
结构体也可以存储对其他对象所拥有的数据的引用,但这样做需要使用生命周期,这是我们将在第 10 章中讨论的 Rust 功能。生命周期确保结构体引用的数据在以下情况下有效:只要结构是。假设您尝试在结构中存储引用而不指定生命周期,如下所示;这是行不通的:

Filename: src/main.rs 文件名:src/main.rs

struct User { active: bool, username: &str, email: &str, sign_in_count: u64, } fn main() { let user1 = User { active: true, username: "someusername123", email: "someone@example.com", sign_in_count: 1, }; }

The compiler will complain that it needs lifetime specifiers:
编译器会抱怨它需要生命周期说明符:

$ cargo run Compiling structs v0.1.0 (file:///projects/structs) error[E0106]: missing lifetime specifier --> src/main.rs:3:15 | 3 | username: &str, | ^ expected named lifetime parameter | help: consider introducing a named lifetime parameter | 1 ~ struct User<'a> { 2 | active: bool, 3 ~ username: &'a str, | error[E0106]: missing lifetime specifier --> src/main.rs:4:12 | 4 | email: &str, | ^ expected named lifetime parameter | help: consider introducing a named lifetime parameter | 1 ~ struct User<'a> { 2 | active: bool, 3 | username: &str, 4 ~ email: &'a str, | For more information about this error, try `rustc --explain E0106`. error: could not compile `structs` (bin "structs") due to 2 previous errors

In Chapter 10, we’ll discuss how to fix these errors so you can store references in structs, but for now, we’ll fix errors like these using owned types like String instead of references like &str.
在第 10 章中,我们将讨论如何修复这些错误,以便您可以在结构中存储引用,但现在,我们将使用String等自有类型而不是&str等引用来修复此类错误。

An Example Program Using Structs
使用结构的示例程序

To understand when we might want to use structs, let’s write a program that calculates the area of a rectangle. We’ll start by using single variables, and then refactor the program until we’re using structs instead.
为了了解何时需要使用结构体,让我们编写一个计算矩形面积的程序。我们将从使用单个变量开始,然后重构程序,直到我们使用结构体。

Let’s make a new binary project with Cargo called rectangles that will take the width and height of a rectangle specified in pixels and calculate the area of the rectangle. Listing 5-8 shows a short program with one way of doing exactly that in our project’s src/main.rs.
让我们用 Cargo 创建一个名为矩形的新二进制项目,它将采用以像素为单位指定的矩形的宽度和高度并计算矩形的面积。清单 5-8 显示了一个简短的程序,其中一种方法在我们项目的src/main.rs中实现了这一点。

Filename: src/main.rs 文件名:src/main.rs

fn main() { let width1 = 30; let height1 = 50; println!( "The area of the rectangle is {} square pixels.", area(width1, height1) ); } fn area(width: u32, height: u32) -> u32 { width * height }

Listing 5-8: Calculating the area of a rectangle specified by separate width and height variables
示例 5-8:计算由单独的宽度和高度变量指定的矩形的面积

Now, run this program using cargo run:
现在,使用cargo run运行该程序:

$ cargo run Compiling rectangles v0.1.0 (file:///projects/rectangles) Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.42s Running `target/debug/rectangles` The area of the rectangle is 1500 square pixels.

This code succeeds in figuring out the area of the rectangle by calling the area function with each dimension, but we can do more to make this code clear and readable.
这段代码通过调用每个维度的area函数成功地计算出了矩形的面积,但是我们可以做更多的事情来使这段代码清晰易读。

The issue with this code is evident in the signature of area:
该代码的问题在area的签名中很明显:

fn main() { let width1 = 30; let height1 = 50; println!( "The area of the rectangle is {} square pixels.", area(width1, height1) ); } fn area(width: u32, height: u32) -> u32 { width * height }

The area function is supposed to calculate the area of one rectangle, but the function we wrote has two parameters, and it’s not clear anywhere in our program that the parameters are related. It would be more readable and more manageable to group width and height together. We’ve already discussed one way we might do that in “The Tuple Type” section of Chapter 3: by using tuples.