mirror of
https://github.com/LCTT/TranslateProject.git
synced 2025-01-10 22:21:11 +08:00
[翻译完成][tech]: 20230515.0 ⭐️⭐️ Rust Basics Series 8 Write the Milestone Rust Program
This commit is contained in:
parent
74ab2c0975
commit
f1878d8b6d
@ -7,119 +7,123 @@
|
||||
[#]: publisher: " "
|
||||
[#]: url: " "
|
||||
|
||||
Rust Basics Series #8: Write the Milestone Rust Program
|
||||
Rust 基础系列 #8:编写里程碑 Rust 程序
|
||||
======
|
||||
|
||||
So long, we have covered a handful of fundamental topics about programming in Rust. Some of these topics are [variables, mutability, constants][1], [data types][2], [functions][3], [if-else statements][4] and [loops][5].
|
||||
到目前为止,我们已经讲解了包括 [变量、可变性、常量][1]、[数据类型][2]、[函数][3]、[if-else 语句][4] 和 [循环][5] 在内的一些关于 Rust 编程的基础知识。
|
||||
|
||||
In the final chapter of the Rust Basics series, let us now write a program in Rust that uses these topics so their real-world use can be better understood. Let's work on a _relatively simple_ program to order fruits from a fruit mart.
|
||||
在 Rust 基础系列的最后一章里,让我们现在用 Rust 编写一个程序,使用这些主题,以便更好地理解它们在现实世界中的用途。让我们来编写一个相对简单的程序,用来从水果市场订购水果。
|
||||
|
||||
### The basic structure of our program
|
||||
### 我们程序的基本结构
|
||||
|
||||
Let us first start by greeting the user and informing them about how to interact with the program.
|
||||
来让我们首先向用户问好,并告诉他们如何与程序交互。
|
||||
|
||||
```
|
||||
fn main() {
|
||||
println!("Welcome to the fruit mart!");
|
||||
println!("Please select a fruit to buy.\n");
|
||||
|
||||
println!("\nAvailable fruits to buy: Apple, Banana, Orange, Mango, Grapes");
|
||||
println!("Once you are done purchasing, type in 'quit' or 'q'.\n");
|
||||
println!("欢迎来到水果市场!");
|
||||
println!("请选择要购买的水果。\n");
|
||||
|
||||
println!("\n可以购买的水果:苹果、香蕉、橘子、芒果、葡萄");
|
||||
println!("购买完成后,请输入“quit”或“q”。\n");
|
||||
}
|
||||
```
|
||||
|
||||
### Getting user input
|
||||
### 获取用户输入
|
||||
|
||||
The above code is very simple. At the moment, you do not know what to do next because you do not know what the user wants to do next.
|
||||
上面的代码非常简单。目前,你不知道接下来该做什么,因为你不知道用户接下来想做什么。
|
||||
|
||||
So let's add code that accepts the user input and stores it somewhere to parse it later, and take the appropriate action based on the user input.
|
||||
所以让我们添加一些代码,接受用户输入并将其存储在某个地方以便稍后解析,然后根据用户输入采取适当的操作。
|
||||
|
||||
```
|
||||
use std::io;
|
||||
|
||||
fn main() {
|
||||
println!("Welcome to the fruit mart!");
|
||||
println!("Plase select a fruit to buy.\n");
|
||||
|
||||
println!("Available fruits to buy: Apple, Banana, Orange, Mango, Grapes");
|
||||
println!("Once you are done purchasing, type in 'quit' or 'q'.\n");
|
||||
|
||||
// get user input
|
||||
println!("欢迎来到水果市场!");
|
||||
println!("请选择要购买的水果。\n");
|
||||
|
||||
println!("\n可以购买的水果:苹果、香蕉、橘子、芒果、葡萄");
|
||||
println!("购买完成后,请输入“quit”或“q”。\n");
|
||||
|
||||
// 获取用户输入
|
||||
let mut user_input = String::new();
|
||||
io::stdin()
|
||||
.read_line(&mut user_input)
|
||||
.expect("Unable to read user input.");
|
||||
.expect("无法读取用户输入。");
|
||||
}
|
||||
```
|
||||
|
||||
There are three new elements that I need to tell you about. So let's take a shallow dive into each of these new elements.
|
||||
有三个新元素需要告诉你。所以让我们对这些新元素进行浅层次的探索。
|
||||
|
||||
#### 1. Understanding the 'use' keyword
|
||||
#### 1. 理解 `use` 关键字
|
||||
|
||||
On the first line of this program, you might have noticed the use (haha!) of a new keyword called `use`. The `use` keyword in Rust is similar to the `#include` directive in C/C++ and the `import` keyword in Python. Using the `use` keyword, we "import" the `io` (input output) module from the Rust standard library `std`.
|
||||
在这个程序的第一行,你可能已经注意到我们使用(哈哈!)了一个叫做 `use` 的新关键字。Rust 中的 `use` 关键字类似于 C/C++ 中的 `#include` 指令和 Python 中的 `import` 关键字。使用 `use` 关键字,我们从 Rust 标准库 `std` 中“导入”了 `io`(输入输出)模块。
|
||||
|
||||
You might be wondering why importing the _io_ module was necessary when you could use the `println` macro to _output_ something to STDOUT. Rust's standard library has a module called `prelude` that gets automatically included. The prelude module contains all the commonly used functions that a Rust programmer might need to use, like the `println` macro. (You can read more about `std::prelude` module [here][6].)
|
||||
> LCTT 译注:“使用”在原文中为“use”,与新介绍的关键字一样。
|
||||
|
||||
The `io` module from the Rust standard library `std` is necessary to accept user input. Hence, a `use` statement was added to the 1st line of this program.
|
||||
你可能会想知道为什么我们在可以使用 `println` 宏来将某些内容输出到标准输出时,导入 _io_ 模块是必要的。Rust 的标准库有一个叫做 `prelude` 的模块,它会自动被包含。该模块包含了 Rust 程序员可能需要使用的所有常用函数,比如 `println` 宏。(你可以在[这里][6]阅读更多关于 `std::prelude` 模块的内容。)
|
||||
|
||||
#### 2. Understanding the String type in Rust
|
||||
Rust 标准库 `std` 中的 `io` 模块是接受用户输入所必需的。因此,我们在程序的第一行添加了一个 `use` 语句。
|
||||
|
||||
On line 11, I create a new mutable variable called `user_input` that, as its name suggests, will be used to store the user input down the road. But on the same line, you might have noticed something new (haha, again!).
|
||||
#### 2. 理解 Rust 中的 String 类型
|
||||
|
||||
Instead of declaring an empty string using double quotes with nothing between them (`""`), I used the `String::new()` function to create a new, empty string.
|
||||
在第 11 行,我创建了一个新的可变变量 `user_input`,正如它的名字所表示的那样,它将被用来存储用户输入。但是在同一行,你可能已经注意到了一些新的东西(哈哈,又来了!)。
|
||||
|
||||
The difference between using `""` and `String::new()` is something that you will learn later in the Rust series. For now, know that, with the use of the `String::new()` function, you can create a String that is **_mutable_** and lives on the **_heap_**.
|
||||
> LCTT 译注:“新的”在原文中为“new”,在第 11 行的代码中,原作者使用了 String::new() 函数,所以此处的梗与“使用”一样,原作者使用了一个在代码中用到的单词。
|
||||
|
||||
If I had created a string with `""`, I would get something called a "String slice". The String slice's contents are on the heap too, but the string itself is **immutable**. So, even if the variable itself is mutable, the actual data stored as a string is immutable and needs to be _overwritten_ instead of modification.
|
||||
我没有使用双引号(`""`)声明一个空字符串,而是使用 `String::new()` 函数来创建一个新的空字符串。
|
||||
|
||||
#### 3. Accepting the user input
|
||||
`""` 与 `String::new()` 的区别是你将在 Rust 系列的后续文章中学习到的。现在,只需要知道,使用 `String::new()` 函数,你可以创建一个**可变**的,**位于堆上**的字符串。
|
||||
|
||||
On line 12, I call the `stdin()` function that is part of `std::io`. If I had not included the `std::io` module in the beginning of this program, this line would be `std::io::stdin()` instead of `io::stdin()`.
|
||||
如果我使用 `""` 创建了一个字符串,我将得到一个叫做“字符串切片”的东西。字符串切片的内容也位于堆上,但是字符串本身是**不可变**的。所以,即使变量本身是可变的,作为字符串存储的实际数据是不可变的,需要被**覆盖**而不是修改。
|
||||
|
||||
The `stdin()` function returns an input handle of the terminal. The `read_line()` function grabs onto that input handle and, as its name suggests, reads a line of input. This function takes in a reference to a mutable string. So, I pass in the `user_input` variable by preceding it with `&mut`, making it a mutable reference.
|
||||
#### 3. 接受用户输入
|
||||
|
||||
> ⚠️ The `read_line()` function has a _quirk_. This function stops reading the input **_after_** the user presses the Enter/Return key. Therefore, this function also records that newline character (`\n`) and a trailing newline is stored in the mutable string variable that you passed in.
|
||||
在第 12 行,我调用了 `std::io` 的 `stdin()` 函数。如果我在程序的开头没有导入 `std::io` 模块,那么这一行将是 `std::io::stdin()` 而不是 `io::stdin()`。
|
||||
|
||||
So please, either account for this trailing newline when dealing with it or remove it.
|
||||
`sdtin()` 函数返回一个终端的输入句柄。`read_line()` 函数抓住这个输入句柄,然后,正如它的名字所暗示的那样,读取一行输入。这个函数接受一个可变字符串的引用。所以,我传入了 `user_input` 变量,通过在它前面加上 `&mut`,使它成为一个可变引用。
|
||||
|
||||
### A primer on error handling in Rust
|
||||
> ⚠️ `read_line()` 函数有一个 _怪癖_。这个函数在用户按下回车键之后**_停止_**读取输入。因此,这个函数也会记录换行符(`\n`),并将一个换行符存储在你传入的可变字符串变量的结尾处。
|
||||
|
||||
Finally, there is an `expect()` function at the end of this chain. Let's divert a bit to understand why this function is called.
|
||||
所以,请在处理它时要么考虑到这个换行符,要么将它删除。
|
||||
|
||||
The `read_line()` function returns an Enum called `Result`. I will get into Enums in Rust later on but know that Enums are very powerful in Rust. This `Result` Enum returns a value that informs the programmer if an error occurred when the user input was being read.
|
||||
### Rust 中的错误处理入门
|
||||
|
||||
The `expect()` function takes this `Result` Enum and checks if the result was okay or not. If no error occurs, nothing happens. But if an error did occur, the message that I passed in (`"Unable to read user input."`) will be printed to STDERR and _the program will exit_.
|
||||
最后,在这个链的末尾有一个 `expect()` 函数。让我们稍微偏题一下,来理解为什么要调用这个函数。
|
||||
|
||||
> 📋 **All the new concepts that I have briefly touched on will be covered in a new Rust series later.**
|
||||
`read_line()` 函数返回一个叫做 `Result` 的枚举。我会在后面的文章中讲解 Rust 中的枚举,但是现在只需要知道,枚举在 Rust 中是非常强大的。这个 `Result` 枚举返回一个值,告诉程序员在读取用户输入时是否发生了错误。
|
||||
|
||||
Now that you hopefully understand these newer concepts, let's add more code to increase the functionality.
|
||||
`expect()` 函数接受这个 `Result` 枚举,并检查结果是否正常。如果没有发生错误,什么都不会发生。但是如果发生了错误,我传入的消息(`无法读取用户输入。`)将会被打印到 STDERR,_程序将会退出_。
|
||||
|
||||
### Validating user input
|
||||
> 📋 **所有我简要提及的新概念将会在后续的新 Rust 系列文章中讲解。**
|
||||
|
||||
I surely accepted the user's input but I have not validated it. In the current context, validation means that the user inputs some "command" that _we expect to handle_. At the moment, the commands are of two "categories".
|
||||
现在我希望你应该已经理解了这些新概念,让我们添加更多的代码来增加程序的功能。
|
||||
|
||||
The first category of the command that the user can input is the name of fruit that the user wishes to buy. The second command conveys that the user wants to quit the program.
|
||||
### 验证用户输入
|
||||
|
||||
So our task now is to make sure that the input from the user does not diverge from the _acceptable commands_.
|
||||
我接受了用户的输入,但是我没有对其进行验证。在当前的上下文中,验证意味着用户输入了一些“命令”,我们希望能够处理这些命令。目前,这些命令有两个“类别”。
|
||||
|
||||
第一类用户可以输入的命令是用户希望购买的水果的名称。第二个命令表示用户想要退出程序。
|
||||
|
||||
我们的任务现在是确保用户输入不会偏离 _可接受的命令_。
|
||||
|
||||
```
|
||||
use std::io;
|
||||
|
||||
fn main() {
|
||||
println!("Welcome to the fruit mart!");
|
||||
println!("Plase select a fruit to buy.\n");
|
||||
|
||||
println!("Available fruits to buy: Apple, Banana, Orange, Mango, Grapes");
|
||||
println!("Once you are done purchasing, type in 'quit' or 'q'.\n");
|
||||
|
||||
// get user input
|
||||
println!("欢迎来到水果市场!");
|
||||
println!("请选择要购买的水果。\n");
|
||||
|
||||
println!("\n可以购买的水果:苹果、香蕉、橘子、芒果、葡萄");
|
||||
println!("购买完成后,请输入“quit”或“q”。\n");
|
||||
|
||||
// 获取用户输入
|
||||
let mut user_input = String::new();
|
||||
io::stdin()
|
||||
.read_line(&mut user_input)
|
||||
.expect("Unable to read user input.");
|
||||
|
||||
// validate user input
|
||||
let valid_inputs = ["apple", "banana", "orange", "mango", "grapes", "quit", "q"];
|
||||
.expect("无法读取用户输入。");
|
||||
|
||||
// 验证用户输入
|
||||
let valid_inputs = ["苹果", "香蕉", "橘子", "芒果", "葡萄", "quit", "q"];
|
||||
user_input = user_input.trim().to_lowercase();
|
||||
let mut input_error = true;
|
||||
for input in valid_inputs {
|
||||
@ -131,42 +135,42 @@ fn main() {
|
||||
}
|
||||
```
|
||||
|
||||
To make validation easier, I created an array of string slices called `valid_inputs` (on line 17). This array contains the names of all the fruits that are available for purchase, along with the string slices `q` and `quit` to let the user convey if they wish to quit.
|
||||
要使验证更容易,我创建了一个叫做 `valid_inputs` 的字符串切片数组(第 17 行)。这个数组包含了所有可以购买的水果的名称,以及字符串切片 `q` 和 `quit`,让用户可以传达他们是否希望退出。
|
||||
|
||||
The user may not know how we expect the input to be. The user may type "Apple" or "apple" or "APPLE" to tell that they intend to purchase Apples. It is our job to handle this correctly.
|
||||
用户可能不知道我们希望输入是什么样的。用户可能会输入“Apple”、“apple”或 “APPLE” 来表示他们想要购买苹果。我们的工作是正确处理这些输入。
|
||||
|
||||
On line 18, I trim the trailing newline from the `user_input` string by calling the `trim()` function on it. And to handle the previous problem, I convert all the characters to lowercase with the `to_lowercase()` function so that "Apple", "apple" and "APPLE" all end up as "apple".
|
||||
在第 18 行,我通过调用 `trim()` 函数从 `user_input` 字符串中删除了尾部的换行符。为了处理上面提到的问题,我使用 `to_lowercase()` 函数将所有字符转换为小写,这样 “Apple”、“apple” 和 “APPLE” 都会变成 “apple”。
|
||||
|
||||
Now on line 19, I create a mutable boolean variable called `input_error` with the initial value of `true`. Later on line 20, I create a `for` loop that iterates over all the elements (string slices) of the `valid_inputs` array and stores the iterated pattern inside the `input` variable.
|
||||
现在,来看第 19 行,我创建了一个名为 `input_error` 的可变布尔变量,初始值为 `true`。稍后在第 20 行,我创建了一个 `for` 循环,它遍历了 `valid_inputs` 数组的所有元素(字符串切片),并将迭代的模式存储在 `input` 变量中。
|
||||
|
||||
Inside the loop, I check if the user input is equal to one of the valid strings, and if it is, I set the value of `input_error` boolean to `false` and break out of the for loop.
|
||||
在循环内部,我检查用户输入是否等于其中一个有效字符串,如果是,我将 `input_error` 布尔值的值设置为 `false`,并跳出 `for` 循环。
|
||||
|
||||
### Dealing with invalid input
|
||||
### 处理无效输入
|
||||
|
||||
Now is time to deal with an invalid input. This can be done by moving some of the code inside an infinite loop and _continuing_ said infinite loop if the user gives an invalid input.
|
||||
现在是时候处理无效输入了。这可以通过将一些代码移动到无限循环中来完成,如果用户给出无效输入,则 _继续_ 该无限循环。
|
||||
|
||||
```
|
||||
use std::io;
|
||||
|
||||
fn main() {
|
||||
println!("Welcome to the fruit mart!");
|
||||
println!("Plase select a fruit to buy.\n");
|
||||
|
||||
let valid_inputs = ["apple", "banana", "orange", "mango", "grapes", "quit", "q"];
|
||||
|
||||
println!("欢迎来到水果市场!");
|
||||
println!("请选择要购买的水果。\n");
|
||||
|
||||
let valid_inputs = ["苹果", "香蕉", "橘子", "芒果", "葡萄", "quit", "q"];
|
||||
|
||||
'mart: loop {
|
||||
let mut user_input = String::new();
|
||||
|
||||
println!("\nAvailable fruits to buy: Apple, Banana, Orange, Mango, Grapes");
|
||||
println!("Once you are done purchasing, type in 'quit' or 'q'.\n");
|
||||
println!("\n可以购买的水果:苹果、香蕉、橘子、芒果、葡萄");
|
||||
println!("购买完成后,请输入“quit”或“q”。\n");
|
||||
|
||||
// get user input
|
||||
// 读取用户输入
|
||||
io::stdin()
|
||||
.read_line(&mut user_input)
|
||||
.expect("Unable to read user input.");
|
||||
.expect("无法读取用户输入。");
|
||||
user_input = user_input.trim().to_lowercase();
|
||||
|
||||
// validate user input
|
||||
// 验证用户输入
|
||||
let mut input_error = true;
|
||||
for input in valid_inputs {
|
||||
if input == user_input {
|
||||
@ -175,46 +179,46 @@ fn main() {
|
||||
}
|
||||
}
|
||||
|
||||
// handle invalid input
|
||||
// 处理无效输入
|
||||
if input_error {
|
||||
println!("ERROR: please enter a valid input");
|
||||
println!("错误: 请输入有效的输入");
|
||||
continue 'mart;
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Here, I moved some of the code inside the loop and re-structured the code a bit to better deal with this introduction of the loop. Inside the loop, on line 31, I `continue` the `mart` loop if the user entered an invalid string.
|
||||
这里,我将一些代码移动到了循环内部,并重新组织了一下代码,以便更好地处理循环的引入。在循环内部,第 31 行,如果用户输入了一个无效的字符串,我将 `continue` `mart` 循环。
|
||||
|
||||
### Reacting to user's input
|
||||
### 对用户输入做出反应
|
||||
|
||||
Now that everything else is handled, time to actually write code about purchasing fruits from the fruit market and quit when the user wishes.
|
||||
现在,所有其他的状况都已经处理好了,是时候写一些代码来让用户从水果市场购买水果了,当用户希望退出时,程序也会退出。
|
||||
|
||||
Since you also know which fruit the user chose, let's ask how much they intend to purchase and inform them about the format of entering the quantity.
|
||||
因为你也知道用户选择了哪种水果,所以让我们问一下他们打算购买多少,并告诉他们输入数量的格式。
|
||||
|
||||
```
|
||||
use std::io;
|
||||
|
||||
fn main() {
|
||||
println!("Welcome to the fruit mart!");
|
||||
println!("Plase select a fruit to buy.\n");
|
||||
|
||||
let valid_inputs = ["apple", "banana", "orange", "mango", "grapes", "quit", "q"];
|
||||
|
||||
println!("欢迎来到水果市场!");
|
||||
println!("请选择要购买的水果。\n");
|
||||
|
||||
let valid_inputs = ["苹果", "香蕉", "橘子", "芒果", "葡萄", "quit", "q"];
|
||||
|
||||
'mart: loop {
|
||||
let mut user_input = String::new();
|
||||
let mut quantity = String::new();
|
||||
|
||||
println!("\nAvailable fruits to buy: Apple, Banana, Orange, Mango, Grapes");
|
||||
println!("Once you are done purchasing, type in 'quit' or 'q'.\n");
|
||||
println!("\n可以购买的水果:苹果、香蕉、橘子、芒果、葡萄");
|
||||
println!("购买完成后,请输入“quit”或“q”。\n");
|
||||
|
||||
// get user input
|
||||
// 读取用户输入
|
||||
io::stdin()
|
||||
.read_line(&mut user_input)
|
||||
.expect("Unable to read user input.");
|
||||
.expect("无法读取用户输入。");
|
||||
user_input = user_input.trim().to_lowercase();
|
||||
|
||||
// validate user input
|
||||
// 验证用户输入
|
||||
let mut input_error = true;
|
||||
for input in valid_inputs {
|
||||
if input == user_input {
|
||||
@ -223,67 +227,67 @@ fn main() {
|
||||
}
|
||||
}
|
||||
|
||||
// handle invalid input
|
||||
// 处理无效输入
|
||||
if input_error {
|
||||
println!("ERROR: please enter a valid input");
|
||||
println!("错误: 请输入有效的输入");
|
||||
continue 'mart;
|
||||
}
|
||||
|
||||
// quit if user wants to
|
||||
|
||||
// 如果用户想要退出,就退出
|
||||
if user_input == "q" || user_input == "quit" {
|
||||
break 'mart;
|
||||
}
|
||||
|
||||
// get quantity
|
||||
// 获取数量
|
||||
println!(
|
||||
"\nYou choose to buy \"{}\". Please enter the quantity in Kilograms.
|
||||
(Quantity of 1Kg 500g should be entered as '1.5'.)",
|
||||
"\n你选择购买的水果是 \"{}\"。请输入以千克为单位的数量。
|
||||
(1 千克 500 克的数量应该输入为 '1.5'。)",
|
||||
user_input
|
||||
);
|
||||
io::stdin()
|
||||
.read_line(&mut quantity)
|
||||
.expect("Unable to read user input.");
|
||||
.expect("无法读取用户输入。");
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
On line 11, I declare another mutable variable with an empty string and on line 48, I accept input from the user, but this time the quantity of said fruit that the user intends to buy.
|
||||
在第 11 行,我声明了另一个可变变量,它的值是一个空字符串,在第 48 行,我接受了用户的输入,但是这次是用户打算购买的水果的数量。
|
||||
|
||||
#### Parsing the quantity
|
||||
#### 解析数量
|
||||
|
||||
I just added code that takes in quantity in a known format, but that data is stored as a string. I need to extract the float out of that. Lucky for us, it can be done with the `parse()` method.
|
||||
我刚刚增加了一些代码,以已知的格式接受数量,但是这些数据被存储为字符串。我需要从中提取出浮点数。幸运的是,这可以通过 `parse()` 方法来完成。
|
||||
|
||||
Just like the `read_line()` method, the `parse()` method returns the `Result` Enum. The reason why the `parse()` method returns the `Result` Enum can be easily understood with what we are trying to achieve.
|
||||
就像 `read_line()` 方法一样,`parse()` 方法返回一个 `Result` 枚举。`parse()` 方法返回 `Result` 枚举的原因可以通过我们试图实现的内容来轻松理解。
|
||||
|
||||
I am accepting a string from users and trying to convert it to a float. A float has two possible values in it. One is the floating point itself and the second is a decimal number.
|
||||
我正在接受用户的字符串,并尝试将其转换为浮点数。浮点数有两个可能的值。一个是浮点数本身,另一个是小数。
|
||||
|
||||
While a String can have alphabets, a float does not. So, if the user entered something _other_ than the [optional] floating point and the decimal number(s), the `parse()` function will return an error.
|
||||
字符串可以包含字母,但是浮点数不行。所以,如果用户输入的不是浮点数和小数,`parse()` 函数将会返回一个错误。
|
||||
|
||||
Hence, this error needs to be handled too. We will use the `expect()` function to deal with this.
|
||||
因此,这个错误也需要处理。我们将使用 `expect()` 函数来处理这个错误。
|
||||
|
||||
```
|
||||
use std::io;
|
||||
|
||||
fn main() {
|
||||
println!("Welcome to the fruit mart!");
|
||||
println!("Plase select a fruit to buy.\n");
|
||||
|
||||
let valid_inputs = ["apple", "banana", "orange", "mango", "grapes", "quit", "q"];
|
||||
|
||||
println!("欢迎来到水果市场!");
|
||||
println!("请选择要购买的水果。\n");
|
||||
|
||||
let valid_inputs = ["苹果", "香蕉", "橘子", "芒果", "葡萄", "quit", "q"];
|
||||
|
||||
'mart: loop {
|
||||
let mut user_input = String::new();
|
||||
let mut quantity = String::new();
|
||||
|
||||
println!("\nAvailable fruits to buy: Apple, Banana, Orange, Mango, Grapes");
|
||||
println!("Once you are done purchasing, type in 'quit' or 'q'.\n");
|
||||
println!("\n可以购买的水果:苹果、香蕉、橘子、芒果、葡萄");
|
||||
println!("购买完成后,请输入“quit”或“q”。\n");
|
||||
|
||||
// get user input
|
||||
// 读取用户输入
|
||||
io::stdin()
|
||||
.read_line(&mut user_input)
|
||||
.expect("Unable to read user input.");
|
||||
.expect("无法读取用户输入。");
|
||||
user_input = user_input.trim().to_lowercase();
|
||||
|
||||
// validate user input
|
||||
// 验证用户输入
|
||||
let mut input_error = true;
|
||||
for input in valid_inputs {
|
||||
if input == user_input {
|
||||
@ -292,49 +296,49 @@ fn main() {
|
||||
}
|
||||
}
|
||||
|
||||
// handle invalid input
|
||||
// 处理无效输入
|
||||
if input_error {
|
||||
println!("ERROR: please enter a valid input");
|
||||
println!("错误: 请输入有效的输入");
|
||||
continue 'mart;
|
||||
}
|
||||
|
||||
// quit if user wants to
|
||||
|
||||
// 如果用户想要退出,就退出
|
||||
if user_input == "q" || user_input == "quit" {
|
||||
break 'mart;
|
||||
}
|
||||
|
||||
// get quantity
|
||||
// 获取数量
|
||||
println!(
|
||||
"\nYou choose to buy \"{}\". Please enter the quantity in Kilograms.
|
||||
(Quantity of 1Kg 500g should be entered as '1.5'.)",
|
||||
"\n你选择购买的水果是 \"{}\"。请输入以千克为单位的数量。
|
||||
(1 千克 500 克的数量应该输入为 '1.5'。)",
|
||||
user_input
|
||||
);
|
||||
io::stdin()
|
||||
.read_line(&mut quantity)
|
||||
.expect("Unable to read user input.");
|
||||
.expect("无法读取用户输入。");
|
||||
|
||||
let quantity: f64 = quantity
|
||||
.trim()
|
||||
.parse()
|
||||
.expect("Please enter a valid quantity.");
|
||||
.expect("请输入有效的数量。");
|
||||
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
As you can see, I store the parsed float in the variable `quantity` by making use of variable shadowing. To inform the `parse()` function that the intention is to parse the string into `f64`, I manually annotate the type of the variable `quantity` as `f64`.
|
||||
如你所见,我通过变量遮蔽将解析后的浮点数存储在变量 `quantity` 中。为了告诉 `parse()` 函数,我的意图是将字符串解析为 `f64`,我手动将变量 `quantity` 的类型注释为 `f64`。
|
||||
|
||||
Now, the `parse()` function will parse the String and return a `f64` or an error, that the `expect()` function will deal with.
|
||||
现在,`parse()` 函数将会解析字符串并返回一个 `f64` 或者一个错误,`expect()` 函数将会处理这个错误。
|
||||
|
||||
### Calculating the price + final touch ups
|
||||
### 计算价格 + 最后的修饰
|
||||
|
||||
Now that we know which fruit the user wants to buy and its quantity, it is time to perform those calculations now and let the user know about the results/total.
|
||||
现在我们知道了用户想要购买的水果及其数量,现在是时候进行计算了,并让用户知道结果/总价了。
|
||||
|
||||
For the sake of realness, I will have two prices for each fruit. The first price is the retail price, which we pay to fruit vendors when we buy in small quantities. The second price for fruit will be the wholesale price, when someone buys fruits in bulk.
|
||||
为了真实起见,我将为每种水果设置两个价格。第一个价格是零售价,我们在购买少量水果时向水果供应商支付的价格。水果的第二个价格是当有人批量购买水果时支付的批发价。
|
||||
|
||||
The wholesale price will be determined if the order is greater than the minimum order quantity to be considered as a wholesale purchase. This minimum order quantity varies for every fruit. The prices for each fruit will be in Rupees per Kilogram.
|
||||
批发价将会在订单数量大于被认为是批发购买的最低订单数量时确定。这个最低订单数量对于每种水果都是不同的。每种水果的价格都是每千克多少卢比。
|
||||
|
||||
With that logic in mind, down below is the program in its final form.
|
||||
想好了逻辑,下面是最终的程序。
|
||||
|
||||
```
|
||||
use std::io;
|
||||
@ -355,26 +359,25 @@ const GRAPES_RETAIL_PER_KG: f64 = 120.0;
|
||||
const GRAPES_WHOLESALE_PER_KG: f64 = 100.0;
|
||||
|
||||
fn main() {
|
||||
println!("Welcome to the fruit mart!");
|
||||
println!("Please select a fruit to buy.\n");
|
||||
println!("欢迎来到水果市场!");
|
||||
println!("请选择要购买的水果。\n");
|
||||
|
||||
let mut total: f64 = 0.0;
|
||||
let valid_inputs = ["apple", "banana", "orange", "mango", "grapes", "quit", "q"];
|
||||
let valid_inputs = ["苹果", "香蕉", "橘子", "芒果", "葡萄", "quit", "q"];
|
||||
|
||||
'mart: loop {
|
||||
let mut user_input = String::new();
|
||||
let mut quantity = String::new();
|
||||
|
||||
println!("\nAvailable fruits to buy: Apple, Banana, Orange, Mango, Grapes");
|
||||
println!("Once you are done purchasing, type in 'quit' or 'q'.\n");
|
||||
println!("\n可以购买的水果:苹果、香蕉、橘子、芒果、葡萄");
|
||||
println!("购买完成后,请输入“quit”或“q”。\n");
|
||||
|
||||
// get user input
|
||||
// 读取用户输入
|
||||
io::stdin()
|
||||
.read_line(&mut user_input)
|
||||
.expect("Unable to read user input.");
|
||||
.expect("无法读取用户输入。");
|
||||
user_input = user_input.trim().to_lowercase();
|
||||
|
||||
// validate user input
|
||||
// 验证用户输入
|
||||
let mut input_error = true;
|
||||
for input in valid_inputs {
|
||||
if input == user_input {
|
||||
@ -383,35 +386,36 @@ fn main() {
|
||||
}
|
||||
}
|
||||
|
||||
// handle invalid input
|
||||
// 处理无效输入
|
||||
if input_error {
|
||||
println!("ERROR: please enter a valid input");
|
||||
println!("错误: 请输入有效的输入");
|
||||
continue 'mart;
|
||||
}
|
||||
|
||||
// quit if user wants to
|
||||
// 如果用户想要退出,就退出
|
||||
if user_input == "q" || user_input == "quit" {
|
||||
break 'mart;
|
||||
}
|
||||
|
||||
// get quantity
|
||||
// 获取数量
|
||||
println!(
|
||||
"\nYou choose to buy \"{}\". Please enter the quantity in Kilograms.
|
||||
(Quantity of 1Kg 500g should be entered as '1.5'.)",
|
||||
"\n你选择购买的水果是 \"{}\"。请输入以千克为单位的数量。
|
||||
(1 千克 500 克的数量应该输入为 '1.5'。)",
|
||||
user_input
|
||||
);
|
||||
io::stdin()
|
||||
.read_line(&mut quantity)
|
||||
.expect("Unable to read user input.");
|
||||
.expect("无法读取用户输入。");
|
||||
|
||||
let quantity: f64 = quantity
|
||||
.trim()
|
||||
.parse()
|
||||
.expect("Please enter a valid quantity.");
|
||||
.expect("请输入有效的数量。");
|
||||
|
||||
total += calc_price(quantity, user_input);
|
||||
}
|
||||
|
||||
println!("\n\nYour total is {} Rupees.", total);
|
||||
println!("\n\n总价是 {} 卢比。", total);
|
||||
}
|
||||
|
||||
fn calc_price(quantity: f64, fruit: String) -> f64 {
|
||||
@ -469,37 +473,37 @@ fn price_grapes(quantity: f64) -> f64 {
|
||||
}
|
||||
```
|
||||
|
||||
Compared to the previous iteration, I made some changes...
|
||||
对比之前的版本,我做了一些改动……
|
||||
|
||||
The fruit prices may fluctuate, but for the lifecycle of our program, these prices will not fluctuate. So I store the retail and wholesale prices of each fruit in constants. I define these constants outside the `main()` functions (i.e. globally) because I will not calculate the prices for each fruit inside the `main()` function. These constants are declared as `f64` because they will be multiplied with `quantity` which is `f64`. Recall, Rust doesn't have implicit type casting ;)
|
||||
水果的价格可能会波动,但是在我们程序的生命周期内,这些价格不会波动。所以我将每种水果的零售价和批发价存储在常量中。我将这些常量定义在 `main()` 函数之外(即全局常量),因为我不会在 `main()` 函数内计算每种水果的价格。这些常量被声明为 `f64`,因为它们将与 `quantity` 相乘,而 `quantity` 是 `f64`。记住,Rust 没有隐式类型转换 ;)
|
||||
|
||||
After storing the fruit name and the quantity that the user wants to purchase, the `calc_price()` function is called to calculate the price of said fruit in the user provided quantity. This function takes in the fruit name and the quantity as its parameters and returns the price as `f64`.
|
||||
当水果名称和用户想要购买的数量被存下来之后,`calc_price()` 函数被调用来计算用户指定数量的水果的价格。这个函数接受水果名称和数量作为参数,并将价格作为 `f64` 返回。
|
||||
|
||||
Looking inside the `calc_price()` function, it is what many people call a wrapper function. It is called a wrapper function because it calls other functions to do its dirty laundry.
|
||||
当你看到 `calc_price()` 函数的内部时,你会发现它是许多人所说的包装函数。它被称为包装函数,因为它调用其他函数来完成它的脏活。
|
||||
|
||||
Since each fruit has a different minimum order quantity to be considered as a wholesale purchase, to ensure that the code can be maintained easily in the future, the actual price calculation for each fruit is split in separate functions for each individual fruit.
|
||||
因为每种水果都有不同的最低订单数量,才能被认为是批发购买,为了确保代码在未来可以轻松维护,每种水果都有单独的函数负责计算价格。
|
||||
|
||||
So, all that the `calc_price()` function does is to determine which fruit was chosen and call the respective function for chosen fruit. These fruit-specific functions accept only one argument: quantity. And these fruit-specific functions return the price as `f64`.
|
||||
所以,`calc_price()` 函数所做的就是确定用户选择了哪种水果,并调用相应的函数来计算所选水果的价格。这些水果特定的函数只接受一个参数:数量。这些水果特定的函数将价格作为 `f64` 返回。
|
||||
|
||||
Now, `price_*()` functions do only one thing. They check if the order quantity is greater than the minimum order quantity to be considered as a wholesale purchase for said fruit. If it is such, `quantity` is multiplied by the fruit's wholesale price per Kilogram. Otherwise, `quantity` is multiplied by the fruit's retail price per Kilogram.
|
||||
现在,`price_*()` 函数只做一件事。它们检查订单数量是否大于被认为是批发购买的最低订单数量。如果是这样,`quantity` 将会乘以水果的每千克批发价格。否则,`quantity` 将会乘以水果的每千克零售价格。
|
||||
|
||||
Since the line with multiplication does not have a semi-colon at the end, the function returns the resulting product.
|
||||
由于乘法行末尾没有分号,所以函数返回乘积。
|
||||
|
||||
If you look closely at the function calls of the fruit-specific functions in the `calc_price()` function, these function calls do not have a semi-colon at the end. Meaning, the value returned by the `price_*()` functions will be returned by the `calc_price()` function to its caller.
|
||||
如果你仔细看看 `calc_price()` 函数中水果特定函数的函数调用,这些函数调用在末尾没有分号。这意味着,`price_*()` 函数返回的值将会被 `calc_price()` 函数返回给它的调用者。
|
||||
|
||||
And there is only one caller for `calc_price()` function. This is at the end of the `mart` loop where the returned value from this function is what is used to increment the value of `total`.
|
||||
而且 `calc_price()` 函数只有一个调用者。这个调用者在 `mart` 循环的末尾,这个调用者使用这个函数返回的值来增加 `total` 的值。
|
||||
|
||||
Finally, when the `mart` loop ends (when the user inputs `q` or `quit`), the value stored inside the variable `total` gets printed to the screen and the user is informed about the price he/she has to pay.
|
||||
最终,当 `mart` 循环结束(当用户输入 `q` 或 `quit` 时),存储在变量 `total` 中的值将会被打印到屏幕上,并且用户将会被告知他/她需要支付的价格。
|
||||
|
||||
### Conclusion
|
||||
### 总结
|
||||
|
||||
With this post, I have used all the previously explained topics about the Rust programming language to create a simple program that still somewhat demonstrates a real-world problem.
|
||||
这篇文章中,我使用了之前讲解的 Rust 编程语言的所有主题来创建一个简单的程序,这个程序仍然在某种程度上展示了一个现实世界的问题。
|
||||
|
||||
Now, the code that I wrote can definitely be written in a more idiomatic way that best uses Rust's loved features but I haven't covered them yet!
|
||||
现在,我写的代码肯定可以用一种更符合编程习惯的方式来写,这种方式最好地使用了 Rust 的喜爱特性,但是我还没有讲到它们!
|
||||
|
||||
So stay tuned for follow-up **Take Rust to The Next Level series** and learn more of the Rust programming language!
|
||||
所以,敬请关注后续的 **将 Rust 带入下一个层次** 系列,并学习更多 Rust 编程语言的内容!
|
||||
|
||||
The Rust Basics series concludes here. I welcome your feedback.
|
||||
Rust 基础系列到此结束。欢迎你的反馈。
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
|
||||
@ -514,9 +518,9 @@ via: https://itsfoss.com/milestone-rust-program/
|
||||
|
||||
[a]: https://itsfoss.com/author/pratham/
|
||||
[b]: https://github.com/lkxed/
|
||||
[1]: https://itsfoss.com/rust-variables
|
||||
[2]: https://itsfoss.com/rust-data-types-01
|
||||
[3]: https://itsfoss.com/rust-functions
|
||||
[4]: https://itsfoss.com/rust-conditional-statements
|
||||
[5]: https://itsfoss.com/rust-loops
|
||||
[1]: https://linux.cn/article-15771-1.html
|
||||
[2]: https://linux.cn/article-15811-1.html
|
||||
[3]: https://linux.cn/article-15855-1.html
|
||||
[4]: https://linux.cn/article-15896-1.html
|
||||
[5]: https://linux.cn/article-15908-1.html
|
||||
[6]: https://doc.rust-lang.org/std/prelude/index.html?ref=itsfoss.com
|
||||
|
Loading…
Reference in New Issue
Block a user