Rust 基础系列 #8：编写里程碑 Rust 程序
Rust
![](/data/attachment/album/202306/23/114333ni9fkzpykmz37zpu.jpg)


到目前为止，我们已经讲解了包括 [变量、可变性、常量](/article-15771-1.html)、[数据类型](/article-15811-1.html)、[函数](/article-15855-1.html)、[if-else 语句](/article-15896-1.html) 和 [循环](/article-15908-1.html) 在内的一些关于 Rust 编程的基础知识。


在 Rust 基础系列的最后一章里，让我们现在用 Rust 编写一个程序，使用这些主题，以便更好地理解它们在现实世界中的用途。让我们来编写一个相对简单的程序，用来从水果市场订购水果。


### 我们程序的基本结构


来让我们首先向用户问好，并告诉他们如何与程序交互。



```
fn main() {
    println!("欢迎来到水果市场！");
    println!("请选择要购买的水果。\n");

    println!("\n可以购买的水果：苹果、香蕉、橘子、芒果、葡萄");
    println!("购买完成后，请输入“quit”或“q”。\n");
}

```

### 获取用户输入


上面的代码非常简单。目前，你不知道接下来该做什么，因为你不知道用户接下来想做什么。


所以让我们添加一些代码，接受用户输入并将其存储在某个地方以便稍后解析，然后根据用户输入采取适当的操作。



```
use std::io;

fn main() {
    println!("欢迎来到水果市场！");
    println!("请选择要购买的水果。\n");

    println!("\n可以购买的水果：苹果、香蕉、橘子、芒果、葡萄");
    println!("购买完成后，请输入“quit”或“q”。\n");

    // 获取用户输入
    let mut user_input = String::new();
    io::stdin()
        .read_line(&mut user_input)
        .expect("无法读取用户输入。");
}

```

有三个新元素需要告诉你。所以让我们对这些新元素进行浅层次的探索。


#### 1. 理解 use 关键字


在这个程序的第一行，你可能已经注意到我们“使用”（哈哈！）了一个叫做 `use` 的新关键字。Rust 中的 `use` 关键字类似于 C/C++ 中的 `#include` 指令和 Python 中的 `import` 关键字。使用 `use` 关键字，我们从 Rust 标准库 `std` 中“导入”了 `io`（输入输出）模块。



> 
> LCTT 译注：“使用”在原文中为“use”，与新介绍的关键字一样。
> 
> 
> 


你可能会想知道为什么我们在可以使用 `println` 宏来将某些内容输出到标准输出时，导入 `io` 模块是必要的。Rust 的标准库有一个叫做 `prelude` 的模块，它会自动被包含。该模块包含了 Rust 程序员可能需要使用的所有常用函数，比如 `println` 宏。（你可以在 [这里](https://doc.rust-lang.org/std/prelude/index.html) 阅读更多关于 `std::prelude` 模块的内容。）


Rust 标准库 `std` 中的 `io` 模块是接受用户输入所必需的。因此，我们在程序的第一行添加了一个 `use` 语句。


#### 2. 理解 Rust 中的 String 类型


在第 11 行，我创建了一个新的可变变量 `user_input`，正如它的名字所表示的那样，它将被用来存储用户输入。但是在同一行，你可能已经注意到了一些“新的”东西（哈哈，又来了！）。



> 
> LCTT 译注：“新的”在原文中为“new”，在第 11 行的代码中，原作者使用了 `String::new()` 函数，所以此处的梗与“使用”一样，原作者使用了一个在代码中用到的单词。
> 
> 
> 


我没有使用双引号（`""`）声明一个空字符串，而是使用 `String::new()` 函数来创建一个新的空字符串。


`""` 与 `String::new()` 的区别是你将在 Rust 系列的后续文章中学习到的。现在，只需要知道，使用 `String::new()` 函数，你可以创建一个**可变**的，**位于堆上**的字符串。


如果我使用 `""` 创建了一个字符串，我将得到一个叫做“字符串切片”的东西。字符串切片的内容也位于堆上，但是字符串本身是**不可变**的。所以，即使变量本身是可变的，作为字符串存储的实际数据是不可变的，需要被**覆盖**而不是修改。


#### 3. 接受用户输入


在第 12 行，我调用了 `std::io` 的 `stdin()` 函数。如果我在程序的开头没有导入 `std::io` 模块，那么这一行将是 `std::io::stdin()` 而不是 `io::stdin()`。


`sdtin()` 函数返回一个终端的输入句柄。`read_line()` 函数抓住这个输入句柄，然后，正如它的名字所暗示的那样，读取一行输入。这个函数接受一个可变字符串的引用。所以，我传入了 `user_input` 变量，通过在它前面加上 `&mut`，使它成为一个可变引用。



> 
> ⚠️ `read_line()` 函数有一个 *怪癖*。这个函数在用户按下回车键之后 **停止** 读取输入。因此，这个函数也会记录换行符（`\n`），并将一个换行符存储在你传入的可变字符串变量的结尾处。
> 
> 
> 


所以，请在处理它时要么考虑到这个换行符，要么将它删除。


### Rust 中的错误处理入门


最后，在这个链的末尾有一个 `expect()` 函数。让我们稍微偏题一下，来理解为什么要调用这个函数。


`read_line()` 函数返回一个叫做 `Result` 的枚举。我会在后面的文章中讲解 Rust 中的枚举，但是现在只需要知道，枚举在 Rust 中是非常强大的。这个 `Result` 枚举返回一个值，告诉程序员在读取用户输入时是否发生了错误。


`expect()` 函数接受这个 `Result` 枚举，并检查结果是否正常。如果没有发生错误，什么都不会发生。但是如果发生了错误，我传入的消息（`无法读取用户输入。`）将会被打印到 STDERR，*程序将会退出*。



> 
> ? **所有我简要提及的新概念将会在后续的新 Rust 系列文章中讲解。**
> 
> 
> 


现在我希望你应该已经理解了这些新概念，让我们添加更多的代码来增加程序的功能。


### 验证用户输入


我接受了用户的输入，但是我没有对其进行验证。在当前的上下文中，验证意味着用户输入了一些“命令”，我们希望能够处理这些命令。目前，这些命令有两个“类别”。


第一类用户可以输入的命令是用户希望购买的水果的名称。第二个命令表示用户想要退出程序。


我们的任务现在是确保用户输入不会偏离 *可接受的命令*。



```
use std::io;

fn main() {
    println!("欢迎来到水果市场！");
    println!("请选择要购买的水果。\n");

    println!("\n可以购买的水果：苹果、香蕉、橘子、芒果、葡萄");
    println!("购买完成后，请输入“quit”或“q”。\n");

    // 获取用户输入
    let mut user_input = String::new();
    io::stdin()
        .read_line(&mut user_input)
        .expect("无法读取用户输入。");

    // 验证用户输入
    let valid_inputs = ["苹果", "香蕉", "橘子", "芒果", "葡萄", "quit", "q"];
    user_input = user_input.trim().to_lowercase();
    let mut input_error = true;
    for input in valid_inputs {
        if input == user_input {
            input_error = false;
            break;
        }
    }
}

```

要使验证更容易，我创建了一个叫做 `valid_inputs` 的字符串切片数组（第 17 行）。这个数组包含了所有可以购买的水果的名称，以及字符串切片 `q` 和 `quit`，让用户可以传达他们是否希望退出。


用户可能不知道我们希望输入是什么样的。用户可能会输入“Apple”、“apple”或 “APPLE” 来表示他们想要购买苹果。我们的工作是正确处理这些输入。


在第 18 行，我通过调用 `trim()` 函数从 `user_input` 字符串中删除了尾部的换行符。为了处理上面提到的问题，我使用 `to_lowercase()` 函数将所有字符转换为小写，这样 “Apple”、“apple” 和 “APPLE” 都会变成 “apple”。


现在，来看第 19 行，我创建了一个名为 `input_error` 的可变布尔变量，初始值为 `true`。稍后在第 20 行，我创建了一个 `for` 循环，它遍历了 `valid_inputs` 数组的所有元素（字符串切片），并将迭代的模式存储在 `input` 变量中。


在循环内部，我检查用户输入是否等于其中一个有效字符串，如果是，我将 `input_error` 布尔值的值设置为 `false`，并跳出 `for` 循环。


### 处理无效输入


现在是时候处理无效输入了。这可以通过将一些代码移动到无限循环中来完成，如果用户给出无效输入，则 *继续* 该无限循环。



```
use std::io;

fn main() {
    println!("欢迎来到水果市场！");
    println!("请选择要购买的水果。\n");

    let valid_inputs = ["苹果", "香蕉", "橘子", "芒果", "葡萄", "quit", "q"];

    'mart: loop {
        let mut user_input = String::new();

        println!("\n可以购买的水果：苹果、香蕉、橘子、芒果、葡萄");
        println!("购买完成后，请输入“quit”或“q”。\n");

        // 读取用户输入
        io::stdin()
            .read_line(&mut user_input)
            .expect("无法读取用户输入。");
        user_input = user_input.trim().to_lowercase();

        // 验证用户输入
        let mut input_error = true;
        for input in valid_inputs {
            if input == user_input {
                input_error = false;
                break;
            }
        }

        // 处理无效输入
        if input_error {
            println!("错误: 请输入有效的输入");
            continue 'mart;
        }
    }
}

```

这里，我将一些代码移动到了循环内部，并重新组织了一下代码，以便更好地处理循环的引入。在循环内部，第 31 行，如果用户输入了一个无效的字符串，我将 `continue` `mart` 循环。


### 对用户输入做出反应


现在，所有其他的状况都已经处理好了，是时候写一些代码来让用户从水果市场购买水果了，当用户希望退出时，程序也会退出。


因为你也知道用户选择了哪种水果，所以让我们问一下他们打算购买多少，并告诉他们输入数量的格式。



```
use std::io;

fn main() {
    println!("欢迎来到水果市场！");
    println!("请选择要购买的水果。\n");

    let valid_inputs = ["苹果", "香蕉", "橘子", "芒果", "葡萄", "quit", "q"];

    'mart: loop {
        let mut user_input = String::new();
        let mut quantity = String::new();

        println!("\n可以购买的水果：苹果、香蕉、橘子、芒果、葡萄");
        println!("购买完成后，请输入“quit”或“q”。\n");

        // 读取用户输入
        io::stdin()
            .read_line(&mut user_input)
            .expect("无法读取用户输入。");
        user_input = user_input.trim().to_lowercase();

        // 验证用户输入
        let mut input_error = true;
        for input in valid_inputs {
            if input == user_input {
                input_error = false;
                break;
            }
        }

        // 处理无效输入
        if input_error {
            println!("错误: 请输入有效的输入");
            continue 'mart;
        }

        // 如果用户想要退出，就退出
        if user_input == "q" || user_input == "quit" {
            break 'mart;
        }

        // 获取数量
        println!(
            "\n你选择购买的水果是 \"{}\"。请输入以千克为单位的数量。
（1 千克 500 克的数量应该输入为 '1.5'。）",
            user_input
        );
        io::stdin()
            .read_line(&mut quantity)
            .expect("无法读取用户输入。");
    }
}

```

在第 11 行，我声明了另一个可变变量，它的值是一个空字符串，在第 48 行，我接受了用户的输入，但是这次是用户打算购买的水果的数量。


#### 解析数量


我刚刚增加了一些代码，以已知的格式接受数量，但是这些数据被存储为字符串。我需要从中提取出浮点数。幸运的是，这可以通过 `parse()` 方法来完成。


就像 `read_line()` 方法一样，`parse()` 方法返回一个 `Result` 枚举。`parse()` 方法返回 `Result` 枚举的原因可以通过我们试图实现的内容来轻松理解。


我正在接受用户的字符串，并尝试将其转换为浮点数。浮点数有两个可能的值。一个是浮点数本身，另一个是小数。


字符串可以包含字母，但是浮点数不行。所以，如果用户输入的不是浮点数和小数，`parse()` 函数将会返回一个错误。


因此，这个错误也需要处理。我们将使用 `expect()` 函数来处理这个错误。



```
use std::io;

fn main() {
    println!("欢迎来到水果市场！");
    println!("请选择要购买的水果。\n");

    let valid_inputs = ["苹果", "香蕉", "橘子", "芒果", "葡萄", "quit", "q"];

    'mart: loop {
        let mut user_input = String::new();
        let mut quantity = String::new();

        println!("\n可以购买的水果：苹果、香蕉、橘子、芒果、葡萄");
        println!("购买完成后，请输入“quit”或“q”。\n");

        // 读取用户输入
        io::stdin()
            .read_line(&mut user_input)
            .expect("无法读取用户输入。");
        user_input = user_input.trim().to_lowercase();

        // 验证用户输入
        let mut input_error = true;
        for input in valid_inputs {
            if input == user_input {
                input_error = false;
                break;
            }
        }

        // 处理无效输入
        if input_error {
            println!("错误: 请输入有效的输入");
            continue 'mart;
        }

        // 如果用户想要退出，就退出
        if user_input == "q" || user_input == "quit" {
            break 'mart;
        }

        // 获取数量
        println!(
            "\n你选择购买的水果是 \"{}\"。请输入以千克为单位的数量。
（1 千克 500 克的数量应该输入为 '1.5'。）",
            user_input
        );
        io::stdin()
            .read_line(&mut quantity)
            .expect("无法读取用户输入。");

        let quantity: f64 = quantity
            .trim()
            .parse()
            .expect("请输入有效的数量。");

    }
}

```

如你所见，我通过变量遮蔽将解析后的浮点数存储在变量 `quantity` 中。为了告诉 `parse()` 函数，我的意图是将字符串解析为 `f64`，我手动将变量 `quantity` 的类型注释为 `f64`。


现在，`parse()` 函数将会解析字符串并返回一个 `f64` 或者一个错误，`expect()` 函数将会处理这个错误。


### 计算价格 + 最后的修饰


现在我们知道了用户想要购买的水果及其数量，现在是时候进行计算了，并让用户知道结果/总价了。


为了真实起见，我将为每种水果设置两个价格。第一个价格是零售价，我们在购买少量水果时向水果供应商支付的价格。水果的第二个价格是当有人批量购买水果时支付的批发价。


批发价将会在订单数量大于被认为是批发购买的最低订单数量时确定。这个最低订单数量对于每种水果都是不同的。每种水果的价格都是每千克多少卢比。


想好了逻辑，下面是最终的程序。



```
use std::io;

const APPLE_RETAIL_PER_KG: f64 = 60.0;
const APPLE_WHOLESALE_PER_KG: f64 = 45.0;

const BANANA_RETAIL_PER_KG: f64 = 20.0;
const BANANA_WHOLESALE_PER_KG: f64 = 15.0;

const ORANGE_RETAIL_PER_KG: f64 = 100.0;
const ORANGE_WHOLESALE_PER_KG: f64 = 80.0;

const MANGO_RETAIL_PER_KG: f64 = 60.0;
const MANGO_WHOLESALE_PER_KG: f64 = 55.0;

const GRAPES_RETAIL_PER_KG: f64 = 120.0;
const GRAPES_WHOLESALE_PER_KG: f64 = 100.0;

fn main() {
    println!("欢迎来到水果市场！");
    println!("请选择要购买的水果。\n");

    let valid_inputs = ["苹果", "香蕉", "橘子", "芒果", "葡萄", "quit", "q"];

    'mart: loop {
        let mut user_input = String::new();
        let mut quantity = String::new();

        println!("\n可以购买的水果：苹果、香蕉、橘子、芒果、葡萄");
        println!("购买完成后，请输入“quit”或“q”。\n");

        // 读取用户输入
        io::stdin()
            .read_line(&mut user_input)
            .expect("无法读取用户输入。");
        user_input = user_input.trim().to_lowercase();

        // 验证用户输入
        let mut input_error = true;
        for input in valid_inputs {
            if input == user_input {
                input_error = false;
                break;
            }
        }

        // 处理无效输入
        if input_error {
            println!("错误: 请输入有效的输入");
            continue 'mart;
        }

        // 如果用户想要退出，就退出
        if user_input == "q" || user_input == "quit" {
            break 'mart;
        }

        // 获取数量
        println!(
            "\n你选择购买的水果是 \"{}\"。请输入以千克为单位的数量。
（1 千克 500 克的数量应该输入为 '1.5'。）",
            user_input
        );
        io::stdin()
            .read_line(&mut quantity)
            .expect("无法读取用户输入。");

        let quantity: f64 = quantity
            .trim()
            .parse()
            .expect("请输入有效的数量。");

        total += calc_price(quantity, user_input);
    }

    println!("\n\n总价是 {} 卢比。", total);
}

fn calc_price(quantity: f64, fruit: String) -> f64 {
    if fruit == "apple" {
        price_apple(quantity)
    } else if fruit == "banana" {
        price_banana(quantity)
    } else if fruit == "orange" {
        price_orange(quantity)
    } else if fruit == "mango" {
        price_mango(quantity)
    } else {
        price_grapes(quantity)
    }
}

fn price_apple(quantity: f64) -> f64 {
    if quantity > 7.0 {
        quantity * APPLE_WHOLESALE_PER_KG
    } else {
        quantity * APPLE_RETAIL_PER_KG
    }
}

fn price_banana(quantity: f64) -> f64 {
    if quantity > 4.0 {
        quantity * BANANA_WHOLESALE_PER_KG
    } else {
        quantity * BANANA_RETAIL_PER_KG
    }
}

fn price_orange(quantity: f64) -> f64 {
    if quantity > 3.5 {
        quantity * ORANGE_WHOLESALE_PER_KG
    } else {
        quantity * ORANGE_RETAIL_PER_KG
    }
}

fn price_mango(quantity: f64) -> f64 {
    if quantity > 5.0 {
        quantity * MANGO_WHOLESALE_PER_KG
    } else {
        quantity * MANGO_RETAIL_PER_KG
    }
}

fn price_grapes(quantity: f64) -> f64 {
    if quantity > 2.0 {
        quantity * GRAPES_WHOLESALE_PER_KG
    } else {
        quantity * GRAPES_RETAIL_PER_KG
    }
}

```

对比之前的版本，我做了一些改动……


水果的价格可能会波动，但是在我们程序的生命周期内，这些价格不会波动。所以我将每种水果的零售价和批发价存储在常量中。我将这些常量定义在 `main()` 函数之外（即全局常量），因为我不会在 `main()` 函数内计算每种水果的价格。这些常量被声明为 `f64`，因为它们将与 `quantity` 相乘，而 `quantity` 是 `f64`。记住，Rust 没有隐式类型转换 ?


当水果名称和用户想要购买的数量被存下来之后，`calc_price()` 函数被调用来计算用户指定数量的水果的价格。这个函数接受水果名称和数量作为参数，并将价格作为 `f64` 返回。


当你看到 `calc_price()` 函数的内部时，你会发现它是许多人所说的包装函数。它被称为包装函数，因为它调用其他函数来完成它的脏活。


因为每种水果都有不同的最低订单数量，才能被认为是批发购买，为了确保代码在未来可以轻松维护，每种水果都有单独的函数负责计算价格。


所以，`calc_price()` 函数所做的就是确定用户选择了哪种水果，并调用相应的函数来计算所选水果的价格。这些水果特定的函数只接受一个参数：数量。这些水果特定的函数将价格作为 `f64` 返回。


现在，`price_*()` 函数只做一件事。它们检查订单数量是否大于被认为是批发购买的最低订单数量。如果是这样，`quantity` 将会乘以水果的每千克批发价格。否则，`quantity` 将会乘以水果的每千克零售价格。


由于乘法行末尾没有分号，所以函数返回乘积。


如果你仔细看看 `calc_price()` 函数中水果特定函数的函数调用，这些函数调用在末尾没有分号。这意味着，`price_*()` 函数返回的值将会被 `calc_price()` 函数返回给它的调用者。


而且 `calc_price()` 函数只有一个调用者。这个调用者在 `mart` 循环的末尾，这个调用者使用这个函数返回的值来增加 `total` 的值。


最终，当 `mart` 循环结束（当用户输入 `q` 或 `quit` 时），存储在变量 `total` 中的值将会被打印到屏幕上，并且用户将会被告知他/她需要支付的价格。


### 总结


这篇文章中，我使用了之前讲解的 Rust 编程语言的所有主题来创建一个简单的程序，这个程序仍然在某种程度上展示了一个现实世界的问题。


现在，我写的代码肯定可以用一种更符合编程习惯的方式来写，这种方式最好地使用了 Rust 的喜爱特性，但是我还没有讲到它们！


所以，敬请关注后续的 **将 Rust 带入下一个层次** 系列，并学习更多 Rust 编程语言的内容！


Rust 基础系列到此结束。欢迎你的反馈。


*（题图：MJ/6d486f23-e6fe-4bef-a28d-df067ef2ec06）*




---


via: <https://itsfoss.com/milestone-rust-program/>


作者：[Pratham Patel](https://itsfoss.com/author/pratham/) 选题：[lkxed](https://github.com/lkxed/) 译者：[Cubik65536](https://github.com/Cubik65536) 校对：[wxy](https://github.com/wxy)


本文由 [LCTT](https://github.com/LCTT/TranslateProject) 原创编译，[Linux中国](https://linux.cn/) 荣誉推出


OK
# Rust Basics Series #8: Write the Milestone Rust Program

In the final chapter of the Rust Basics Series, recall the concepts you learned and write a somewhat complex Rust program.

So long, we have covered a handful of fundamental topics about programming in Rust. Some of these topics are [variables, mutability, constants](https://itsfoss.com/rust-variables), [data types](https://itsfoss.com/rust-data-types-01), [functions](https://itsfoss.com/rust-functions), [if-else statements](https://itsfoss.com/rust-conditional-statements) and [loops](https://itsfoss.com/rust-loops).

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.

## 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");
}
```


## 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
let mut user_input = String::new();
io::stdin()
.read_line(&mut user_input)
.expect("Unable to read user input.");
}
```


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

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`

.

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](https://doc.rust-lang.org/std/prelude/index.html).)

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.

### 2. Understanding the String type in Rust

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!).

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.

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*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.

### 3. Accepting the user input

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.

*. This function stops reading the input*

*quirk**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.*

**after**So please, either account for this trailing newline when dealing with it or remove it.

## A primer on error handling in Rust

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.

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*.


**All the new concepts that I have briefly touched on will be covered in a new Rust series later.**Now that you hopefully understand these newer concepts, let's add more code to increase the functionality.

## Validating user input

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
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"];
user_input = user_input.trim().to_lowercase();
let mut input_error = true;
for input in valid_inputs {
if input == user_input {
input_error = false;
break;
}
}
}
```


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.

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.

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".

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.

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.

## 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"];
'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");
// get user input
io::stdin()
.read_line(&mut user_input)
.expect("Unable to read user input.");
user_input = user_input.trim().to_lowercase();
// validate user input
let mut input_error = true;
for input in valid_inputs {
if input == user_input {
input_error = false;
break;
}
}
// handle invalid input
if input_error {
println!("ERROR: please enter a valid input");
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.

## 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"];
'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");
// get user input
io::stdin()
.read_line(&mut user_input)
.expect("Unable to read user input.");
user_input = user_input.trim().to_lowercase();
// validate user input
let mut input_error = true;
for input in valid_inputs {
if input == user_input {
input_error = false;
break;
}
}
// handle invalid input
if input_error {
println!("ERROR: please enter a valid input");
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'.)",
user_input
);
io::stdin()
.read_line(&mut quantity)
.expect("Unable to read user input.");
}
}
```


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.

### 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.

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.

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.

Hence, this error needs to be handled too. We will use the `expect()`

function to deal with this.

```
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"];
'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");
// get user input
io::stdin()
.read_line(&mut user_input)
.expect("Unable to read user input.");
user_input = user_input.trim().to_lowercase();
// validate user input
let mut input_error = true;
for input in valid_inputs {
if input == user_input {
input_error = false;
break;
}
}
// handle invalid input
if input_error {
println!("ERROR: please enter a valid input");
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'.)",
user_input
);
io::stdin()
.read_line(&mut quantity)
.expect("Unable to read user input.");
let quantity: f64 = quantity
.trim()
.parse()
.expect("Please enter a valid quantity.");
}
}
```


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`

.

Now, the `parse()`

function will parse the String and return a `f64`

or an error, that the `expect()`

function will deal with.

## 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;
const APPLE_RETAIL_PER_KG: f64 = 60.0;
const APPLE_WHOLESALE_PER_KG: f64 = 45.0;
const BANANA_RETAIL_PER_KG: f64 = 20.0;
const BANANA_WHOLESALE_PER_KG: f64 = 15.0;
const ORANGE_RETAIL_PER_KG: f64 = 100.0;
const ORANGE_WHOLESALE_PER_KG: f64 = 80.0;
const MANGO_RETAIL_PER_KG: f64 = 60.0;
const MANGO_WHOLESALE_PER_KG: f64 = 55.0;
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");
let mut total: f64 = 0.0;
let valid_inputs = ["apple", "banana", "orange", "mango", "grapes", "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");
// get user input
io::stdin()
.read_line(&mut user_input)
.expect("Unable to read user input.");
user_input = user_input.trim().to_lowercase();
// validate user input
let mut input_error = true;
for input in valid_inputs {
if input == user_input {
input_error = false;
break;
}
}
// handle invalid input
if input_error {
println!("ERROR: please enter a valid input");
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'.)",
user_input
);
io::stdin()
.read_line(&mut quantity)
.expect("Unable to read user input.");
let quantity: f64 = quantity
.trim()
.parse()
.expect("Please enter a valid quantity.");
total += calc_price(quantity, user_input);
}
println!("\n\nYour total is {} Rupees.", total);
}
fn calc_price(quantity: f64, fruit: String) -> f64 {
if fruit == "apple" {
price_apple(quantity)
} else if fruit == "banana" {
price_banana(quantity)
} else if fruit == "orange" {
price_orange(quantity)
} else if fruit == "mango" {
price_mango(quantity)
} else {
price_grapes(quantity)
}
}
fn price_apple(quantity: f64) -> f64 {
if quantity > 7.0 {
quantity * APPLE_WHOLESALE_PER_KG
} else {
quantity * APPLE_RETAIL_PER_KG
}
}
fn price_banana(quantity: f64) -> f64 {
if quantity > 4.0 {
quantity * BANANA_WHOLESALE_PER_KG
} else {
quantity * BANANA_RETAIL_PER_KG
}
}
fn price_orange(quantity: f64) -> f64 {
if quantity > 3.5 {
quantity * ORANGE_WHOLESALE_PER_KG
} else {
quantity * ORANGE_RETAIL_PER_KG
}
}
fn price_mango(quantity: f64) -> f64 {
if quantity > 5.0 {
quantity * MANGO_WHOLESALE_PER_KG
} else {
quantity * MANGO_RETAIL_PER_KG
}
}
fn price_grapes(quantity: f64) -> f64 {
if quantity > 2.0 {
quantity * GRAPES_WHOLESALE_PER_KG
} else {
quantity * GRAPES_RETAIL_PER_KG
}
}
```


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 ;)

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`

.

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.

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`

.

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.

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.

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`

.

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.

## 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.

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!

So stay tuned for follow-up **Take Rust to The Next Level series** and learn more of the Rust programming language!

The Rust Basics series concludes here. I welcome your feedback.