标签 Rust 下的文章

到目前为止,我们已经讲解了包括 变量、可变性、常量数据类型函数if-else 语句循环 在内的一些关于 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 宏。(你可以在 这里 阅读更多关于 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::iostdin() 函数。如果我在程序的开头没有导入 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 行)。这个数组包含了所有可以购买的水果的名称,以及字符串切片 qquit,让用户可以传达他们是否希望退出。

用户可能不知道我们希望输入是什么样的。用户可能会输入“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 相乘,而 quantityf64。记住,Rust 没有隐式类型转换 ?

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

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

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

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

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

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

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

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

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

总结

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

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

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

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

(题图:MJ/6d486f23-e6fe-4bef-a28d-df067ef2ec06)


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

作者:Pratham Patel 选题:lkxed 译者:Cubik65536 校对:wxy

本文由 LCTT 原创编译,Linux中国 荣誉推出

在 Rust 系列的 上一篇文章 中,我介绍了如何使用 ifelse 关键字来处理 Rust 程序的控制流。

这是处理程序控制流的一种方法。另一种方法是使用循环。因此,让我们在本文中看看循环。

Rust 中可用的循环

Rust 编程语言有三种不同的循环,基于你想要实现什么以及可用的内容:

  • for
  • while
  • loop

我假设你对 forwhile 已经很熟悉了,但 loop 对你来说可能是个新概念。让我们先从熟悉的概念开始。

for 循环

for 循环主要用于迭代一种称为迭代器的东西。

这个迭代器可以从任何东西中创建,从数组、向量(很快就会介绍!)、一系列值,或者任何自定义的东西。这里的可能性是无限的。

来看看 for 循环的语法。

for 迭代变量 in 迭代器 {
    <语句>;
}

其中的 迭代变量 在大多数其他编程语言教程中通常被称为 i ; )

迭代器 可以是任何东西,只要它能告诉下一个值是什么,如果有的话。

来通过一个程序来理解这个。

fn main() {
    let my_arr = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9];

    println!("迭代数组");
    for element in my_arr {
        println!("{}", element);
    }

    println!("\n迭代一个真正的迭代器");
    for element in my_arr.iter() {
        println!("{}", element);
    }

    println!("\nPython 风格的范围");
    for element in 0..10 {
        println!("{}", element);
    }
}

这里,我声明了一个数组,它包含从 0 到 9 的 10 个数字。在第 5 行的 for 循环中,我只是将这个数组指定为迭代器,Rust 会自动处理对这个数组的所有元素的迭代。不需要花哨的 my_arr[i] 魔法。

但是,在第 10 行,我调用了 .iter() 函数。这是一个明确的提及,它基于 my_arr 的值来获取一个迭代器。这个循环和第 5 行的循环之间唯一的区别是,这里你是通过在数组上调用 .iter() 函数来明确地调用它的。

在这个上下文环境中,在一个数据类型上调用 .iter() 函数不是必须的。因为这是一个数组,是语言本身提供的一种数据类型,Rust 已经知道如何处理它了。但是你 需要 在自定义数据类型中使用它。

最后,在第 15 行,我们有一个循环,它循环遍历一个范围。嗯,差不多是这样。如果你仔细看,这个范围看起来很像切片 “类型”。Rust 也知道这一点,并且 你处理了迭代(哈哈,明白了吗?)。

LCTT 译注:此处的梗是,“为你处理了迭代” 的英文原文是 “handles iteration for you",其中的 “for” 与 “for 循环” 的 “for” 是同一个单词。

输出如下:

迭代数组
0
1
2
3
4
5
6
7
8
9

迭代一个真正的迭代器
0
1
2
3
4
5
6
7
8
9

Python 风格的范围
0
1
2
3
4
5
6
7
8
9

while 循环

while 循环可以被认为是非常类似于 if 条件语句。使用 if 语句,只要用户提供的条件为 trueif 语句体中的代码就会被执行 一次

但是,在 while 循环中,如果条件评估为 true,循环就会开始循环循环体。只要条件继续评估为 true,循环就会继续迭代。

while 循环只有在循环完成当前迭代中所有语句的执行并且在检查条件时,它的结果为 false 时才会停止。

来看看 while 循环的语法...

while 条件 {
    <语句>;
}

看到了吗?和 if 条件语句非常相似!不过没有 else 块 ; )

来看一个程序来更好地理解这个。

fn main() {
    let mut var = 0;

    while var < 3 {
        println!("{var}");
        var += 1;
    }
}

我有一个可变变量 var,它的初始值为 0。只要可变变量 var 中存储的值小于 3,while 循环就会执行。

在循环中,var 的值被打印出来,然后它的值被增加 1。

这是上面代码的输出:

0
1
2

loop 循环

Rust 有一个无限循环。是的,一个没有开始条件和停止条件的循环。它只是一直循环,直到永远。当然,它有触发器来停止代码本身的循环执行。

无限循环的语法如下:

loop {
    <语句>;
}
? 这些循环主要用于 GUI 软件,退出是一个 显式 操作。

在我给你一个例子之前,因为这个循环非常特殊,让我们先看看如何 退出 它 :p

要停止无限循环的执行,需要在循环内使用 break 关键字。

来看一个例子,只有 0 到 3 之间的整数(包括 0 和 3)才会被打印到程序输出。

fn main() {
    let mut var = 0;

    loop {
        if var > 3 {
            break;
        }

        println!("{}", var);
        var += 1;
    }
}

看待这个特定的例子的最好方法是将它看作是一个增加了一堆没有必要的东西的 while 循环 ; )

你有一个可变变量 var,它的初始值为 0,它被用作迭代器。无限循环从一个 if 条件开始,如果 var 的值大于 3,break 关键字就会被执行。后来,就像 while 循环的前一个例子一样,var 的值被打印到标准输出,然后它的值被增加 1。

它的输出如下:

0
1
2
3

标记循环

假设有两个无限循环,一个嵌套在另一个中。由于某种原因,退出条件在最内层循环中被检查,但这个退出条件是为了退出最外层循环。

在这种情况下,标记循环可能是有益的。

? breakcontinue 关键字并不仅仅用于无限循环。它们可以用于 Rust 语言提供的所有三种循环。

接下来是如何标记循环。

'标记: loop {}

要告诉编译器一个循环被标记了,从一个单引号字符开始,输入它的标签,然后跟着一个冒号。然后,继续使用你通常定义循环的方式。

当你需要退出某个循环时,只需像这样指定循环标签:

break '标记;

来看一个例子来更好地理解这个。

fn main() {
    let mut a = 0;
    let mut b = 0;

    'parent: loop {
        a += 1;

        loop {
            println!("a: {}, b: {}", a, b);
            b += 1;

            if a + b == 10 {
                println!("\n{} + {} = 10", a, b);
                break 'parent;
            }
        }
    }
}

这里,我使用两个可变变量 ab,它们的初始值都设置为 0。

然后,最外层的循环被标记为 parentparent 循环将变量 a 的值增加 1,并有一个内部/子循环。

这个(在第 8 行的)子循环打印变量 ab 的值。在这个循环内部,变量 b 的值增加了 1。退出条件是 a + b == 10。这意味着只要变量 ab 中存储的值相加,结果为 10,parent 循环就会被打破。即使第 14 行的 break 条件“属于”内部循环,它也会打破 parent 循环。

来看看程序的输出。

a: 1, b: 0
a: 1, b: 1
a: 1, b: 2
a: 1, b: 3
a: 1, b: 4
a: 1, b: 5
a: 1, b: 6
a: 1, b: 7
a: 1, b: 8

1 + 9 = 10

就像从程序输出中可以看出的那样,循环在 ab 分别具有值 1 和 9 时停止。

continue 关键字

如果你已经在其他编程语言(如 C/C++/Java/Python)中使用过循环,你可能已经知道 continue 关键字的用法。

break 关键字用于完全停止循环执行时,continue 关键字用于“跳过”循环执行的 当前迭代 并从下一迭代开始(如果条件允许)。

来看一个例子来理解 continue 关键字的工作原理。

fn main() {
    for i in 0..10 {
        if i % 2 == 0 {
            continue;
        }
        println!("{}", i)
    }
}

在上面的代码中,我有一个 for 循环,它迭代了 0 到 9 之间的整数(包括 0 和 9)。一旦循环开始,我就设置了一个条件检查,看看这个数字是不是偶数。如果这个数字是偶数,continue 关键字就会被执行。

但是如果这个数字是奇数,这个数字就会被打印到程序输出。

来看看这个程序的输出。

1
3
5
7
9

正如你所看到的,循环似乎一直在“进行”,尽管 0 到 9 之间显然有偶数。但是因为我使用了 continue 关键字,当遇到这个关键字时,循环执行就会停止。

这个循环跳过了它下面的任何东西,并继续下一次迭代。这就是为什么偶数没有被打印出来,但是 0 到 9 之间的所有奇数都被打印到了程序输出中。

总结

要总结这篇长文,我演示了 3 种不同循环的用法:forwhileloop。我还讨论了两个关键字,它们影响这些循环的控制流:breakcontinue

我希望你现在能理解每个循环的适当用例。如果你有任何问题,请告诉我。

(题图:MJ/25579e09-ae1c-47d3-8266-3bd9a54456c0)


via: https://itsfoss.com/rust-loops/

作者:Pratham Patel 选题:lkxed 译者:Cubik65536 校对:wxy

本文由 LCTT 原创编译,Linux中国 荣誉推出

上一篇文章 中,你学习了函数。在这篇文章中,我们将学习使用条件语句来管理 Rust 程序的控制流。

什么是条件语句?

在编写代码的时候,最常见的任务之一就是检查某些条件是否为 truefalse。“如果温度高于 35°C,打开空调。”

通过使用 ifelse 这样的关键字(有时候结合使用),程序员可以根据条件(例如提供的参数数量、从命令行传递的选项、文件名、错误发生等)改变程序的行为。

所以,对一个程序员来说,了解任何语言的控制流都是至关重要的,更不用说 Rust 了。

条件运算符

下表列出了所有常用的单个条件运算符:

运算符示例解释
>a > ba 大于 b
<a < ba 小于 b
==a == ba 等于 b
!=a != ba 不等于 b
>=a >= ba 大于等于 b
<=a <= ba 小于等于 b

以及下表是逻辑运算符,它们用于一个或多个条件之间:

运算符示例解释
` ` (逻辑或)`条件1 条件2`条件1条件2 中至少有一个为 true
&& (逻辑与)条件1 && 条件2所有 条件都为 true
! (逻辑非)!条件条件 的布尔值的相反值
? 与数学相似,你可以使用圆括号来指定操作的优先级。

使用 if else

要控制 Rust 代码的基本流程,使用两个关键字:ifelse。这可以根据提供的条件的状态创建两个“执行路径”。

一个简单的带有替代执行路径的 if 块的语法如下:

if 条件 {
    <语句>;
} else {
    <语句>;
}
? 当只有一个条件时,将其括在圆括号中并不是强制性的。根据语法,使用圆括号是可选的。你仍然应该使用它们来指定优先级并优化可读性。

来看看一个例子。

fn main() {
    let a = 36;
    let b = 25;

    if a > b {
        println!("a 大于 b");
    } else {
        println!("b 大于 a");
    }
}

这里,我声明了两个整数变量 ab,它们的值分别为 '36' 和 '25'。在第 5 行,我检查变量 a 中存储的值是否大于变量 b 中存储的值。如果条件计算结果为 true,则会执行第 6 行的代码。如果条件计算结果为 false,由于我们有一个 else 块(可选),第 8 行的代码将被执行。

来看看程序的输出。

a 大于 b

完美!

来修改一下变量 a 的值,使其小于变量 b 的值,看看会发生什么。我将把 a 的值改为 '10'。修改后的输出如下:

b 大于 a

但是,如果我将相同的值存储在变量 ab 中呢?为了看到这一点,我将两个变量的值都设置为 '40'。修改后的输出如下:

b 大于 a

嗯?从逻辑上讲,这没有任何意义... :frowning:

但是这可以改进!我们继续。

使用 else if 条件

与其他任何编程语言一样,你可以使用 else if 块来提供多于两个的执行路径。语法如下:

if 条件 {
    <语句>;
} else if 条件 {
    <语句>;
} else {
    <语句>;
}

现在,通过使用 else if 块,我可以改进程序的逻辑。下面是修改后的程序。

fn main() {
    let a = 40;
    let b = 40;

    if a == b {
        println!("a 与 b 是相等的");
    } else if a > b {
        println!("a 大于 b");
    } else {
        println!("b 大于 a");
    }
}

现在,我的程序的逻辑是正确的。它已经处理了所有的边缘情况(我能想到的)。第 5 行处理了 a 等于 b 的情况。第 7 行处理了 a 可能大于 b 的情况。而 a 小于 b 的情况则由第 9 行的 else 块隐式处理。

现在,当我运行这段代码时,我得到了以下输出:

a 与 b 是相等的

现在这就完美了!

示例:找到最大值

我知道使用 ifelse 很容易,但是让我们再看一个程序。这次,我们来比较三个数字。我还将在这个实例中使用逻辑运算符!

fn main() {
    let a = 73;
    let b = 56;
    let c = 15;

    if (a != b) && (a != c) && (b != c) {
        if (a > b) && (a > c) {
            println!("a 是最大的");
        } else if (b > a) && (b > c) {
            println!("b 是最大的");
        } else {
            println!("c 是最大的");
        }
    }
}

这个程序第一眼看上去可能很复杂,但是不要害怕,我会解释的!

最开始,我声明了三个变量 abc,并赋予了我能想到的随机值。然后,在第 6 行,我检查了没有变量的值与其他变量相同的条件。首先,我检查 ab 的值,然后是 ac,最后是 bc。这样我就可以确定没有变量中存储了重复的值。

然后,在第 7 行,我检查了变量 a 中存储的值是否是最大的。如果这个条件计算结果为 true,则会执行第 8 行的代码。否则,将检查第 9 行的执行路径。

在第 9 行,我检查了变量 b 中存储的值是否是最大的。如果这个条件计算结果为 true,则会执行第 10 行的代码。如果这个条件也是 false,那么只有一种可能。3 个变量中的最大值既不是 a 也不是 b

所以,自然地,在 else 块中,我打印出变量 c 拥有最大值。

来看看程序的输出:

a 是最大的

这是预期的结果。尝试修改分配给每个变量的值,并自己测试一下! :smiley:

总结

你学习到了如何使用 ifelse 语句。在你继续使用大量 if else if 语句制作自己的 AI 之前(哈哈),让我们在本系列的下一篇文章中学习 Rust 中的循环。

持续关注。

(题图:MJ/3eea3bbb-b630-4470-ae21-391ab86cd5bf)


via: https://itsfoss.com/rust-if-else/

作者:Pratham Patel 选题:lkxed 译者:Cubik65536 校对:wxy

本文由 LCTT 原创编译,Linux中国 荣誉推出

Rust 项目再次陷入领导力危机

继两年前 Rust 审核团队宣布立即辞职后,Rust 项目的管理就一直处于跛脚运行之中。最近,Rust 项目又卷入了 RustConf 主题演讲争议。RustConf 2023 主动邀请了 JeanHeyd Meneide 发表主题演讲,他是 C 语言官方标准的两位编辑之一,管理着许多重要的大型开源项目。但在他花费了大量时间准备了演讲《编译时编程(可能)的未来》后,Rustconf 以其演讲“不被 Rust 项目认可”,担心该演讲被认为是官方方向的理由,而通知他将主题演讲降为普通演讲。但实际原因被推测为他曾经分享过一些认为 Rust 应该反思的内容,Rust 管理层内有人反感他。他认为这是一次侮辱,随即 宣布 拒绝出席 RustConf。此事还导致推荐他发表演讲的 Rust 核心成员愤而辞职。在此事冲上热搜后,Rust 项目发表 声明 对此事公开道歉,称它的决策和沟通流程出现了问题,开始启动两年来迟迟未决的新的治理委员会。

消息来源:The PHD
老王点评:一个成功的开源项目,需要广泛的社区决策,也需要一个定海神针般的领袖人物,Linux 如此、Python 如此。而 Rust 社区却没有一个灵魂人物,我们只能看到各种内部斗争和自行其是,这样的风格,即便成立治理委员会,我认为也很难说有未来。

英伟达成为第一家万亿芯片公司

英伟达周二成为第一家市值突破 1 万亿美元的芯片公司。此前进入万亿美元俱乐部的公司包括苹果、微软、Alphabet/谷歌、亚马逊和 Meta。AI 的繁荣催动了英伟达的股价上周一度飙升 24%,接近万亿美元,本周二开盘短时突破这一数字。

消息来源:The Verge
老王点评:热点常有,而芯片永存。无论是区块链、游戏还是 AI,这都让芯片公司赚得盆满钵满。

ChatGPT 为律师编写的法庭文件杜撰了六个不存在的案例

尽管我们知道人工智能会煞有介事的杜撰一些“事实”,但是一位律师在使用 ChatGPT 帮助编写法庭文件时,AI 居然杜撰了六个不存在的案例,并且还信誓旦旦地给出了法律来源,保证了其可靠性。律师表示,没有确认 ChatGPT 提供的来源是他的错。法官称这是“前所未有的”,并考虑对该律师进行惩罚。

消息来源:Ars Technica
老王点评:在 AI 出错时,你对它提出的批评它往往会听从。但如果你指出它的“事实性”错误时,它往往是嘴硬的,所以你还是得依靠“古典的”搜索引擎来确认事实。

在这一章中,在实例的帮助下,学习如何使用函数并从中返回值。

就跟任何现代编程语言一样,Rust 也有函数。

你已经熟悉的函数是 main 函数。这个函数在程序启动时被调用。

但是其他函数呢?在本文中,你将学习如何在 Rust 程序中使用函数。

函数的基本语法

你可能已经在我们声明 main 函数时知道了这一点,不管怎么样,还是让我们看一下声明函数的语法。

// 声明函数
fn function_name() {
    <statement(s)>;
}

// 调用函数
function_name();

来让我们看一个简单的函数,它将字符串 "Hi there!" 打印到标准输出。

fn main() {
    greet();
}

fn greet() {
    println!("Hi there!");
}
? 与 C 不一样的是,不管你是否要在声明或定义之前调用函数都没有关系。只要这个函数在 某个地方 被声明了,Rust 就会处理它。

正如预期,它的输出如下:

Hi there!

这挺简单的。让我们把它提升到下一个级别。让我们创建一个接受参数并返回值的函数。有没有参数和有没有返回值这两者之间无关。

使用函数接受参数

声明一个接受参数的函数的语法如下:

// 声明函数
fn function_name(variable_name: type) {
    <statement(s)>;
}

// 调用函数
function_name(value);

你可以把函数参数想象成一个传递给函数的 元组。它可以接受多种数据类型的参数,而且你可以接受任意多个参数。所以,你不必局限于接受相同类型的参数。

与某些语言不同的是,Rust 没有 默认参数在调用函数时填充所有参数是强制性的

示例:饥饿函数

来让我们看一个程序来更好地理解这个。

fn main() {
    food(2, 4);
}

fn food(theplas: i32, rotis: i32) {
    println!(
        "我饿了... 我需要 {} 个葫芦巴叶饼和 {} 个罗提!",
        theplas, rotis
    );
}

在第 5 行,我声明了一个名为 food 的函数。这个函数接受 2 个参数:theplasrotis(印度食物的名字)。然后我打印了这些变量的内容。

对于 main 函数,我使用参数 24 调用 food 函数。这意味着 theplas 被赋值为 2rotis 被赋值为 4

来让我们看一下程序的输出:

我饿了... 我需要 2 个葫芦巴叶饼和 4 个罗提!

我现在真的饿了... ?

从函数返回值

就像函数可以接受参数一样,函数也可以返回一个或多个值。这样的函数的语法如下:

// 声明函数
fn function_name() -> data_type {
    <statement(s)>;
}

// 调用函数
let x = function_name();

函数可以使用 return 关键字或者使用表达式而不是语句来返回一个值。

等等!什么是表达式?

在进一步之前:语句与表达式

在讲解 Rust 函数的例子中提起这个可能不太合适,但是你应该理解 Rust 和其他编程语言中语句和表达式的区别。

语句是以分号结尾且 不会计算出某个值 的代码行。另一方面,表达式是一行不以分号结尾且计算出某个值的代码行。

来让我们用一个例子来理解:

fn main() {
    let a = 873;
    let b = {
        // 语句
        println!("Assigning some value to b...");

        // 表达式
        a * 10
    };

    println!("b: {b}");
}

在第 3 行,我开始了一个代码块,在这个代码块中我有一个语句和一个表达式。注释标明了哪个是哪个。

在第 5 行的代码不会计算出某个值,因此需要以分号结尾。这是一个语句。

第 8 行的代码计算出了一个值。它是 a * 10873 * 10),并计算出了 8730。因为这一行没有以分号结尾,所以这是一个表达式。

? 使用表达式是从代码块中返回某些东西的一种方便的方法。因此,当返回一个值时,它是 return 关键字的替代方案。表达式不仅仅用于从函数中 “返回” 一个值。正如你刚刚看到的,a * 10 的值是从内部作用域 “返回” 到外部作用域,并赋值给变量 b。一个简单的作用域不是一个函数,但表达式的值仍然被 “返回” 了。

示例:购买腐烂的水果

来让我们看一个演示以理解函数如何返回一个值:

fn main() {
    println!(
        "如果我从水果摊买了 2 公斤苹果,我必须付给他们 {} 印度卢比。",
        retail_price(2.0)
    );
    println!(
        "但是,如果我从水果摊买了 30 公斤苹果,我就要付给他们 {} 印度卢比。",
        wholesale_price(30.0)
    );
}

fn retail_price(weight: f64) -> f64 {
    return weight * 500.0;
}

fn wholesale_price(weight: f64) -> f64 {
    weight * 400.0
}

我在上述代码中有两个函数:retail_pricewholesale_price。两个函数都接受一个参数并将值存储在 weight 变量中。这个变量的类型是 f64,函数签名表示最终函数返回一个 f64 值。

这两个函数都将购买的苹果的重量乘以一个数字。这个数字表示苹果的当前每公斤价格。由于批发商有大量订单,物流在某种程度上更容易,价格可以降低一点。

除了每公斤价格之外,这两个函数还有一个区别。那就是,retail_price 函数使用 return 关键字返回乘积。而 wholesale_price 函数使用表达式返回乘积。

如果我从水果摊买了 2 公斤苹果,我必须付给他们 1000 印度卢比。
但是,如果我从水果摊买了 30 公斤苹果,我就要付给他们 12000 印度卢比。

输出显示,从函数返回值的两种方法都按预期工作。

返回多个值

你可以有一个返回不同类型的多个值的函数。你有很多选择,但返回一个元组是最简单的。

接下来是一个示例:

fn main() {
    let (maths, english, science, sanskrit) = tuple_func();

    println!("数学考试得分: {maths}");
    println!("英语考试得分: {english}");
    println!("科学考试得分: {science}");
    println!("梵语考试得分: {sanskrit}");
}

fn tuple_func() -> (f64, f64, f64, f64) {
    // return marks for a student
    let maths = 84.50;
    let english = 85.00;
    let science = 75.00;
    let sanskrit = 67.25;

    (maths, english, science, sanskrit)
}

函数 tuple_func 返回 4 个封装在一个元组中的 f64 值。这些值是一个学生在四门科目(满分 100 分)中获得的分数。

当函数被调用时,这个元组被返回。我可以使用 tuple_name.0 方案打印这些值,但我认为最好先解构元组,这样可以帮助我们搞清楚值对应的是什么。然后我使用了包含被解构的元组的值的变量来打印分数。

这是我得到的输出:

数学考试得分: 84.5
英语考试得分: 85
科学考试得分: 75
梵语考试得分: 67.25

总结

本文介绍了 Rust 编程语言中的函数。这些是函数的 “类型”:

  • 不接受任何参数也不返回任何值的函数
  • 接收一个或多个参数的函数
  • 给调用者返回一个或多个值的函数

你知道接下来是什么吗?Rust 中的条件语句,也就是 if-else。请继续关注并享受学习 Rust 的过程。

(题图:MJ/5a07503b-c691-4276-83b2-bb42f5fda347)


via: https://itsfoss.com/rust-functions/

作者:Pratham Patel 选题:lkxed 译者:Cubik65536 校对:wxy

本文由 LCTT 原创编译,Linux中国 荣誉推出

Rust 和 Python 的优势互补。可以使用 Python 进行原型设计,然后将性能瓶颈转移到 Rust 上。

Python 和 Rust 是非常不同的语言,但它们实际上非常搭配。但在讨论如何将 Python 与 Rust 结合之前,我想先介绍一下 Rust 本身。你可能已经听说了这种语言,但可能还没有了解过它的细节。

什么是 Rust?

Rust 是一种低级语言,这意味着程序员所处理的东西接近于计算机的 “真实” 运行方式。

例如,整数类型由字节大小定义,与 CPU 支持的类型相对应。虽然我们很想简单地说 Rust 中的 a+b 对应于一条机器指令,但实际上并不完全是这样!

Rust 编译器链非常复杂。作为第一种近似的方法,将这样的语句视为 “有点” 真实是有用的。

Rust 旨在实现零成本抽象,这意味着许多语言级别可用的抽象在运行时环境中会被编译去掉。

例如,除非明确要求,对象会在堆栈上分配。结果是,在 Rust 中创建本地对象没有运行时成本(尽管可能需要进行初始化)。

最后,Rust 是一种内存安全的语言。也有其他内存安全的语言和其他支持零成本抽象的语言。但通常这些是两类不同的语言。

内存安全并不意味着不可能在 Rust 中出现内存违规。它确实意味着只有两种方式可能导致内存违规:

  • 编译器的错误。
  • 显式声明为不安全(unsafe)的代码。

Rust 标准库代码有很多被标记为不安全的代码,虽然比许多人预期的少。这并不意味着该语句无意义。除了需要自己编写不安全代码的(罕见的)情况外,内存违规通常是由基础设施造成的。

为什么会有 Rust 出现?

为什么人们要创建 Rust?是哪些问题没有被现有编程语言解决吗?

Rust 被设计成既能高效运行,又保证内存安全。在现代的联网世界中,这是一个越来越重要的问题。

Rust 的典型应用场景是协议的低级解析。待解析的数据通常来自不受信任的来源,并且需要通过高效的方式进行解析。

如果你认为这听起来像 Web 浏览器所做的事情,那不是巧合。Rust 最初起源于 Mozilla 基金会,它是为了改进 Firefox 浏览器而设计的。

如今,需要保证安全和速度的不仅仅是浏览器。即使是常见的微服务架构也必须能够快速解析不受信任的数据,同时保证安全。

现实示例:统计字符

为了理解 “封装 Rust” 的例子,需要解决一个问题。这个问题需要满足以下要求:

  • 足够容易解决。
  • 能够写高性能循环来优化。
  • 有一定的现实意义。

这个玩具问题的例子是判断一个字符在一个字符串中是否出现超过了 X 次。这个问题不容易通过高效的正则表达式解决。即使是专门的 Numpy 代码也可能不够快,因为通常没有必要扫描整个字符串。

你可以想象一些 Python 库和技巧的组合来解决这个问题。然而,如果在低级别的语言中实现直接的算法,它会非常快,并且更易于阅读。

为了使问题稍微有趣一些,以演示 Rust 的一些有趣部分,这个问题增加了一些变化。该算法支持在换行符处重置计数(意即:字符是否在一行中出现了超过 X 次?)或在空格处重置计数(意即:字符是否在单词中出现了超过 X 次?)。

这是唯一与 “现实性” 相关的部分。过多的现实性将使这个示例在教育上不再有用。

支持枚举

Rust 支持使用枚举(enum)。你可以使用枚举做很多有趣的事情。

目前,只使用了一个简单的三选一的枚举,并没有其他的变形。这个枚举编码了哪种字符重置计数。

#[derive(Copy)]
enum Reset {
    NewlinesReset,
    SpacesReset,
    NoReset,
}

支持结构

接下来的 Rust 组件更大一些:这是一个结构(struct)。Rust 的结构与 Python 的 dataclass 有些相似。同样,你可以用结构做更复杂的事情。

#[pyclass]
struct Counter {
    what: char,
    min_number: u64,
    reset: Reset, 
}

实现块

你可以在 Rust 中使用一个单独的块,称为实现(impl)块,为结构添加一个方法。但具体细节超出了本文的范围。

在这个示例中,该方法调用了一个外部函数。这主要是为了分解代码。更复杂的用例将指示 Rust 编译器内联该函数,以便在不产生任何运行时成本的情况下提高可读性。

#[pymethods]
impl Counter {
    #[new]
    fn new(what: char, min_number: u64, reset: Reset) -> Self {
        Counter{what: what, min_number: min_number, reset: reset}
    }
    
    fn has_count(
        &self,
        data: &str,
    ) -> bool {
        has_count(self, data.chars())
    }
}

函数

默认情况下,Rust 变量是常量。由于当前的计数(current_count)必须更改,因此它被声明为可变变量。

fn has_count(cntr: &Counter, chars: std::str::Chars) -> bool {
    let mut current_count : u64 = 0;
    for c in chars {
        if got_count(cntr, c, &mut current_count) {
            return true;
        }
    }
    false
}

该循环遍历字符并调用 got_count 函数。再次强调,这是为了将代码分解成幻灯片展示。它展示了如何向函数发送可变引用。

尽管 current_count 是可变的,但发送和接收站点都显式标记该引用为可变。这可以清楚地表明哪些函数可能修改一个值。

计数

got_count 函数重置计数器,将其递增,然后检查它。Rust 的冒号分隔的表达式序列评估最后一个表达式的结果,即是否达到了指定的阈值。

fn got_count(cntr: &Counter, c: char, current_count: &mut u64) -> bool {
    maybe_reset(cntr, c, current_count);
    maybe_incr(cntr, c, current_count);
    *current_count >= cntr.min_number
}

重置代码

reset 的代码展示了 Rust 中另一个有用的功能:模式匹配。对 Rust 中匹配的完整描述需要一个学期级别的课程,不适合在一个无关的演讲中讲解。这个示例匹配了该元组的两个选项之一。

fn maybe_reset(cntr: &Counter, c: char, current_count: &mut u64) -> () {
    match (c, cntr.reset) {
        ('\n', Reset::NewlinesReset) | (' ', Reset::SpacesReset)=> {
            *current_count = 0;
        }
        _ => {}
    };
}

增量支持

增量将字符与所需字符进行比较,并在匹配时增加计数。

fn maybe_incr(cntr: &Counter, c: char, current_count: &mut u64) -> (){
    if c == cntr.what {
        *current_count += 1;
    };
}

请注意,我在本文中优化了代码以适合幻灯片。这不一定是 Rust 代码的最佳实践示例,也不是如何设计良好的 API 的示例。

为 Python 封装 Rust 代码

为了将 Rust 代码封装到 Python 中,你可以使用 PyO3。PyO3 Rust “crate”(即库)允许内联提示将 Rust 代码包装为 Python,使得修改两者更容易。

包含 PyO3 crate 原语

首先,你必须包含 PyO3 crate 原语。

use pyo3::prelude::*;

封装枚举

枚举需要被封装。derive 从句对于将枚举封装为 PyO3 是必需的,因为它们允许类被复制和克隆,使它们更容易在 Python 中使用。

#[pyclass]
#[derive(Clone)]
#[derive(Copy)]
enum Reset {
    /* ... */
}

封装结构

结构同样需要被封装。在 Rust 中,这些被称为 “宏”,它们会生成所需的接口位。

#[pyclass]
struct Counter {
    /* ... */
}

封装实现

封装实现(impl)更有趣。增加了另一个名为 new 的宏。此方法被标记为 #[new],让 PyO3 知道如何为内置对象公开构造函数。

#[pymethods]
impl Counter {
    #[new]
    fn new(what: char, min_number: u64,
          reset: Reset) -> Self {
        Counter{what: what,
          min_number: min_number, reset: reset}
    }
    /* ... */
}

定义模块

最后,定义一个初始化模块的函数。此函数具有特定的签名,必须与模块同名,并用 #[pymodule] 修饰。

#[pymodule]
fn counter(_py: Python, m: &PyModule
) -> PyResult<()> {
    m.add_class::<Counter>()?;
    m.add_class::<Reset>()?;
    Ok(())
}

? 显示此函数可能失败(例如,如果类没有正确配置)。 PyResult 在导入时转换为 Python 异常。

Maturin 开发

为了快速检查,用 maturin develop 构建并将库安装到当前虚拟环境中。这有助于快速迭代。

$ maturin develop

Maturin 构建

maturin build 命令构建一个 manylinux 轮子,它可以上传到 PyPI。轮子是特定于 CPU 架构的。

Python 库

从 Python 中使用库是最简单的部分。没有任何东西表明这与在 Python 中编写代码有什么区别。这其中的一个有用方面是,如果你优化了已经有单元测试的 Python 中的现有库,你可以使用 Python 单元测试来测试 Rust 库。

导入

无论你是使用 maturin develop 还是 pip install 来安装它,导入库都是使用 import 完成的。

import counter

构造函数

构造函数的定义正好使对象可以从 Python 构建。这并不总是如此。有时仅从更复杂的函数返回对象。

cntr = counter.Counter(
    'c',
    3,
    counter.Reset.NewlinesReset,
)

调用函数

最终的收益终于来了。检查这个字符串是否至少有三个 “c” 字符:

>>> cntr.has_count("hello-c-c-c-goodbye")
True

添加一个换行符会触发剩余操作,这里没有插入换行符的三个 “c” 字符:

>>> cntr.has_count("hello-c-c-\nc-goodbye")
False

使用 Rust 和 Python 很容易

我的目标是让你相信将 Rust 和 Python 结合起来很简单。我编写了一些“粘合剂”代码。Rust 和 Python 具有互补的优点和缺点。

Rust 非常适合高性能、安全的代码。Rust 具有陡峭的学习曲线,对于快速原型解决方案而言可能有些笨拙。

Python 很容易入手,并支持非常紧密的迭代循环。Python 确实有一个“速度上限”。超过一定程度后,从 Python 中获得更好的性能就更难了。

将它们结合起来完美无缝。在 Python 中进行原型设计,并将性能瓶颈移至 Rust 中。

使用 Maturin,你的开发和部署流程更容易进行。开发、构建并享受这一组合吧!


via: https://opensource.com/article/23/3/python-loves-rust

作者:Moshe Zadka 选题:lkxed 译者:ChatGPT 校对:wxy

本文由 LCTT 原创编译,Linux中国 荣誉推出