Rust 语言入门:迈出你的第一步
Rust 语言,一个近年来在软件开发领域备受瞩目的“后起之秀”,以其独特的“安全、性能、并发”优势吸引了大量开发者。它被 Stack Overflow 开发者调查连续多年评选为“最受欢迎的编程语言”,这绝非偶然。Rust 旨在解决 C/C++ 等传统系统级语言长期以来面临的内存安全问题,同时又不牺牲性能。这听起来像魔法,但它是通过一套创新性的所有权(Ownership)、借用(Borrowing)和生命周期(Lifetimes)系统实现的,这些概念在编译时强制执行内存安全,从而避免了运行时垃圾回收带来的性能开销和不可预测性。
对于初学者来说,Rust 可能不像 Python 或 JavaScript 那样“开箱即用”,它对内存和类型系统的严格要求可能会带来一定的学习曲线。但是,一旦你跨过了这个门槛,你会发现 Rust 强大的工具链、清晰的错误信息以及对构建健壮、高效软件的理念,会让你事半功倍。
本篇文章将作为你 Rust 学习之旅的第一个向导。我们将从零开始,指导你完成 Rust 开发环境的搭建,编写你的第一个 Rust 程序,并深入了解 Rust 项目管理的核心工具 Cargo。我们将详细剖析代码的每一个部分,让你对 Rust 的基本语法和结构有一个初步的认识。
准备好了吗?让我们一起踏上 Rust 的奇妙旅程!
第一步:安装 Rust
Rust 的安装非常简单,主要依赖于一个官方的工具链安装管理工具——rustup
。rustup
可以帮助你在不同的平台安装 Rust,管理不同的 Rust 版本和目标平台,并更新 Rust 工具链。
1. 下载并运行 rustup
打开你的终端或命令提示符。根据你的操作系统,执行以下命令:
对于 Linux 或 macOS:
bash
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
这条命令会下载一个脚本并执行它。脚本会引导你完成安装过程。大部分情况下,你可以直接按回车键选择默认安装选项(1) Proceed with installation (default)
)。
安装完成后,脚本会提示你需要配置环境变量,以便在任何地方都能使用 cargo
、rustc
等 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 程序。
手动编译过程解析:
- 源文件 (
main.rs
): 包含你的 Rust 源代码。 - 编译器 (
rustc
):rustc main.rs
命令调用 Rust 编译器,它会读取main.rs
文件,检查语法、类型、所有权规则等,如果一切正常,就会将其编译成机器码。 - 可执行文件 (
main
或main.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 的几个基本概念。
fn
关键字: 在 Rust 中,使用fn
关键字来声明一个函数。函数是一段可执行的代码块,用于完成特定任务。main
函数:main
函数是所有可执行 Rust 程序的入口点。当你的程序运行时,它首先会执行main
函数中的代码。每个可执行程序都必须有一个main
函数。()
: 紧跟在函数名后面的括号()
表示该函数不接受任何参数。如果函数需要参数,参数会列在括号内,后面跟着它们的类型(我们稍后会看到)。{}
: 花括号{}
用于定义函数体的代码块。所有属于main
函数的代码都包含在这对花括号之间。println!
: 这一行是实际打印文本到控制台的指令。println
是一个 Rust 的 宏 (macro)。宏在 Rust 中用于执行一些元编程任务,它们看起来像函数,但在名称后面带有一个感叹号!
。宏在编译时展开,生成实际的代码。println!
宏用于向标准输出打印一行文本,并自动在末尾添加换行符。("Hello, world!")
:这是传递给println!
宏的参数。在这种情况下,它是一个字符串字面量(string literal)。字符串字面量是用双引号""
包围的固定文本。
;
: 大多数语句在 Rust 中以分号;
结尾。它表示一个表达式的结束或一个语句的完成。然而,有些结构(如函数体的最后一个表达式,如果它作为返回值)则不以分号结尾。在println!
的例子中,它是一个语句,所以需要分号。
注释:
在 main.rs
中,你还看到了以 //
开头的行,以及用 /*
和 */
包围的文本。这些是注释。
//
: 单行注释,从//
开始到行尾。/* ... */
: 块注释,可以跨越多行。
注释是用来解释代码的,编译器会忽略它们。它们对于提高代码的可读性非常重要。
第四步:探索一些基本概念
现在你已经成功运行了第一个 Rust 程序,让我们在此基础上稍微深入一点,了解一些 Rust 的基本构建块。
1. 变量与可变性
在 Rust 中,你可以使用 let
关键字来声明一个变量。
“`rust
fn main() {
let x = 5; // 声明一个不可变的变量 x,并赋值 5
println!(“x 的值是: {}”, x);
// x = 6; // 错误:默认变量是不可变的
}
“`
运行 cargo run
或 cargo 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
)。isize
和usize
的位数取决于你的计算机架构(32位或64位)。 - 浮点类型 (Floating-Point Types):
f32
(单精度) 和f64
(双精度)。 - 布尔类型 (Boolean Type):
bool
,只有两个值:true
和false
。 - 字符类型 (Character Type):
char
,表示一个 Unicode 标量值,占用 4 个字节。
- 整数类型 (Integer Types): 带符号整数 (
- 复合类型 (Compound Types): 将多个值组合成一个类型。包括:
- 元组 (Tuples): 可以包含不同类型的值,长度固定。
let tup: (i32, f64, u8) = (500, 6.4, 1);
- 数组 (Arrays): 必须包含相同类型的值,长度固定。
let arr: [i32; 5] = [1, 2, 3, 4, 5];
- 元组 (Tuples): 可以包含不同类型的值,长度固定。
对于字符串,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 check
或 cargo 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 入门的第一个教程:
- 成功安装了 Rust 开发环境 (
rustup
)。 - 编写并运行了你的第一个 Rust 程序 “Hello, World!”(手动和使用 Cargo)。
- 了解了 Rust 项目管理的核心工具 Cargo 的基本用法。
- 初步剖析了 “Hello, World!” 代码的结构和语法。
- 学习了 Rust 中的变量声明、可变性、影子以及基本数据类型。
- 对函数的定义和使用有了更进一步的理解。
- 体验了 Rust 编译器友好的错误提示。
这仅仅是 Rust 世界的冰山一角。接下来,你将需要深入学习 Rust 最独特和强大的概念:
- 所有权 (Ownership): Rust 的核心内存管理机制,它解释了 Rust 如何在没有垃圾回收的情况下实现内存安全。
- 借用 (Borrowing): 允许你临时访问所有者的数据,而无需获取所有权。
- 切片 (Slices): 引用集合中连续的部分,例如字符串切片
&str
。 - 结构体 (Structs): 用于创建自定义的复合数据类型。
- 枚举 (Enums): 用于表示一个值可能是多种可能中的一种。
- 模式匹配 (Pattern Matching): 处理枚举和其他类型数据的强大方式。
- 错误处理 (Error Handling): Rust 如何通过
Result
和Option
枚举来强制处理可能的错误和缺失值。
推荐的学习资源:
- 《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 的更多精彩特性吧!