37

Rust 初学者指南: 初识 Rust

 5 years ago
source link: http://www.ibm.com/developerworks/cn/opensource/os-know-rust/index.html?ca=drs-&%3Butm_source=tuicool&%3Butm_medium=referral
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

Rust 初学者指南

初识 Rust

一种替代 C 和 C++ 的编译语言

ZfQnMr7.jpg!web

Dylan Hicks

2018 年 6 月 14 日发布

系列内容:

此内容是该系列 # 部分中的第 # 部分: Rust 初学者指南

https://www.ibm.com/developerworks/cn/library/?series_title_by=**auto**

敬请期待该系列的后续内容。

此内容是该系列的一部分: Rust 初学者指南

敬请期待该系列的后续内容。

如果您编写过软件,您肯定问过自己“我应该使用哪种语言来编写这个程序?”这是一个合理的问题。您的代码是否需要尽可能快?它是否会在网络上运行?该代码将位于后端还是前端?所有语言都有自己的定位,Rust 也不例外。

Rust 是一种静态类型的编译语言,满足了大多数用户使用 CC++ 能够实现的目标。但是不同于 CC++ ,Rust 还侵占了 C# 和 Java™ 语言在本世纪统治了很长时间的领域:Rust 语言是内存安全且与操作系统无关的,这意味着它可以在任何计算机上运行。实质上,您会获得系统语言的所有速度和低阶优势,而没有我提及的后几种语言中麻烦的垃圾收集。激动不已?是的,我也很激动。欢迎使用 Rust!

备注:本文中演示的所有代码和命令都是我在运行 Linux® 的计算机上写的,但它们很容易移植到 macOS® 和 Windows®。

安装并运行 Rust

大多数初学者指南都会直接分析代码,但我认为在开始开发之前,最好首先了解如何编译和运行该软件。无论您采用何种操作系统, Rust 安装说明 都介绍了必要的安装步骤。

接下来,我将设置一个项目,将我的示例放入其中。我可以自行创建项目的目录结构,但我更愿意使用 Rust 附带的强大的包管理器 Cargo。参阅 Cargo 文档 ,使用 Cargo 创建一个新的二进制可执行程序很容易。只需在终端发出此命令:

$ cd ~/Documents
$ cargo new rusty_start –bin

cargo new rusty_start 命令告诉 Cargo 创建一个名为 rusty_start 的新程序目录结构。 --bin 选项告诉 Cargo,此程序将是一个二进制可执行程序。反之,如果保持此选项关闭,Cargo 将创建一个库。 实质上,将文件 main.rs 替换为 lib.rs 很简单。查看 tree 程序中的新 rusty_start 目录,我看到了以下代码:

$ cd rusty_start
$ tree .
.
??? Cargo.toml
??? src
    ??? main.rs

完美!不过,Cargo.toml 文件是什么?它是 Cargo 用来构建程序的文件。现在,它描述了程序的作者和程序的依赖项。对于本示例,我是唯一的作者,而且不需要任何外部包。

在 main.rs 中可以注意到,Cargo 还生成了一些代码:

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

这是 Rust 定义其 main 函数的方式。就现在而言,只需知道此函数会输出到终端即可。现在的问题是,我如何运行此代码? 这实际上很简单 ()。

清单 1. Rust 中的 Hello World!

$ cargo build
    Compiling …
     Finished …
$ cargo run
     Finished …
      Running …
Hello, world!

; java 或设置 CMake 相比,这无疑是非常简单的。为了更容易操作,我可以执行 cargo run ,这个命令会同时编译和执行。

安装 Rust 并运行它之后,我就可以介绍一些重要功能了。

Rust 的核心功能

本节包括两个主题:绑定变量和函数,它们使得理解后续小节中的示例变得容易得多。

了解绑定

不同于前面构建的 Hello, World! 程序,最终我需要在我的 Rust 代码中使用变量。为了将值绑定到名称,我在 main.rs 文件中使用了 let 关键字:

fn main() {
    let x = 5;
}

在此代码段中,我将值 5 绑定到了变量 x 。 但是,类型该如何处理?我之前是否说过 Rust 是一种静态类型语言?Rust 的另一个有趣事实是,它可以在编译时通过值来推断变量的类型 – 一个无需在运行时付出代价即可获得的出色功能。如果我想显式声明类型(比如一个 32 位有符号整数),我可以轻松地完成此工作:

let x: i32 = 5;

您也可以从多种原语类型中进行选择,下一节将介绍此主题。

但是,在介绍函数之前,我想使用模式来演示 Rust 的 let 绑定的一个有趣且有用的方面:

let (x, y) = (5, ‘6’); // types (i32, char)

在这里,我将整数 5 绑定到变量 x ,将字符 6 绑定到变量 y 。此技巧非常有用,而且模式在 Rust 社区中随处可见(该社区的成员被称为 Rustacean )。使用模式可以做的事情要多得多,但为了简洁起见,在此不做累述。

默认情况下,Rust 中的一切都是不可变的,这意味着您无法在以后执行更改。例如,如果我编译了这个命令:

let x: i32 = 5;
x = 6;

编译器告诉我 x 不可变。所以,要更改此变量,必须使用 mut 关键字:

let mut x: i32 = 5;
x = 6;

现在,一切恢复正常!

我要介绍的变量的最后一个方面是作用域 (scoping)。 作用域 体现的思想是,变量仅在实例化它们的一组给定的花括号 ( {} ) 内有效。从作用域外访问变量会导致出现编译错误。考虑中的代码。

清单 2. 作用域变量

fn main() {
    let x: i32 = 5; // x lives in this scope

    {
        let y: i32 = 4; // y only live in this scope.
    }
  
    println!(“x: {}, y: {}, x, y); // y is not in this scope
}

在涉及到 Rust 的所有权和借用属性时,理解作用域至关重要。现在,我们将介绍一些函数!

您的函数是什么?

变量固然很好,但如果没有随行的伙伴 – 函数,它们终将无用。每个程序至少有一个函数,在 Rust 中,该函数就是 main 函数。但是,我可以使用 fn 关键字定义新函数:

fn foo() {
}

您会问,如何采用参数?很简单:

fn print_value(value: i32) {
    println!(“The value given was: {}”, value);
}

此函数采用一个整数参数并输出它的值。展示了如何在 main.rs 中使用参数。

清单 3. 输出一个参数的值

fn main() {
    print_value(17);
}

fn print_value(value: i32) {
    println!(“The value given was: {}”, value);
}

就像使用 let 分配变量一样,参数遵循的模式是一个名称后跟一个分号 ( ; ),然后是一个类型。对于多个参数,只需使用逗号 ( , ) 分隔变量即可:

fn print_values(value_1: i32, value_2: i32) {
    println!(“Values given were: {} and {}”, value_1, value_2);
}

既然可以创建包含和不含参数的函数,我想尝试创建返回一个值的函数。如何创建一个将整数加 1 的函数?

fn increase_by_one(value: i32) -> i32 {
    value += 1
}

在 Rust 中,函数仅返回一个值,而且类型是在箭头 ( -> ) 后声明的。但是,我在何处返回 i32?有趣的事实:在 Rust 中,常见的做法是用分号结束来返回值。(该语言有一个 return 关键字,但它不经常使用。)如果我想使用 return 关键字,可以编写一个类似这样的函数:

fn divmod(x: i32, y: i32) -> (i32, i32) {
    return (x / y, x % y);
}

就这么简单!这就是绑定变量和编写函数的基础知识。正如我所提到的,Rust 拥有大量要处理的类型。我会在下一节中简要介绍。

我是您的类型吗?

像其他编程语言一样,Rust 有一个类型系统。以下各节会简要介绍这些类型。

布尔值

对于布尔类型,没有太多要讲的。Rust 有一种内置的布尔类型( bool ),该类型有两个值: truefalse 。这是一个示例:

let x = true;
let y: bool = !x; // y == false

字符

bool 一样,Rust 有一种表示单一 Unicode 标量值的类型: char 。但与大多数语言不同, char 不是一种填入数字值的类型。字符使用单引号来初始化,如下所示:

let x = ‘x’;
let y: char = ‘y’;

字符串

Rust 是一种有着两种字符串类型的特殊语言: strString 。关于 str 没有特别重要的地方,因为它“未确定大小”,但是如果在它前面添加一个逻辑与符号 ( & ), &str 类型就会变得非常有用。简言之,它使用了 str 的引用 – 我将在下一节中介绍此主题。 String&str 的由堆分配的对象版本。

首先,让我们详细分析一下 &str 。此类型称为 字符串切片 。它是固定大小的、不可变的 UTF-8 字节序列。我可以使用双引号将此类型绑定到变量:

let x = “Hello, World!”;
let y: &str = “Isn’t it a wonderful life?!”;

String 对象是可变的,我可以使用 String::from 构造函数来初始化该类型:

let x = String::from(“Hello, World”);
let y: String = String::from(“Isn’t it a wonderful life?!”);

数字

Rust 中的数字类型涵盖所有符号和大小。以下是目前类型的列表:

i8
i16
i32
i64
u8
u16
u32
u64
isize
usize
f32
f64

i 为前缀的类型表示一个有符号整数,这意味着该数字可以为负值。以 u 为前缀的类型是无符号整数。以 f 为前缀的类型是浮点数。后缀的数字表示该类型的字节大小,后缀的单词 size 表示系统的指针大小,用于访问序列数组的元素。

数组

谈到数组,我想讲讲 Rustacean 是如何表示数据序列的。基本策略是使用一个数组,比如:

let x = [1, 2, 3];
let y: [i32; 3] = [4, 5, 6];

显式声明数组的类型时,公式为 [T; N] ,其中 T 是元素的类型, N 是元素的数量。Rust 中的数组与其他语言中的数组类似,通过方括号 ( [] ) 来存取元素,但它们也有内置的函数,比如 len (数组中的元素数量):

let x = [1, 2, 3];
let s: usize = x.len(); // s == 3
let e = x[1]; // e is an i32, and e == 2;

现在,尽管数组看似很有意思,但它们不经常使用。Rust 社区最常使用的是矢量。不同于数组,矢量在堆上分配它们的数据,这非常类似于 &strString 之间的区别。 您可以使用宏 vec! 创建这些矢量,它们具有类型 Vec<T> ,其中 T 是已包含的元素的类型:

let x = vec![1, 2, 3];
let y: Vec<i32> = [4, 5, 6];

元组

最后一个 Rust 基础类型是 元组 ,它是一种有序、不可变的对象列表。它们的优势在于类型不必是统一的:

let x = (5, ‘6’);
let y: (i32, char) = (7, ‘8’);

不同于数组,这些类型必须以稍微不同的方式建立索引:

let x = (5, ‘6’);
let y: i32 = x.0; // y == 5
let z: char = x.1; // z == ‘6’

如果我想创建包含单个值的元组,可以在第一个元素后添加一个逗号。否则,会忽略圆括号:

let x: (bool) = (true,); x == (true)
let y: bool = (false); y == false

流控制

如果没有一组方便的控制关键字,很难在 Rust 中编写和编程。以下是两个重要的关键字。

If

Rust 类似于大多数其他编程语言,所以您应该熟悉中的 Rust if 语句示例。

清单 4. Rust 中的 if 语句

let x = ‘5’;

if x == ‘5’ {
    println!(“X is the char ‘5’”);
}

This can also be extended with the other keywords else if and else: 

let x = ‘5’;

if x == ‘5’ {
    println!(“X is the char ‘5’!”);
} else if x == ‘6’ {
    println!(“X is the char ‘6’!”);
} else {
    println!(“I don’t know what X is.”);
}

也可以像三元不表达式那样使用 if 代码块:

let x = ‘5’;
let y = if x == ‘5’ { 5 } else if x == ‘6’ { 6 } else { -1 };
// y == 5

此代码运行正常,因为等号 ( = ) 左侧的信息是返回一个值的表达式,它非常类似于一个函数。 请注意, if 代码块中的整数上缺少分号。

循环

同样地,如果没有循环,Rust 就不太像一种语言。该语言提供了 3 种循环方法: loopforwhile 。首先看看 loop ,它会无限循环:

loop {
    println!(“Looping for eternity!”);
}

如果您像我一样,或许并不总是喜欢无限循环。幸运的是,Rust 提供了一种通用的 while 循环,它在给定条件 () 上中断。

清单 5. Rust 中的 while 循环

let mut x = 0;

while x != 5 {
    println!(“x: {}”, x);
    x += 1; // this is equal to x = x + 1;
}

展示了如何结合使用 loopbreak 关键字,这会让循环在那一时刻停止。

清单 6. 在 Rust 中创建一个带 break 关键字的循环

let mut x = 0;

loop {
    if x == 5 {
        break;
    }

    println!(“x: {}”, x);
    x += 1;
}

最后,Rust 还拥有 for 循环。不同于其他循环,此循环不像其他大多数 C 风格的语言,这些语言使用的格式是:

for(int x = 0; x < 5; x++) {
    printf(“%d\n”, x);
}

而 Rust 采用了中的格式。

清单 7. 一种典型的 Rust for 循环(选项 1)

for x in 0..5 {
    println!(“x: {}”, x);
}

或者更通用的格式 ()...

清单 8. 一种典型的 Rust for 循环(选项 2)

for var in expression {
    …
}

... 其中 expression 可以转换为一个迭代器。在清单 7 中的示例中, 0..5 转换为一个从第一个数字开始到第二个数字结束(不含第二个数字)的迭代器。 0..5 称为一个 值域 。Rust 未遵循标准的 C 型格式,所以它可以避免处理循环的每个元素时的常见错误。此外,它通过类似矢量的对象来支持 Python 型迭代,比如在中。

清单 9. Rust 中的迭代

let x = vec![0, 1, 2, 3, 4];

for element in &x {
    println!(“element: {}”, element);
}

// prints out the elements in x: 0, 1, 2, 3, 4

&x 表示 for 循环“借用了”矢量,该概念将在下一节中介绍。

从所有者借用

传递、借用是本文以及整个 Rust 中最重要部分。巧合的是,借用也是最让新用户感到沮丧的概念。此系统使得 Rust 能够像 C 一样快但更安全地运行。

所有权

在 Rust 中,每个变量都有它绑定到的值的所有权。在变量超出作用域时,Rust 就会清理该资源。例如:

fn foo() {
    let x = String::from(“Hello, World!”);
}

在函数 foo 的作用域内创建 x 时,字符串中分配的数据会在堆中分配。 现在,当函数运行结束时, x 将超出作用域,Rust 会清理相关的字符串数据,甚至是堆中的字节。我不必再生活在 C/C++ 的痛苦中,也不需要记住包含 freedelete

另一种实际查看所有权的好方法是使用移动语义。除了每个变量都拥有其绑定的值的所有权之外,Rust 还可以确保每个资源都被绑定到单个值。换言之,如果一个资源从一个变量重新分配给另一个变量,则第一个变量不再有效 ()。

清单 10. Rust 中的移动语义

fn main() {
    let x = String::from(“Hello, World!”);
    
    println!(“{}”, x); // x is valid here

    let y = x; // the resource assigned to x is move to y
    
    println!(“{}”, x);
}

如果运行此代码,程序将会失败,而且编译器会告诉我,我尝试使用的变量 x 已转移到变量 y 。此行为也适用于函数 ()。

清单 11. Rust 函数中的移动语义

fn main() {
    let x = vec![1, 2, 3];

    take_ownership(x);

    println!(“{:?}”, x); // ignore the ‘:?’ for now.
                         // it just prints the full vector.
}

fn take_ownership(v: Vec<i32>) {
   println!(“I took this data: {:?}”, v);
}

此程序同样会失败,而且编译器会告诉我,我尝试使用的变量 x 已转移到函数 take_ownership 中的变量 v 。我可以使用中的构造来修复此问题。

清单 12. 修复编译器错误

fn main() {
    let x = vec![1, 2, 3];

    let x = print_vector(x);

    println!(“{:?}”, x);
}

fn print_vector(v: Vec<i32>) -> Vec<i32> {
   println!(“I took this data: {:?}, and returned it”, v);
   v
}

但是,这看起来很傻,不是吗?如果我想对矢量中的元素求和并返回该值,该怎么办?我真的需要返回一个包含原始矢量及其和的元组吗?不需要:这里可以采用借用概念。但是在介绍该主题之前,请记住 3 条所有权规则:

  • Rust 中的每个值都有一个称为其 所有者 的变量。
  • 一次只能有一个所有者。
  • 当所有者超出作用域时,该值将被丢弃。

引用和借用

有了所有权的明确定义后,我将深入研究另一个同样重要的概念:借用。Rust 在借用资源方面有 3 条规则:

  • 任何借用的有效期都不能超过原始所有者的作用域。
  • 在任何给定时刻,您都能不可变地借用(或 引用 )一个资源许多次。
  • 在任何给定时刻,您都能可变地借用(或 引用 )一个资源一次。

请注意,规则 2 和规则 3 是互斥的。

在小节末尾,我留下了一个关于对象所有权和函数的令人讨厌的难题,如中所示。

清单 13. 对象所有权难题

fn main() {
    let x = vec![1, 2, 3];

    let x = print_vector(x);

    println!(“{:?}”, x);
}

fn print_vector(v: &Vec<i32>) -> Vec<i32> {
   println!(“I took this data: {:?}, and returned it”, v);
   v
}

我必须经常将原始资源返回给原始所有者。此功能不符合 Rust 的语言习惯。我可以使用借用系统和引用来实现同样的目的,但没有可怕的返回结果。 ()。

清单 14. 通过引用实现借用

fn main() {
    let x = vec![1, 2, 3];

    print_vector(&x);

    println!(“{:?}”, x);
}

fn print_vector(v: &Vec<i32>) {
   println!(“I borrowed this data: {:?}”, v);
}

清单 14 中的程序能正常编译。通过在将 x 传递给 print_vectorprint_vector 的参数时向其添加一个逻辑与符号作为前缀,我解决了一个大问题。在用语习惯上,我将此称为 print_vector 借用 x 。现在,如果我想写一个函数来将值 5 推送给 x ,该怎么做?首先,我必须让 m 可变。 ()。

清单 15. 让 m 可变

fn main() {
    let mut x = vec![1, 2, 3];

    push_five(&x);

    println!(“{:?}”, x);
}

fn push_five(v: &Vec<i32>) {
    v.push(5);
}

哪里出错了?构建失败了,而且编译器抛出了错误!这是因为 print_five 的参数是不可变的。它只是一个对该资源的不可变引用。让我修改一下()。

清单 16. 修复可变构建

fn main() {
    let mut x = vec![1, 2, 3];

    push_five(&x);

    println!(“{:?}”, x);
}

fn push_five(v: &mut Vec<i32>) {
    v.push(5);
}

仍然有问题?!不错:我仅将 x 的一个不可变引用传递给了 print_five 。让我修改一下这一行:

push_five(&mut x);

大功告成。编译器最终不再报错。现在看看这 3 条规则,让我看看能否打破这些规则。在这里,我打破了规则 1()。

清单 17. 打破 Rust 规则 1

fn main() {
    let x = 4;
    let mut x_ref: &i32 = &x;

    // this works, and prints “x_ref: 4”
    println!(“x_ref: {}”, x_ref);

    {
        let y = 5;
        x_ref = &y;
    }

    // this, however, will fail, according to rule 1! 
    println!(“x_ref: {}”, x_ref);
}

编译器告诉我,借用的值 y 的寿命不足以让 x_ref 保持有效,此规则很不错,因为我在 C 中犯了很多这样的错误,而我甚至不知道!现在,我打破了规则 2 和规则 3 的互斥特性 ()。

清单 18. 打破 Rust 规则 2

fn main() {
    let mut x = 4; // needs to be mut to borrow mutable.
    let x_ref_1 = &x;
    let x_ref_2: &i32 = &x; // this is fine because we can have 
                            // multiple immutable references.

    let x_mut_ref = &mut x; // this is not fine because it
                            // breaks the rule 2 and 3 mutual
                            // exclusivity.
}

大功告成!编译器警告我,我不能既可变地借用 x ,又不可变地借用它。最后,我想打破规则 3()。

清单 19. 打破 Rust 规则 3

fn main() {
    let mut x = 4;
    let x_mut_ref_1 = &x;
    let x_mut_ref_2: &mut i32 = &x;
}

好哇!我再次打破了规则。显然这 3 条规则得到了严格执行,原因只有一个。让多个变量能够通过引用来更改资源,可能引发大量错误!Rust 确实很有趣,也很安全。

那么对象呢?

在本节中,我将介绍如何在 Rust 中创建对象。在这个面向对象的世界里,对象似乎必不可少。

结构

不同于大多数语言,Rust 没有类。相反,它拥有仅包含数据的结构。以下是 Rust 中的一种典型结构:

struct Circle {
    center: (i32, i32),
    radius: u32,
}

我们分解一下该结构,我使用关键字 struct 来声明一个自定义结构类型。添加成员变量时,我使用了键-值语法和 variable_name: type 格式。我使用逗号分隔多个成员变量。最后,不需要使用分号来终止声明,这与函数非常相像。现在,我已拥有此结构,展示了我如何使用它。

清单 20. Rust 中的结构语法

fn main() {
    let c_1 = Circle {
        center: (0, 0),
        radius: 1,
    };

    let c_2: Circle = Circle {
        center: (-1, 1),
        radius: 2,
    };

    println!(“c_1’s radius is {}”, c_1.radius);
}

struct Circle {
    center: (i32, i32),
    radius: u32,
}

首先, impl 关键字告诉 Rust,它必须将以下函数与给定类型(在本例中为 Circle )相关联。接下来,Rust 中的语言习惯是将构造函数命名为“new”。这个 new 函数被称为 关联函数 。换言之,类型被用于调用该函数,就像清单 20 中的 Circle::new 一样。

创建结构后,如何创建一些方法呢?请看。

清单 21. Rust 中的示例方法

fn main() {
    let mut c = Circle::new((0, 0), 1);

    println!(“The circle’s area is {}”, c.area());
    println!(“The circles location is {:?}”, c.center);
    
    c.move_to((-1, 1));
    
    println!(“The circles location is {:?}”, c.center);}

struct Circle {
    center: (i32, i32),
    radius: u32,
}

impl Circle {
    fn new(center: (i32, i32), radius: u32) -> Circle {
        Circle {
            center: center,
            radius: radius,
        }
    }
    
    fn area(&self) -> f64 {
        // converting self.radius, a u32, to an f64.
        let f_radius = self.radius as f64;
        
        f_radius * f_radius * 3.14159
    }
    
    fn move_to(&mut self, new_center: (i32, i32)) {
        self.center = new_center;
    }
}

添加方法似乎很直接,但这些新的 self 参数是什么?此参数通过点语法传递给该方法(如清单 21 中的 main 函数所示),而且它的值是调用该方法的对象。

Rust 中的方法有 3 个唯一变量: self&self&mut self 。回想一下关于所有权和借用的所有知识,这些变量分别将对象所有权转移给该方法,不可变地借用一个对象,再可变地借用一个对象。

现在我已拥有一个包含方法和构造函数的结构,我可以像使用其他编程语言中提供的基本对象一样使用它。还剩最后一种类型没有介绍: enum

枚举

enum 是一种表示变化状态的类型。许多 C/C++ 用户可能认为此类型非常不错,因为它被大量用于表示程序的某种特定状态。下面展示了如何使用 enum 关键字创建此类型:

enum Color {
    Red,
    Green,
    Blue,
}

这看起来很像一个 struct 声明,但没有给变量分配类型。借助这个新的 enum ,我可以分配一个该类型的变量,如所示。

清单 22. 分配变量

fn main() {
    let red = Color::Red;
    let blue: Color = Color::Blue;
}

enum Color {
    Red,
    Green,
    Blue,
}

现在,Rust 在 enum 类型上完成了一些独特的工作,比如向变量添加关联数据,但这不属于本文的介绍范畴。

其他功能

目前为止,我介绍了开展一个 Rust 项目需要掌握的所有必要知识。本节将介绍一些有助于完善这些知识的细节。

Match

match 是一个关键字,查看用 Rust 编写的程序时会经常看到它。一些人可能将它视为对 switch/case 的简单替换,或者认为 if/else 代码块是万能的,但他们错了。随着条件变复杂, if/else 代码块会严重失控,而且 CC++ 中的 switch/case 代码块饱受错误困扰(如果忘记了 default case)。提供了 match 的一个简单示例。

清单 23. match 的简单示例

fn main() {
    let x = 5;
    
    match x {
        1 => println!(“Matched to 1!”),
        2 => println!(“Matched to 2!”),
        3 => println!(“Matched to 3!”),
        4 => println!(“Matched to 4!”),
        5 => println!(“Matched to 5!”),
        6 => println!(“Matched to 6!”),
        _ => println!(“Matched to some other number!”),
    };
}

它的工作原理如下: match 接受一个表达式(比如 x )并查找与该值匹配的分支。 match 语句的每条“手臂”都采用 value => expression 格式,以冒号分隔。找到合适的“手臂”时,就会执行该分支的表达式。 _ value 是 Rust 表示“其他任何值”或“默认值”的语法。

match 的一个特殊方面在于,它强制要求您全面涵盖要匹配的给定表达式的每种可能结果。换言之,如果我要删除 _ branch ,构建将会失败,而且编译器会告知我不是 i32 的所有可能值(即从 -2,147,483,648 到 2,147,483,647 的值)都得到了处理。

match 的另一种有趣用法是与 enum 结合使用,如所示。

清单 24. 结合 enum 使用 match

fn main() {
    let color = Color::Red;

    match color {
        Color::Red => println!(“Got a red color!”),
        Color::Green => println!(“Got a green color!”),
        Color::Blue => println!(“Got a blue color!”),
    };
}

enum Color {
    Red,
    Green,
    Blue,
}

最后,由于 match 是一个表达式,所以我可以使用它分配变量,这很像我之前用作三元表达式的 if 代码块 ()。

清单 25. 使用 match 分配变量

let x = 5;
    
let s = match x {
    1 => “Matched to 1!”,
    2 => “Matched to 2!”,
    3 => “Matched to 3!”,
    4 => “Matched to 4!”,
    5 => “Matched to 5!”,
    6 => “Matched to 6!”,
    _ => “Matched to some other number!”,
};

println!(“{}”, s);

模块

您可能很震惊我还没有提到名称空间。这是因为 Rust 中没有名称空间。作为替代,Rust 采用了通过 mod 关键字创建的模块。您可以按照中所示的方式定义模块。

清单 26. 定义模块

fn main() {
    let c = Circle {
        center: (0, 0),
        radius: 1,
    };
}

mod shapes {
    struct Circle {
        center: (i32, i32),
        radius: u32,
    }
}

请注意,我将之前定义的 Circle 结构放在了 mod shapes 代码块中。这意味着 Circle 现在属于该名称空间,这正是我编译该程序时构建失败的原因。编译器警告我,类型 Circle 未在 main 的作用域内定义。我通过使用后跟双冒号的模块名称来修复该问题 ()。

清单 27. 定义 Circle

fn main() {
    let c = shapes::Circle {
        center: (0, 0),
        radius: 1,
    };
}

mod shapes {
    struct Circle {
        center: (i32, i32),
        radius: u32,
    }
}

构建仍然失败,但这一次编译器告诉我, Circle 结构是私有的。这是 Rust 中的标准,因为模块中的所有类型、变量和函数默认都是私有的。为了关闭此行为,我不得不使用 pub 关键字。给出了对 Circle 声明的修复。

清单 28. 修复 Circle 声明

mod shapes {
    pub struct Circle {
        center: (i32, i32),
        radius: u32,
    }
}

该代码修复了 Circle 隐私问题,但是现在我被警告成员变量 centerradius 是私有的。现在我将修复此问题:

mod shapes {
    pub struct Circle {
        pub center: (i32, i32),
        pub radius: u32,
    }
}

该程序终于开始编译了。但是,这回避了核心问题:我真的想将该模块放在 main.rs 文件中吗?答案是不想。我想将它放在一个单独的文件中。我将在包含 main.rs 的同一个 src 文件夹中创建一个新的 shapes.rs 文件。 在此文件中,我放入这个新的 Circle 声明。给出了这些新文件及其内容。

清单 29. 新的 shapes.rs

main.rs:
fn main() {
    let c = shapes::Circle {
        center: (0, 0),
        radius: 1,
    };
}

shapes.rs:
pub struct Circle {
    pub center: (i32, i32),
    pub radius: u32,
}

现在我已将 shapes 内容转移到 shapes.rs,我如何访问 Circle 呢?很容易,可以使用 mod 关键字找到具有给定名称的文件,将它们用作一个名称空间。给出了我的新 main.rs 文件的内容。

清单 30. main.rs 的内容

mod shapes;

fn main() {
    let c = shapes::Circle {
        center: (0, 0),
        radius: 1,
    };
}

基本上讲, mod 语句告诉 Rust 查找一个 shapes.rs 文件或一个包含 mod.rs 文件的 shapes 文件夹。如果我不想经常使用 shapes::Circle ,可以利用 Rust 的一个小诀窍: use 关键字。展示了它的使用方法。

清单 31. use 关键字

mod shapes;

use shapes::Circle;

fn main() {
    let c = Circle {
        center: (0, 0),
        radius: 1,
    };
}

现在,我可以使用 Circle 了,就像我在此作用域中声明了该结构一样。您可以在中看到更高级的用法,我在其中想要使用一个哈希映射(位于 Rust 的 std::collections 模块中)。

清单 32. 使用一个哈希映射

use std::collections::HashMap;

fn main() {
    let mut counter = HashMap::new(); // HashMap<char, i32>

    counter.insert(‘a’, 1);
}

呼!基本上完成了。要介绍的最后一点是,如何使用第三方包,也就是 Rust 所称的 Crate

Crate

现在,我已知道如何创建我自行设置的数字变量,但是,如果我想要使用一个随机变量,该怎么做?读遍 Rust 文档,我也没能找到随机变量的标准实现。所以我将使用一个第三方库。在 Google 上快速搜索可以找到 Crate rand

首先,我必须将 Cargo.toml 文件更改为中的代码。

清单 33. 针对 rand 进行了修改的 Cargo.toml

[package]
name = "rusty_start"
version = "0.1.0"
authors = ["Dylan Hicks <[email protected]>"]

[dependencies]
rand = "0.4"

通过将 rand = "0.4" 添加到我的依赖项,我告诉 Cargo 按此名称和版本来搜索该 crate,并将它编译到我的二进制程序中。 在我的 main.rs 文件中,我使用了 extern crate 关键字:

extern crate rand: 

fn main() {
    let x = 5;
}

我现在可以使用 rand crate 借助 use 关键字来生成一个随机数字,如所示。

清单 34. 使用 rand crate 生成一个随机数字

extern crate rand: 

use rand::Rng;

fn main() { 
    let mut rng = rand::thread_rng(); // random number generator
    let x = rng.get::<i32>(); // ignore the ::<i32>
                              // this just tells the rng to make
                              // an i32.

    println!(“x: {}”, x);
}

最后的思考

难以置信,我成功了!但事实是,我仅介绍了 Rust 功能的一小部分。如果启动一个需要某种系统语言的新项目,或者在 Java 或 C# 之间摇摆不定,请考虑使用 Rust 作为替代选择。Rust 快速、有趣、容易理解,比已有的其他任何语言都更安全,而且消除了尽可能多的人为错误。要了解更多信息,请访问 https://doc.rust-lang.org/book ,阅读 Mozilla 编写的有关它的著名语言的两本书。


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK