说明

关于参考的教程

The Rust Programming Language是英文版本的由于本人英语水平相当的有限,所以我们这里直接去看翻译好的版本Rust 程序设计语言.下面我们将两个地址全部都贴出来.
The Rust Programming Language
Rust 程序设计语言
Rust 官方文档中文教程

教程中提到的一些前置

翻译已参照最新的 Rust 1.58.0 版及开发版进行调整,这是目前网上最新的中文版本,最后更新时间 2022 年 2 月 6 日。
本书的版本假定你使用 Rust 1.58(2022 年 1 月 13 日发布)或更高版本。

关于书写

这里我并不会安装这个教程上的通篇书写因为那样没有意义.我写这个文章纯粹是为了自己学习的记录.

为什么学习Rust

对我来说为什么要学习Rust.

说说我的经历吧,刚入行时搞运维后来转到测试再从测试转到java从java干到嵌入式.以至于现在什么都会一点点但都不多.语言那就真是学的太多了基本上什么都学过一点点.对于我这么一愚蠢至极的人来说脑子真的不够用了.所以我决定主要研究一门编程语言来作为以后的日常工作.虽然我的想法过于天真与理想化了,但是对我来说我真的是希望自己能做到这样,不用频繁的切换语言学习新的语言因为真的笨学不会呀.

目前我了解到的rust的优势,这个语言是系统级的语言也就是这个语言的运行速度是很快,快到什么程度的基本上和c++差不多,也就是说如果你要开发的程序很注重性能的话选择rust是很不错的.另外这个语言的提供了很多内置工具,以及这个语言的编译器会帮我们解决很多的问题.再有就是这个语言的社区一直在良好的发展目前来说这个语言基本上可以解决很多问题,并且这个语言只要你能编译通过运行时基本就不会出问题,这个就相当的优秀了.不过据说这个语言的学习曲线非常的陡峭,这点确实不太友好了,不过我相信只要我们坚持坚持啃下这块硬骨头以后就都是美好了.

Rust 也为系统编程世界带来了现代化的开发工具:

  • Cargo,内置的依赖管理器和构建工具,它能轻松增加、编译和管理依赖,并使其在 Rust 生态系统中保持一致。
  • Rustfmt 确保开发者遵循一致的代码风格。
  • Rust Language Server 为集成开发环境(IDE)提供了强大的代码补全和内联错误信息功能

    环境安装

    联网安装

第一步是安装 Rust。我们将通过 rustup 来下载 Rust,这是一个管理 Rust 版本和相关工具的命令行工具。这需要互联网连接才能下载.

注意:如果你出于某些原因不想用 rustup,请参阅 Rust 其他安装方法的页面了解更多选项。

下面步骤将安装 Rust 编译器的最新稳定版本。Rust 的稳定性保证可确保本书中所有能编译的示例在更新的 Rust 版本中能够继续通过编译。不同版本之间的输出可能会略有不同,因为 Rust 经常会改进错误消息和警告。

linux或macOS安装

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

这个命令将下载一个脚本并开始安装 rustup 工具,此工具将安装 Rust 的最新稳定版本。可能会提示你输入密码。如果安装成功,将出现下面这行:

Rust is installed now. Great!

此外,你还需要一个链接器(linker),这是 Rust 用来将其编译的输出关联到一个文件中的程序。很可能你已经有一个了。如果你遇到了链接器错误,请尝试安装一个 C 编译器,其通常包括一个链接器。C 编译器也很有用,因为一些常见的 Rust 包依赖于 C 代码,因此需要安装一个 C 编译器。

在 macOS 上,可运行以下命令获得 C 编译器:

xcode-select --install

Linux 用户一般应按照相应发行版的文档来安装 GCC 或 Clang。例如,如果你使用 Ubuntu,则可安装 build-essential 包。

在windows上安装

在 Windows 上,访问 https://www.rust-lang.org/zh-CN/tools/install 页面并按照说明安装 Rust。在安装过程的某个步骤,你可能会收到一条消息,提示你还需要适用于 Visual Studio 2013 或更高版本的 C++ 的构建工具(C++ build tools)。获取这些构建工具的最简单方法是安装 Visual Studio 2019 的构建工具。当被问及要安装哪些内容时,请确保已选择 “C++ build tools”,并包括 Windows 10 SDK 和英文语言包。

更新和卸载
// 更新
rustup update
// 卸载
rustup self uninstall
// 查看版本
rustc --version

hello word

hello word rust

新建一个记事本文件,命名main.rs并在文件内键入以下内容;

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

编译运行

 rustc main.rs
./main 或 mian.exe

进行解析:
第一行定义了,当然一个完整的函数一定要把花括号进行括回,也就是最后一行, Rust 的函数。main 函数(也称为主函数)很特殊:它始终是每个可执行 Rust 程序中运行的第一个代码,这不一定,因为有的时候我们可能会引入一些外部的资源,比如我们后面要写的猜数字游戏.第一行声明一个名为 main 的函数,不带参数也没有返回值。如果有参数,那么它们的名字会放到括号内,它们将放在括号 () 内。

另外,请注意,函数主体用大括号 {} 括起来。Rust 需要函数体的所有内容都被括号包围起来。一种好的代码风格是将左大括号放在函数声明的同一行,且之间带有一个空格。

如果想在 Rust 项目中坚持标准代码风格,则可以使用自动格式化程序工具 rustfmt 来将代码格式化为特定风格。Rust 团队已将此工具包含在标准 Rust 发行版中(如 rustc),因此它应该已经安装在你的计算机上!

main函数内部代码是println!("Hello, world!");该行完成了此简单程序中的所有工作:它将文本打印到屏幕上。这里有 4 个要注意的重要细节:

  • 首先,Rust 风格的缩进使用 4 个空格,而不是制表符。
  • 其次,println! 调用 Rust 宏。如果改为调用函数,则应该将其输入为 println(不含 !)
  • 第三,你看到 "Hello, world!" 字符串。我们将这个字符串作为参数传递给 println!,接着 println! 将字符串打印到屏幕上
  • 第四,我们用分号(;,注意这是英文分号)结束该行,这表明该表达式已结束,下一个表达式已准备好开始。Rust 代码的大多数行都以一个 ; 结尾。

编译和运行时独立的步骤

在运行 Rust 程序之前,必须使用 Rust 编译器来编译它,输入 rustc 命令并传入源文件的名称.

如果有 C 或 C++ 语言基础,你会注意到这点和 gcc 或 clang 类似。编译成功后,Rust 就会输出一个二进制可执行文件.

在 Linux、macOS 或 Windows 的 PowerShell 中,可通过输入 ls 命令来查看可执行文件。在 Linux 和 macOS 中,你将看到两个文件。使用 Windows 的 PowerShell,你将看到与使用 CMD 相同的三个文件。

main  main.rs
main.exe
main.pdb
main.rs

这显示了带有 .rs 扩展名的源代码文件,可执行文件(在 Windows 上是 main.exe,在所有其他平台上是 main),以及在使用 Windows 时包含一个带有 .pdb 扩展名的调试信息的文件。

Rust 是一门预编译(ahead-of-time compiled)语言,这意味着你可以编译一个程序,将编译后的可执行文件给别人,即使他们没有安装 Rust 也可以运行程序。

使用 rustc 编译对简单的程序可以轻松胜任,但随着项目的增长,你将会想要管理项目中所有相关内容,并想让其他用户和项目能够容易共享你的代码。接下来,我们将引入 Cargo 工具,这将帮助你学会编写真实开发环境的 Rust 程序。

hello word cargo

Cargo 是 Rust 的构建系统和包管理器。大多数 Rustacean 们使用 Cargo 来管理他们的 Rust 项目,因为它可以为你处理很多任务,比如构建代码、下载依赖库,以及编译这些库。(我们把代码所需要的库叫做依赖(dependency)).

Rustacean:(锈虫)是 Rust 编程语言爱好者的

由于绝大多数 Rust 项目使用 Cargo,本书接下来的部分假设你也使用 Cargo。如果使用“安装” 部分介绍的官方安装包的话,则自带了 Cargo。如果通过其他方式安装的话,可以在终端输入如下命令检查是否安装了 Cargo:cargo --version.

如果你看到了版本号,说明安装成功!如果看到类似 command not found 的错误,你应该查看相应安装文档以确定如何单独安装 Cargo。

使用cargo创建项目

cargo new hello_cargo
cd hello_cargo

第一行命令新建了名为 hello_cargo 的目录。我们将项目命名为 hello_cargo,同时 Cargo 在一个同名目录中创建项目文件。

进入 hello_cargo 目录并列出文件。将会看到 Cargo 生成了两个文件和一个目录:一个 Cargo.toml 文件,一个 src 目录,以及位于 src 目录中的 main.rs 文件。

它也在 hello_cargo 目录初始化了一个 Git 仓库,并带有一个 .gitignore 文件。如果在现有的 Git 仓库中运行 cargo new,则不会生成 Git 文件;但你可以使用 cargo new --vcs=git 来无视此限制,强制生成 Git 文件。

注意:Git 是一个常用的版本控制系统(version control system, VCS)。可以通过 --vcs 参数使 cargo new 切换到其它版本控制系统,或者不使用 VCS。运行 cargo new --help 查看可用的选项。

使用你喜欢的文本编辑器打开 Cargo.toml 文件。它应该看起来如示例

[package]
name = "hello_cargo"
version = "0.1.0"
edition = "2021"

[dependencies]

此文件使用 TOML (Tom's Obvious, Minimal Language) 格式,这是 Cargo 配置文件的格式。

第一行,[package],是一个表块(section)标题,表明下面的语句用来配置一个包(package)。随着我们在这个文件增加更多的信息,还将增加其他表块。

接下来的三行设置了 Cargo 编译程序所需的配置:项目的名称、版本,以及使用的 Rust 大版本号(edition,区别于 version)。附录 E 会介绍 edition(译注:Rust 的核心版本,即 2015、2018、2021 版等) 的值。

最后一行,[dependencies] 是一个表块的开头,你可以在其中列出你的项目所依赖的任何包。在 Rust 中,代码包被称为 crate。

现在打开 src/main.rs 看看:

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

Cargo 生成了一个 “Hello, world!” 程序,到目前为止,之前项目与 Cargo 生成项目的区别是,Cargo 将代码放在 src 目录,同时项目根目录包含一个 Cargo.toml 配置文件。

Cargo 期望源文件位于 src 目录中。项目根目录只存放说明文件(README)、许可协议(license)信息、配置文件和其他跟代码无关的文件。使用 Cargo 可帮助你保持项目干净整洁。这里为一切事物所准备,一切都位于正确的位置。

构建并运行Cargo项目

现在让我们看看通过 Cargo 构建和运行 “Hello, world!” 程序有什么不同!在 hello_cargo 目录下,输入下面的命令来构建项目:

cargo build

这个命令会在 target/debug/hello_cargo 下创建一个可执行文件(在 Windows 上是 target\debug\hello_cargo.exe),而不是放在目前目录下。你可以使用下面的命令来运行它:

./target/debug/hello_cargo 
# 或者在 Windows 下为 
.\target\debug\hello_cargo.exe

Hello, world!

如果一切顺利,终端上应该会打印出 Hello, world!。首次运行 cargo build 时,也会使 Cargo 在项目根目录创建一个新文件:Cargo.lock。这个文件记录项目依赖的实际版本。这个项目并没有依赖,所以其内容比较少。您不需要手动更改这个文件,Cargo 会为您管理好它。

我们刚刚使用 cargo build 构建了项目,并使用 ./target/debug/hello_cargo 运行了程序,但是,我们也可以使用 cargo run 命令,一次性完成代码编译和运行的操作;

Cargo 还提供了一个名为 cargo check 的命令。该命令快速检查代码确保其可以编译,但并不产生可执行文件;

通常,cargo check 要比 cargo build 快得多,因为它省略了生成可执行文件的步骤。如果你在编写代码时不断检查你的代码,那么使用 cargo check 命令可以加快这个过程!为此很多 Rustacean 编写代码时会定期运行 cargo check 以确保它们可以编译。当准备好使用可执行文件时才运行 cargo build。

发布构建

当项目最终准备好发布时,可以使用 cargo build --release 来优化编译项目。这会在 target/release 而不是 target/debug 下生成可执行文件。这些优化可以让 Rust 代码运行的更快,不过启用这些优化也需要消耗更长的编译时间。这也就是为什么会有两种不同的配置:一种是为了开发,你需要经常快速重新构建;另一种是为用户构建最终程序,它们不会经常重新构建,并且希望程序运行得越快越好。如果你要对代码运行时间进行基准测试,请确保运行 cargo build --release 并使用 target/release 下的可执行文件进行测试。

猜数字游戏

Cargo.toml

[package]
name = "guessing_game"
version = "0.1.0"
edition = "2021"

[dependencies]
rand = "0.8.3"

main.rs

use std::io;

use rand::Rng;
use std::cmp::Ordering;
fn main() {
    println!("粘豆包编写的第一个Rust项目,猜数字游戏!");
    println!("请输入你的数字:");

    let secret_number = rand::thread_rng().gen_range(1..100);
    loop {
        let mut guess = String::new();
        io::stdin().read_line(&mut guess).expect("在读取用户输入数据时发生异常!");
        println!("用户输入的数据是: {}",guess);
        // 由于使用 guess.cmp(&secret_number) 分支发生了类型异常  这我们要在做一步 处理
        //let guess: u32 = guess.trim().parse().expect("您的输入不正确,请输入正确的数字!");
        // 处理无效输入
        let guess: u32 = match guess.trim().parse() {
            Ok(num) => num,
            Err(_) =>{ 
                println!("输入无效内容,我们跳过本次输入;");
                println!();
                println!();
                println!();
                println!();
                println!("请输入你的数字:");
                continue;
            }
        };
        // 下面开始生成随机数
        //println!("下面我们开始测试生成随机数!");
        
        //println!("生成的随机数是: {}",secret_number);
        // 下面开始 比较用户的输入 与随机数
        // 使用 循环进行测试
   
        match guess.cmp(&secret_number) {
            Ordering::Less => println!("您输入的数字小了!"),
            Ordering::Greater => println!("您输入的数字大了!"),
            Ordering::Equal => {
                println!("太棒了您的输入与神秘数字是一样的!");
                println!("本次游戏结束,我们稍后会关闭系统!");
                println!("..............");
                println!("..............");
                println!("..............");
                println!("..............");
                println!("..............");
                println!("..............");
                println!("..............");
                break;
            }
            
        }
    }
    println!("系统已关闭!");
}

程序的解析

吐槽一下,思否的代码块尽然不支持行号显示.难搞哦!

use std::io;
use rand::Rng;
use std::cmp::Ordering;

这几行代码我们都是要引用库到当前作用域,第一个就是io 输入/输出库.

下一行是我们要引入一个生成随机数的库记住,crate 是一个 Rust 代码包。我们正在构建的项目是一个 二进制 crate,它生成一个可执行文件。 rand crate 是一个 库 crate,库 crate 可以包含任意能被其他程序使用的代码,但是不能独自执行,注意这个不是标准库提供的内容所以我们需要操作Cargo.toml文件如下

rand = "0.8.3"

[dependencies] 表块中,你要告诉 Cargo 本项目依赖了哪些外部 crate 及其版本,在 [dependencies] 表块中,你要告诉 Cargo 本项目依赖了哪些外部 crate 及其版本。本例中,我们使用语义化版本 0.8.3 来指定 rand crate。Cargo 理解语义化版本(Semantic Versioning,有时也称为 SemVer),这是一种定义版本号的标准。0.8.3 实际上是 ^0.8.3 的简写,它表示任何至少包含 0.8.3 但低于 0.9.0 的版本。 Cargo 认为这些版本具有与 0.8.3 版本兼容的公有 API, 并且此规范可确保你将获得最新的补丁版本,它仍然可以与本章中的代码正常编译。0.9.0 或更高版本则不再确保 API 和以下示例所使用的 API 相同。

当我们引入了一个外部依赖后,Cargo 将从 registry 上获取所有依赖所需的最新版本,这是一份来自 Crates.io 的数据拷贝。Crates.io 是 Rust 生态环境中开发者们向他人贡献 Rust 开源项目的地方。

在更新完 registry 后,Cargo 检查 [dependencies] 表块并下载缺失的 crate 。本例中,虽然只声明了 rand 一个依赖,然而 Cargo 还是额外获取了 rand 所需的其他 crate,rand 依赖它们来正常工作。下载完成后,Rust 编译依赖,然后使用这些依赖编译项目。

Cargo.lock 文件确保构建是可重现的
Cargo 有一个机制来确保任何人在任何时候重新构建代码,都会产生相同的结果:Cargo 只会使用你指定的依赖版本,除非你又手动指定了别的。例如,如果下周 rand crate 的 0.8.4 版本出来了,它修复了一个重要的 bug,同时也含有一个会破坏代码运行的缺陷。为了处理这个问题,Rust 在你第一次运行 cargo build 时建立了 Cargo.lock 文件,我们现在可以在 guessing_game 目录找到它。

当第一次构建项目时,Cargo 计算出所有符合要求的依赖版本并写入 Cargo.lock 文件。当将来构建项目时,Cargo 会发现 Cargo.lock 已存在并使用其中指定的版本,而不是再次计算所有的版本。这使得你拥有了一个自动化的可重现的构建。换句话说,项目会持续使用 0.8.3 直到你显式地升级,多亏有了 Cargo.lock 文件。

Rng 是一个 trait,它定义了随机数生成器应实现的方法,想使用这些方法的话,此 trait 必须在作用域中

trait(特征)

在下一行是一个用于比较的库,从标准库引入了一个叫做 std::cmp::Ordering 的类型到作用域中。Ordering 也是一个枚举,不过它的成员是 Less、Greater 和 Equal。这是比较两个值时可能出现的三种结果。

let secret_number = rand::thread_rng().gen_range(1..100);

这一行我们主要是生成一个随机数,随机数的范围是1~100包含1但不包含100想要包含100可以这样写gen_range(1..=100)
我们调用 rand::thread_rng 函数来为我们提供将要使用的特定随机数生成器:它位于当前执行线程的本地环境中,并从操作系统获取 seed。然后我们调用随机数生成器的 gen_range 方法。该方法由我们刚才使用 use rand::Rng 语句引入的 Rng trait 定义。gen_range 方法获得一个区间表达式(range expression)作为参数,并在区间内生成一个随机数。我们在这里使用的区间表达式采用的格式为 start..end。它包括起始端,但排除终止端。

向下是一个loop这是一个无限循环

向下

let mut guess = String::new();

创建一个储存用户输入的变量(variable),在rust中默认变量是不可以变的,如果想让变量可变我们则需要在变量的名字前增加mut
String::new ,这个函数会返回一个 String 的新实例。String 是标准库提供的字符串类型,是一个 UTF-8 编码的可增长文本,:: 语法表明 new 是 String 类型的一个关联函数。关联函数(associated function)是实现一种特定类型的函数,在这个例子中类型是 String。这个 new 函数创建了一个新的空字符串。

向下

io::stdin().read_line(&mut guess).expect("在读取用户输入数据时发生异常!");

如果程序的开头没有使用 use std::io 引入 io 库,我们仍可以通过 std::io::stdin 来调用函数。stdin 函数返回一个 std::io::Stdin 的实例,这是一个类型,代表终端标准输入的句柄。

接下来,.read_line(&mut guess) 这一行调用了 read_line 方法,来从标准输入句柄中获取用户输入。我们还将 &mut guess 作为参数传递给 read_line(),以告诉它在哪个字符串存储用户输入。read_line 的全部工作是,将用户在标准输入中输入的任何内容都追加到一个字符串中(而不会覆盖其内容),所以它需要字符串作为参数。这个字符串应是可变的,以便该方法可以更改其内容.

& 表示这个参数是一个引用(reference),这为你提供了一种方法,让代码的多个部分可以访问同一处数据,而无需在内存中多次拷贝。引用是一个复杂的特性,Rust 的一个主要优势就是安全而简单的使用引用。

现在,我们只需知道就像变量一样,引用默认是不可变的。因此,需要写成 &mut guess 来使其可变,而不是 &guess

使用 Result 类型来处理潜在的错误
read_line 将用户输入存储到我们传递给它的字符串中,但它也返回一个值,在这个例子中是 io::Result。Rust 标准库中有很多名为 Result 的类型:一个通用的 Result 以及在子模块中的特化版本,比如 io::Result。Result 类型是 枚举(enumerations),通常也写作 enum。枚举类型持有固定集合的值,这些值被称为枚举的成员(variant)。枚举往往与条件表达式 match 一起使用,match 是一种条件语句,在其被执行时,可以方便地匹配不同枚举值来执行不同的代码。

Result 的成员是 Ok 和 Err,Ok 成员表示操作成功,且 Ok 内部包含成功生成的值。Err 成员则意味着操作失败,并且包含失败的前因后果。

Result 类型的值,就像任何类型的值一样,都有为其定义的方法。io::Result 的实例拥有 expect 方法。如果 io::Result 实例的值是 Err,expect 会导致程序崩溃,并显示传递给 expect 的参数。如果 read_line 方法返回 Err,则可能是底层操作系统引起的错误结果。如果 io::Result 实例的值是 Ok,expect 会获取 Ok 中的值并原样返回,以便你可以使用它。

向下

let guess: u32 = match guess.trim().parse() {}

这里我们解释的时候先跳过match我们先来接是后面的 guess.trim().parse()这里的作用是将用户的输入去除掉前后的空格trim()然后我们在使用parse()将输入的字符串转换为数字,注意这里要是用类型推断,所以我们一定要加上: u32

这里我们还需要考虑一种异常情况,那就是我们在输入的时候输入的是数字肯定没问题,但是当我们输入的是汉字或者是其他字的时候可能转换就会出错,这个时候就需要我们的match了.一个 match 表达式由分支(arm) 构成。一个分支包含一个用于匹配的模式(pattern),给到 match 的值与分支模式相匹配时,应该执行对应分支的代码。Rust 获取提供给 match 的值并逐个检查每个分支的模式。模式和 match 结构是 Rust 中强大的功能,它体现了代码可能遇到的多种情形,并帮助你确保没有遗漏处理.

parse 方法返回一个 Result 类型。像前面 “使用 Result 类型来处理潜在的错误” 部分讨论的 read_line 方法那样,再次按部就班地用 expect 方法处理即可。也就有了如下代码:

Ok(num) => num,
Err(_) =>{ 
         println!("输入无效内容,我们跳过本次输入;");
         println!();
         println!();
         println!("请输入你的数字:");
         continue;
        }

向下

match guess.cmp(&secret_number) {
            Ordering::Less => println!("您输入的数字小了!"),
            Ordering::Greater => println!("您输入的数字大了!"),
            Ordering::Equal => {
                println!("太棒了您的输入与神秘数字是一样的!");
                println!("本次游戏结束,我们稍后会关闭系统!");
                println!("..............");
                println!("..............");
                println!("..............");
                println!("..............");
                println!("..............");
                println!("..............");
                println!("..............");
                break;
            }
            
        }

首先我们增加了另一个 use 声明,从标准库引入了一个叫做 std::cmp::Ordering 的类型到作用域中。Ordering 也是一个枚举,不过它的成员是 Less、Greater 和 Equal。这是比较两个值时可能出现的三种结果。

cmp 方法用来比较两个值并可以在任何可比较的值上调用。它获取一个被比较值的引用:这里是把 guess 与 secret_number 做比较。 然后它会返回一个刚才通过 use 引入作用域的 Ordering 枚举的成员。使用一个 match 表达式,根据对 guess 和 secret_number 调用 cmp 返回的 Ordering 成员来决定接下来做什么。

好最后如果匹配了我们这盗用break跳出循环,也就意味着程序跳出循环执行循环下面的代码,最后程序结束,我们的分析也就到这里.

后面我们也不会再这么详细的分析代码了,可能直接在代码中直接注释,可能一笔带过.


粘豆包
1 声望0 粉丝

生命不息,战斗不止!