Rust 语言入门:第一个教程 – wiki基地


Rust 语言入门:迈出你的第一步

Rust 语言,一个近年来在软件开发领域备受瞩目的“后起之秀”,以其独特的“安全、性能、并发”优势吸引了大量开发者。它被 Stack Overflow 开发者调查连续多年评选为“最受欢迎的编程语言”,这绝非偶然。Rust 旨在解决 C/C++ 等传统系统级语言长期以来面临的内存安全问题,同时又不牺牲性能。这听起来像魔法,但它是通过一套创新性的所有权(Ownership)、借用(Borrowing)和生命周期(Lifetimes)系统实现的,这些概念在编译时强制执行内存安全,从而避免了运行时垃圾回收带来的性能开销和不可预测性。

对于初学者来说,Rust 可能不像 Python 或 JavaScript 那样“开箱即用”,它对内存和类型系统的严格要求可能会带来一定的学习曲线。但是,一旦你跨过了这个门槛,你会发现 Rust 强大的工具链、清晰的错误信息以及对构建健壮、高效软件的理念,会让你事半功倍。

本篇文章将作为你 Rust 学习之旅的第一个向导。我们将从零开始,指导你完成 Rust 开发环境的搭建,编写你的第一个 Rust 程序,并深入了解 Rust 项目管理的核心工具 Cargo。我们将详细剖析代码的每一个部分,让你对 Rust 的基本语法和结构有一个初步的认识。

准备好了吗?让我们一起踏上 Rust 的奇妙旅程!

第一步:安装 Rust

Rust 的安装非常简单,主要依赖于一个官方的工具链安装管理工具——rustuprustup 可以帮助你在不同的平台安装 Rust,管理不同的 Rust 版本和目标平台,并更新 Rust 工具链。

1. 下载并运行 rustup

打开你的终端或命令提示符。根据你的操作系统,执行以下命令:

对于 Linux 或 macOS:

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

这条命令会下载一个脚本并执行它。脚本会引导你完成安装过程。大部分情况下,你可以直接按回车键选择默认安装选项(1) Proceed with installation (default))。

安装完成后,脚本会提示你需要配置环境变量,以便在任何地方都能使用 cargorustc 等 Rust 命令。通常,它会建议你在你的 shell 配置文件(如 ~/.bashrc, ~/.zshrc, ~/.profile 等)中添加一行:

bash
source $HOME/.cargo/env

请按照脚本的提示操作,并确保在继续之前,新配置的环境变量已经生效。你可以关闭并重新打开终端,或者运行上述 source 命令使配置立即生效。

对于 Windows:

访问 Rust 官方安装页面:https://www.rust-lang.org/tools/install。下载适用于 Windows 的 rustup-init.exe 安装程序。

下载完成后,运行 rustup-init.exe。它会打开一个命令行窗口,引导你完成安装。同样,选择默认安装选项(输入 1 并按回车)通常是最好的选择。安装程序会自动配置环境变量。

安装完成后,你可能需要重启你的命令提示符或 PowerShell 窗口,以确保环境变量生效。

2. 验证安装

无论是哪种操作系统,安装完成后,打开一个新的终端或命令提示符窗口,运行以下命令来验证 Rust 是否成功安装:

bash
rustc --version
cargo --version

如果安装成功,你应该能看到 Rust 编译器 (rustc) 和 Cargo (cargo) 的版本信息,类似于:

rustc x.y.z (abcde 202x-xx-xx)
cargo x.y.z (abcdef 202x-xx-xx)

这里的 x.y.z 是版本号,后面的字符串和日期是具体的构建信息。

如果你看到了版本信息,恭喜你,Rust 开发环境已经准备就绪!rustup 默认安装了最新的稳定版 Rust 工具链,包括编译器 rustc、标准库、Cargo 包管理器以及文档等。

第二步:你的第一个 Rust 程序——“Hello, World!”

按照编程界的传统,我们的第一个程序通常是打印“Hello, World!”。在 Rust 中,这也非常简单。我们将学习两种方式:手动编译和使用 Cargo。

1. 手动编译 “Hello, World!”

这种方式能让你更清楚地看到 Rust 编译器的工作过程。

首先,在你喜欢的位置创建一个新的文件夹,比如 rust_hello。然后在这个文件夹中创建一个名为 main.rs 的文件。Rust 源文件的标准后缀是 .rs

使用任何文本编辑器打开 main.rs 文件,输入以下代码:

“`rust
// 这是我们的第一个 Rust 程序

fn main() {
// 在控制台打印 “Hello, world!”
println!(“Hello, world!”);
}
“`

保存文件。

现在,打开终端或命令提示符,切换到你创建的 rust_hello 文件夹:

bash
cd rust_hello

然后,使用 Rust 编译器 rustc 来编译这个文件:

bash
rustc main.rs

如果你没有输错代码,rustc 应该会默默地执行,没有输出任何错误信息。编译成功后,在同一个文件夹中会生成一个可执行文件。在 Linux 或 macOS 上,这个文件通常叫做 main;在 Windows 上,它叫做 main.exe

现在,运行这个可执行文件:

对于 Linux 或 macOS:

bash
./main

对于 Windows:

bash
.\main.exe

你应该能在终端看到输出:

Hello, world!

太棒了!你刚刚成功地编写并运行了你的第一个 Rust 程序。

手动编译过程解析:

  1. 源文件 (main.rs): 包含你的 Rust 源代码。
  2. 编译器 (rustc): rustc main.rs 命令调用 Rust 编译器,它会读取 main.rs 文件,检查语法、类型、所有权规则等,如果一切正常,就会将其编译成机器码。
  3. 可执行文件 (mainmain.exe): 编译生成的机器码文件,可以直接在操作系统上运行。

虽然手动编译对于简单的单文件程序是可行的,但对于更复杂的项目,你需要管理依赖、构建多个文件、运行测试等。这时,Cargo 就派上用场了。

2. 使用 Cargo 创建并运行 “Hello, World!”

Cargo 是 Rust 的构建系统和包管理器。它是 Rust 开发不可或缺的工具,可以帮助你:

  • 创建新的 Rust 项目(初始化项目结构)。
  • 编译你的项目。
  • 运行你的项目。
  • 下载和管理项目依赖的库(crates)。
  • 运行项目的测试。

强烈建议你在实际开发中使用 Cargo 来管理你的 Rust 项目。

首先,让我们用 Cargo 来创建一个新的项目。在终端中,回到你的用户主目录或者一个你希望创建项目的目录(不要在之前手动创建的 rust_hello 文件夹内创建,以免冲突)。运行以下命令:

bash
cargo new hello_cargo

这个命令会创建一个名为 hello_cargo 的新文件夹。进入这个文件夹,你会发现 Cargo 已经为你生成了一些文件和文件夹:

hello_cargo/
├── Cargo.toml
└── src/
└── main.rs

  • Cargo.toml:这是 Cargo 的配置文件,使用 TOML (Tom’s Obvious, Minimal Language) 格式编写。它包含了项目的元信息(如名称、版本、作者)以及项目的依赖。
  • src 文件夹:这是存放项目源代码的地方。Cargo 默认期望你的主源文件是 src/main.rs 或者 src/lib.rs (如果是一个库项目)。
  • src/main.rs: Cargo 已经为你生成了一个默认的 “Hello, World!” 程序。

现在,我们来看一下 Cargo 生成的 src/main.rs 文件:

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

它和我们手动创建的文件内容是完全一样的。

接下来,进入 hello_cargo 文件夹:

bash
cd hello_cargo

使用 Cargo 来运行你的程序。Cargo 会先编译代码,然后执行生成的可执行文件:

bash
cargo run

第一次运行 cargo run 时,Cargo 需要编译你的代码。这个过程可能需要一些时间。你会看到类似这样的输出:

Compiling hello_cargo v0.1.0 (~/hello_cargo)
Finished dev [unoptimized + debuginfo] target(s) in X.XXs
Running `target/debug/hello_cargo`
Hello, world!

  • Compiling hello_cargo v0.1.0 (...): Cargo 正在编译你的项目。
  • Finished dev [unoptimized + debuginfo] target(s) in X.XXs: 编译完成。默认情况下,Cargo 会生成一个开发版本(未优化,包含调试信息)。
  • Running target/debug/hello_cargo: Cargo 正在运行编译生成的可执行文件,该文件默认位于 target/debug/ 目录下。
  • Hello, world!: 程序的输出。

Cargo 常用命令:

  • cargo build: 只编译项目,生成可执行文件到 target/debug/ 目录,但不运行。
  • cargo run: 编译并运行项目。
  • cargo check: 快速检查代码是否存在编译错误,但不生成可执行文件。这比 cargo build 更快,常用于编写代码时的实时检查。
  • cargo build --release: 编译项目的发布版本,生成优化的可执行文件到 target/release/ 目录。发布版本编译速度较慢,但执行速度更快。
  • cargo run --release: 编译并运行发布版本。

Cargo 项目解析:

  • Cargo.toml: 项目清单文件。
    • [package] 部分包含项目元信息。
    • [dependencies] 部分用于声明项目依赖的外部 crate(库)。
  • src/main.rs: 可执行程序的主入口文件。
  • target/: Cargo 存放编译生成的文件(可执行文件、库文件等)的目录。debug 目录存放开发版本,release 目录存放发布版本。
  • Cargo.lock: Cargo 会生成并维护一个 Cargo.lock 文件,用来精确记录项目依赖的各个 crate 的具体版本。这保证了你在不同时间或不同机器上构建项目时,使用的依赖版本是一致的,从而确保构建的可重现性。你不应该手动修改这个文件,但应该将它提交到版本控制系统(如 Git)中。

使用 Cargo 来管理你的 Rust 项目是最佳实践,它使得项目构建、依赖管理和分享变得异常简单和高效。

第三步:剖析 “Hello, World!” 代码

现在我们来仔细看看 src/main.rs 文件中的代码:

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

虽然只有两行有效代码,但它们包含了 Rust 的几个基本概念。

  1. fn 关键字: 在 Rust 中,使用 fn 关键字来声明一个函数。函数是一段可执行的代码块,用于完成特定任务。
  2. main 函数: main 函数是所有可执行 Rust 程序的入口点。当你的程序运行时,它首先会执行 main 函数中的代码。每个可执行程序都必须有一个 main 函数。
  3. () 紧跟在函数名后面的括号 () 表示该函数不接受任何参数。如果函数需要参数,参数会列在括号内,后面跟着它们的类型(我们稍后会看到)。
  4. {} 花括号 {} 用于定义函数体的代码块。所有属于 main 函数的代码都包含在这对花括号之间。
  5. println! 这一行是实际打印文本到控制台的指令。
    • println 是一个 Rust 的 (macro)。宏在 Rust 中用于执行一些元编程任务,它们看起来像函数,但在名称后面带有一个感叹号 !。宏在编译时展开,生成实际的代码。println! 宏用于向标准输出打印一行文本,并自动在末尾添加换行符。
    • ("Hello, world!"):这是传递给 println! 宏的参数。在这种情况下,它是一个字符串字面量(string literal)。字符串字面量是用双引号 "" 包围的固定文本。
  6. ; 大多数语句在 Rust 中以分号 ; 结尾。它表示一个表达式的结束或一个语句的完成。然而,有些结构(如函数体的最后一个表达式,如果它作为返回值)则不以分号结尾。在 println! 的例子中,它是一个语句,所以需要分号。

注释:

main.rs 中,你还看到了以 // 开头的行,以及用 /**/ 包围的文本。这些是注释。

  • //: 单行注释,从 // 开始到行尾。
  • /* ... */: 块注释,可以跨越多行。

注释是用来解释代码的,编译器会忽略它们。它们对于提高代码的可读性非常重要。

第四步:探索一些基本概念

现在你已经成功运行了第一个 Rust 程序,让我们在此基础上稍微深入一点,了解一些 Rust 的基本构建块。

1. 变量与可变性

在 Rust 中,你可以使用 let 关键字来声明一个变量。

“`rust
fn main() {
let x = 5; // 声明一个不可变的变量 x,并赋值 5
println!(“x 的值是: {}”, x);

// x = 6; // 错误:默认变量是不可变的

}
“`

运行 cargo runcargo check,你会发现尝试修改 x 的值会导致编译错误。这是 Rust 的一个核心特性:变量默认是 不可变的 (immutable)。这意味着一旦给变量绑定了一个值,这个值就不能改变。

为什么 Rust 要这样设计?因为不可变性使得代码更容易理解和推理,尤其是在并发场景下,可以有效地防止数据竞争等问题。

如果你确实需要一个可变的变量,可以使用 mut 关键字来显式声明:

“`rust
fn main() {
let mut y = 10; // 声明一个可变的变量 y,并赋值 10
println!(“y 的初始值是: {}”, y);

y = 15; // 现在可以修改 y 的值了
println!("y 的新值是: {}", y);

}
“`

现在运行 cargo run,你会看到输出:

y 的初始值是: 10
y 的新值是: 15

这表明 mut 成功地让变量变得可变。在 Rust 中,你需要明确地表示你的意图是创建一个可变变量,这增强了代码的安全性。

2. 影子 (Shadowing)

Rust 允许你声明一个新的变量来隐藏 (shadow) 同名的一个旧的不可变变量。这与使用 mut 关键字使变量可变不同。使用 shadowing 时,你实际上是创建了一个全新的变量。

“`rust
fn main() {
let z = 5; // z 是不可变的

let z = z + 1; // 新的 z 隐藏了旧的 z,它的值是 6

let z = z * 2; // 新的 z 再次隐藏了旧的 z,它的值是 12

println!("z 的最终值是: {}", z);

}
“`

运行代码,输出是 z 的最终值是: 12

影子与可变性有几个区别:

  • 影子允许你使用同一个变量名,但给它赋一个全新的值,这个新值可以具有不同的类型(尽管这不常见)。而 mut 只是改变变量绑定的值,类型不能改变。
  • 影子创建了一个新的变量,旧的变量仍然存在(直到其作用域结束),只是暂时无法通过名字访问。
  • 影子可以方便地对同一个变量进行一系列转换操作,而无需发明新的变量名。

3. 数据类型(初步)

Rust 是一种静态类型语言,这意味着它在编译时必须知道所有变量的类型。然而,Rust 通常可以通过你赋的值来推断 (infer) 变量的类型,所以你通常不需要显式地指定类型。

“`rust
fn main() {
let guess = “42”.parse().expect(“不是一个数字!”); // Rust 推断 guess 的类型

let a: i32 = 10; // 显式指定类型为 i32 (32位带符号整数)

let b = 10.5; // Rust 推断 b 的类型为 f64 (64位浮点数)

let is_rust_fun = true; // Rust 推断为 bool 类型

let character = 'z'; // Rust 推断为 char 类型

}
“`

Rust 有多种内置的数据类型,大致分为两类:

  • 标量类型 (Scalar Types): 表示单个值。包括:
    • 整数类型 (Integer Types): 带符号整数 (i8, i16, i32, i64, i128, isize) 和无符号整数 (u8, u16, u32, u64, u128, usize)。isizeusize 的位数取决于你的计算机架构(32位或64位)。
    • 浮点类型 (Floating-Point Types): f32 (单精度) 和 f64 (双精度)。
    • 布尔类型 (Boolean Type): bool,只有两个值:truefalse
    • 字符类型 (Character Type): char,表示一个 Unicode 标量值,占用 4 个字节。
  • 复合类型 (Compound Types): 将多个值组合成一个类型。包括:
    • 元组 (Tuples): 可以包含不同类型的值,长度固定。let tup: (i32, f64, u8) = (500, 6.4, 1);
    • 数组 (Arrays): 必须包含相同类型的值,长度固定。let arr: [i32; 5] = [1, 2, 3, 4, 5];

对于字符串,Rust 有两种主要的字符串类型:

  • 字符串字面量 (&str):"Hello, world!",是硬编码到程序的可执行文件中的不可变的静态字符串切片。
  • String 类型: 一个可增长、可变的、拥有其数据的字符串类型,分配在堆上。在第一个教程中,我们主要使用字符串字面量,后续会详细介绍 String

println! 宏中,我们使用了 {} 作为占位符,它会打印变量的“默认”格式。对于大多数基本类型,这都能正常工作。

4. 函数(更进一步)

我们已经看到了 main 函数。现在看一个接受参数并返回值的函数:

“`rust
fn main() {
let num = 10;
let result = add_one(num); // 调用 add_one 函数

println!("{} 加一的结果是: {}", num, result);

}

// 定义一个名为 add_one 的函数
// 它接受一个类型为 i32 的参数 x
// 它返回一个类型为 i32 的值
fn add_one(x: i32) -> i32 {
x + 1 // 这是一个表达式,它的值是 x + 1
} // 函数体的最后一个表达式的值会被作为函数的返回值
“`

运行 cargo run,输出 10 加一的结果是: 11

函数解析:

  • fn add_one(x: i32) -> i32:
    • fn add_one: 声明一个名为 add_one 的函数。
    • (x: i32): 定义函数的参数列表。这里声明一个名为 x 的参数,其类型为 i32
    • -> i32: 使用箭头 -> 表示函数的返回类型。这里指定函数将返回一个 i32 类型的值。
  • { x + 1 }: 函数体。
    • x + 1: 这是一个表达式。在 Rust 中,表达式会产生一个值。
    • 注意 x + 1 后面没有分号 ;。在 Rust 中,函数体的最后一个表达式的值,如果没有以分号结尾,会被自动作为函数的返回值。如果加上分号,它就变成了一个语句,表达式的值会被丢弃,函数将返回一个空元组 ()(表示“没有有意义的值”)。

函数是组织代码的基本单元,通过参数传递数据,通过返回值输出结果。

第五步:阅读编译错误

对于初学者来说,Rust 编译器最初可能会让你感到沮丧,因为它非常严格。然而,与其将错误视为障碍,不如将它们视为 Rust 编译器在帮你写出更安全、更健壮的代码。Rust 的编译错误信息通常非常友好和有帮助,它们会告诉你哪里错了,以及如何修复它。

让我们故意制造一个错误来体验一下。修改 src/main.rs 文件,移除 main 函数末尾的右花括号:

rust
fn main() {
println!("Hello, world!");
// } <-- 移除这个括号

保存文件并运行 cargo checkcargo run。你会看到类似这样的错误信息:

``
error: expected
}--> src/main.rs:4:1
|
3 | println!("Hello, world!");
4 |
| ^ expected
}`

error: aborting due to previous error
“`

解析错误信息:

  • error: expected}: 告诉你期望在这里找到一个右花括号 }
  • --> src/main.rs:4:1: 指出错误发生的文件和位置。src/main.rs:4:1 表示在 src/main.rs 文件的第 4 行,第 1 列。
  • |: 表示行号。
  • 3 | println!("Hello, world!");: 显示错误发生位置附近的代码。
  • 4 |: 指出错误发生的具体行。
  • | ^ expected}: 使用箭头 ^ 精确指向错误的位置,并再次说明期望找到 }

这个错误信息非常清晰,直接告诉你错误是什么,在哪里,以及如何修复(加上 })。

在 Rust 的学习过程中,请务必耐心阅读编译器给出的每一个错误和警告信息。它们是你的最佳学习伙伴!

总结与下一步

恭喜你!你已经完成了 Rust 入门的第一个教程:

  1. 成功安装了 Rust 开发环境 (rustup)。
  2. 编写并运行了你的第一个 Rust 程序 “Hello, World!”(手动和使用 Cargo)。
  3. 了解了 Rust 项目管理的核心工具 Cargo 的基本用法。
  4. 初步剖析了 “Hello, World!” 代码的结构和语法。
  5. 学习了 Rust 中的变量声明、可变性、影子以及基本数据类型。
  6. 对函数的定义和使用有了更进一步的理解。
  7. 体验了 Rust 编译器友好的错误提示。

这仅仅是 Rust 世界的冰山一角。接下来,你将需要深入学习 Rust 最独特和强大的概念:

  • 所有权 (Ownership): Rust 的核心内存管理机制,它解释了 Rust 如何在没有垃圾回收的情况下实现内存安全。
  • 借用 (Borrowing): 允许你临时访问所有者的数据,而无需获取所有权。
  • 切片 (Slices): 引用集合中连续的部分,例如字符串切片 &str
  • 结构体 (Structs): 用于创建自定义的复合数据类型。
  • 枚举 (Enums): 用于表示一个值可能是多种可能中的一种。
  • 模式匹配 (Pattern Matching): 处理枚举和其他类型数据的强大方式。
  • 错误处理 (Error Handling): Rust 如何通过 ResultOption 枚举来强制处理可能的错误和缺失值。

推荐的学习资源:

  • 《Rust 程序设计语言》(The Rust Programming Language): 这是官方的 Rust 图书,通常被称为 “The Book”。它是学习 Rust 最权威、最详细的资源。你可以通过 rustup doc --book 命令在本地打开它,或者访问官方网站的在线版本:https://doc.rust-lang.org/book/ (有中文翻译)。强烈建议你按照书中的章节顺序进行学习。
  • Rust By Example: 通过大量的代码示例来讲解 Rust 的概念:https://doc.rust-lang.org/rust-by-example/ (有中文翻译)。
  • Rust 官方文档: 使用 rustup doc 命令可以打开所有已安装工具链的本地文档。

学习任何新语言都需要时间和实践。不要害怕犯错,耐心阅读错误信息,多动手写代码。Rust 的学习曲线在开始阶段可能会稍陡峭,但一旦你掌握了所有权、借用等核心概念,后面的学习会变得更加顺畅,你也能体会到 Rust 在构建可靠、高效软件方面的独特优势。

祝你在 Rust 的学习旅程中一切顺利!继续前进,探索 Rust 的更多精彩特性吧!


发表评论

您的邮箱地址不会被公开。 必填项已用 * 标注

滚动至顶部