入门 Rust 语言:一篇快速介绍 – wiki基地


入门 Rust 语言:一篇快速介绍

引言:为何选择 Rust?

在当前的软件开发领域,我们常常面临一个艰难的选择:是追求极致的性能和底层控制(如 C/C++),还是追求开发效率和内存安全(如 Java、Python、Go 等带有垃圾回收机制的语言)?长期以来,这似乎是一个鱼与熊掌不可兼得的困境。

然而,随着技术的不断发展,一种新的编程语言崭露头角,它试图打破这一平衡,在保证内存安全的同时,提供接近 C/C++ 的性能,并且拥有现代语言的开发效率和强大的工具链。这种语言就是 Rust

Rust 由 Mozilla 开发,并在 2015 年发布 1.0 版本。它迅速获得了开发者的广泛关注和喜爱,连续多年在 Stack Overflow 开发者调查中被评为“最受喜爱”的编程语言。这并非偶然,Rust 的设计理念和特性解决了许多困扰传统系统级编程语言的问题,尤其是在内存安全、并发编程和可靠性方面。

Rust 适用于广泛的场景,包括:
* 系统编程: 操作系统、设备驱动、嵌入式系统。
* WebAssembly: 在浏览器中运行高性能代码。
* 命令行工具: 开发快速、可靠的 CLI 应用。
* 网络服务: 构建高性能、高并发的网络应用。
* 游戏开发: 引擎或游戏逻辑。
* 分布式系统: 高可靠性的后端服务。

如果你对构建高性能、可靠的软件充满热情,并且希望深入理解计算机底层原理,同时享受现代语言的开发体验,那么学习 Rust 将是一个非常有价值的投资。

本文旨在为初学者提供一个快速而全面的 Rust 入门指南。我们将探讨 Rust 的核心理念、如何安装和配置环境,并通过实践示例介绍 Rust 的基本语法和最重要的概念。

什么是 Rust?

Rust 是一种静态类型、编译型的多范式系统编程语言。它的主要设计目标是安全性(尤其是内存安全)和性能

与其他许多现代语言不同,Rust 不使用垃圾回收器 (GC) 来管理内存。相反,它通过一套独特的所有权 (Ownership) 系统、借用 (Borrowing) 规则以及生命周期 (Lifetimes) 概念,在编译时强制执行内存安全规则。这意味着,如果你的 Rust 代码通过了编译器的检查,那么它在运行时将几乎不会出现空指针引用、数据竞争、双重释放等内存相关的错误。这种“无畏并发”(Fearless Concurrency)是 Rust 的一大卖点。

同时,由于没有运行时垃圾回收的开销,Rust 程序可以拥有预测性的高性能,并且可以用于对性能和资源消耗要求极高的场景。

Rust 还拥有强大的类型系统、模式匹配、特性 (Traits) 等现代语言特性,以及一套优秀的工具链 cargo,极大地提高了开发效率。

为何学习 Rust?更深入的探讨

Rust 的受欢迎并非仅因为它“没有 GC”或“性能好”,更在于它如何解决实际问题改善开发者体验

  1. 极致的内存安全(无 GC 开销): 这是 Rust 的基石。传统系统语言(如 C/C++)容易出现内存错误,导致程序崩溃或安全漏洞。带 GC 的语言虽然安全,但 GC 会引入不可预测的停顿(Stop-the-world)和额外的内存开销。Rust 的所有权系统在编译时检查内存访问的合法性,将内存错误扼杀在摇篮里,同时不损失运行时性能。
  2. 无畏并发 (Fearless Concurrency): 并发编程是现代软件的必备能力,但也充满了挑战,尤其是数据竞争问题。Rust 的所有权和借用规则被设计用来防止数据竞争,通过类型系统保证多线程访问共享数据是安全的。如果你能写出并通过编译器的并发代码,它通常是正确且安全的。
  3. 卓越的性能: 由于没有 GC 和其他运行时开销,Rust 程序的性能通常与 C/C++ 相媲美,甚至在某些场景下因其零成本抽象而更优。
  4. 可靠性和鲁棒性: Rust 强大的类型系统和编译时检查不仅限于内存安全。它能够捕获许多逻辑错误和类型不匹配的问题,使得程序在运行时更加稳定可靠。其优秀的错误处理机制 (ResultOption) 鼓励开发者显式地处理可能出现的错误和缺失值。
  5. 强大的工具链 Cargo: Cargo 是 Rust 的官方构建工具和包管理器。它简化了项目创建、依赖管理、编译、测试、文档生成等一系列开发流程。通过 crates.io(Rust 的官方包注册中心),开发者可以轻松地共享和使用高质量的第三方库。
  6. 现代语言特性: Rust 支持模式匹配、枚举 (Enums) 和结构体 (Structs) 的强大组合、特性 (Traits) 实现多态、零成本抽象、函数式编程风格等现代语言特性,提高了代码的可读性和可维护性。
  7. 活跃和友好的社区: Rust 社区以其友好、乐于助人和包容性而闻名。官方文档(尤其是《Rust 程序设计语言》,常被称为 “The Book”)质量极高,是学习 Rust 的绝佳资源。

尽管 Rust 的学习曲线在初期可能比一些高级语言陡峭,尤其是理解所有权和借用系统,但一旦掌握了这些核心概念,开发者会发现 Rust 编译器成为了一个非常得力的助手,它会在早期捕获大量错误,节省调试时间。

开始入门:安装 Rust

学习任何编程语言的第一步通常是安装其工具链。Rust 官方推荐使用 rustup 这个工具来安装和管理 Rust 版本。rustup 允许你在不同的 Rust 版本之间切换,并轻松更新到最新版本。

步骤 1:下载并运行 rustup 安装脚本

  • 在 Linux 或 macOS 上:
    打开终端并运行以下命令:
    bash
    curl --proto '=https' --tlsv1.2 -sSF https://sh.rustup.rs | sh

    这个脚本会下载 rustup 并执行安装。它会引导你完成安装过程,通常选择默认安装即可。

  • 在 Windows 上:
    访问 Rust 官方网站的安装页面:https://www.rust-lang.org/tools/install。下载 rustup-init.exe 安装程序并运行。按照提示操作,同样选择默认安装即可。对于 Windows,你可能需要安装 C++ Build Tools,如果你的系统中没有安装 Visual Studio 或者 MinGW 的话。安装程序通常会提示你需要安装它们。

步骤 2:配置你的 Shell 环境

安装脚本完成后,它会告诉你如何配置你的 shell 环境,以便能够直接运行 cargorustc 命令。通常,这涉及将 Rust 的 bin 目录添加到系统的 PATH 环境变量中。按照安装脚本的指示操作即可。对于大多数系统,你可能需要关闭并重新打开终端,或者运行一个命令(如 source $HOME/.cargo/env)来使配置生效。

步骤 3:验证安装

打开新的终端或命令提示符窗口,运行以下命令来验证 Rust 是否成功安装:

bash
rustc --version
cargo --version

如果安装成功,这些命令将显示安装的 Rust 编译器 (rustc) 和包管理器 (cargo) 的版本信息。

至此,你已经成功安装了 Rust 开发环境。

你的第一个 Rust 程序:Hello, World!

就像学习其他语言一样,我们从经典的 “Hello, World!” 程序开始。我们将使用 cargo 来创建一个新的 Rust 项目。

步骤 1:使用 Cargo 创建新项目

在终端中,切换到你希望创建项目的目录,然后运行以下命令:

bash
cargo new hello_world

这个命令会创建一个名为 hello_world 的新目录。进入这个目录,你会看到以下文件和目录结构:

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

  • Cargo.toml 是项目的清单文件,它包含项目的元信息(如名称、版本)和依赖。
  • src 目录是源代码目录。
  • src/main.rs 是默认的入口文件,包含了 Cargo 生成的 “Hello, World!” 代码。

步骤 2:查看并理解代码 (src/main.rs)

用你喜欢的文本编辑器打开 src/main.rs 文件,你会看到如下内容:

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

让我们分解这几行代码:

  • fn main(): fn 关键字用于声明一个函数。main 是程序执行的入口函数,每个可执行的 Rust 程序都必须有一个 main 函数。
  • (): 表示 main 函数没有参数。
  • {}: 大括号标记了函数体的开始和结束。
  • println!: 这是一个宏 (macro),而不是一个普通函数。在 Rust 中,宏以 ! 结尾。println! 用于将文本打印到标准输出。
  • "Hello, world!": 这是一个字符串字面量,是我们要打印的内容。
  • ;: 大多数语句在 Rust 中以分号结束。

步骤 3:使用 Cargo 运行项目

回到终端,确保你当前目录是 hello_world 目录(也就是 Cargo.toml 文件所在的目录),然后运行:

bash
cargo run

cargo run 命令会执行两个操作:
1. 编译你的项目。如果这是你第一次运行,或者代码有改动,Cargo 会调用 rustc 编译器将 src/main.rs 编译成可执行文件。编译生成的文件通常位于 target/debug 目录下。
2. 运行编译生成的可执行文件。

你会在终端看到如下输出:

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

第一行显示 Cargo 正在编译你的项目。第二行表示编译完成。第三行显示正在运行生成的可执行文件。最后一行是程序的输出。

恭喜!你已经成功地编写、编译并运行了你的第一个 Rust 程序。

Rust 的核心概念

现在我们已经运行了第一个程序,是时候深入了解 Rust 中一些最核心和独特的概念了。理解这些概念对于掌握 Rust 至关重要,尤其是所有权和借用。

1. 所有权 (Ownership)

所有权是 Rust 最独特且最重要的概念。它是 Rust 在没有 GC 的情况下实现内存安全的关键。所有权规则管理着程序如何管理内存。

所有权规则:

  • 规则 1: Rust 中的每一个值都有一个变量,这个变量是该值的所有者 (owner)。
  • 规则 2: 在任何时刻,一个值只能有一个所有者。
  • 规则 3: 当所有者(变量)离开作用域 (scope) 时,该值将被丢弃 (dropped),占用的内存会被释放。

示例 1:变量作用域

rust
fn main() {
{ // s 在此处进入作用域
let s = String::from("hello"); // s 是一个 String 类型的值,被创建
// 在此处可以使用 s
} // s 在此处离开作用域。
// 由于 s 不再有效,它所拥有的内存被自动释放(调用 drop)
}

这里我们使用了 String 类型而不是字符串字面量("hello"),因为 String 是一个在堆上分配内存的可变字符串,更适合用来讲解所有权的概念。字符串字面量是不可变的,且通常存储在编译时已知大小的栈上或二进制文件中,所有权规则对其行为稍有不同(更像原始数据类型)。

示例 2:Move(移动)

Rust 中的赋值操作默认执行“移动”(move)。当一个变量被赋值给另一个变量时,原始变量的所有权会转移给新的变量,原始变量将失效。

“`rust
fn main() {
let s1 = String::from(“hello”);
let s2 = s1; // s1 的所有权被移动给了 s2

// println!("{}", s1); // <-- 错误!s1 在此处已经失效,不能再使用
println!("{}", s2); // s2 是新的所有者,可以正常使用

}
“`

这里的关键是,s1s2 并不仅仅是两个指向同一块内存的指针,而是所有权的转移。这避免了双重释放(两个指针都试图释放同一块内存)的问题。

对于像整数、布尔值等存储在栈上的、已知大小的原始数据类型,它们在赋值或传递时会发生“复制”(copy),而不是移动。因为复制这些类型的数据是廉价的。实现了 Copy 特性的类型会发生复制,否则发生移动。

“`rust
fn main() {
let x = 5;
let y = x; // x 是 i32 类型,实现了 Copy 特性,所以这里发生复制

println!("x = {}, y = {}", x, y); // x 和 y 都可以正常使用

}
“`

2. 借用 (Borrowing)

如果我们不想转移所有权,但又想访问数据怎么办?这就是借用的作用。通过创建引用 (references),我们可以“借用”值的所有权,而不会转移它。引用本身也需要遵循一套规则,由 Rust 编译器(通常被称为“借用检查器”)强制执行。

借用规则:

  • 在任何给定时刻,你只能拥有:
    • 一个可变引用 (&mut),或者
    • 任意数量的不可变引用 (&)。
  • 引用不能比它们指向的数据存在更长的时间(由生命周期保证,稍后简要介绍)。

示例 1:不可变借用

“`rust
fn main() {
let s = String::from(“hello”);

let len = calculate_length(&s); // 将 s 的不可变引用传递给函数

println!("The length of '{}' is {}.", s, len); // s 的所有权没有转移,可以继续使用

}

fn calculate_length(some_string: &String) -> usize { // some_string 是对 String 的不可变引用
some_string.len() // 可以访问,但不能修改 some_string
} // some_string 离开作用域,但它只是一个引用,没有所有权,所以什么都不会发生
“`

函数参数 &String 表示它接受一个 String 的不可变引用。在函数内部,可以通过引用访问原始数据,但不能修改它。原始变量 s 仍然拥有其值的所有权。

示例 2:可变借用

“`rust
fn main() {
let mut s = String::from(“hello”); // 需要将 s 声明为 mut(可变的)

change(&mut s); // 将 s 的可变引用传递给函数

println!("{}", s); // s 的值已被修改

}

fn change(some_string: &mut String) { // some_string 是对 String 的可变引用
some_string.push_str(“, world”); // 可以修改所引用的 String
}
“`

函数参数 &mut String 表示它接受一个 String 的可变引用。在函数内部,可以通过引用修改原始数据。

示例 3:借用规则冲突

试图同时拥有多个可变引用或同时拥有可变引用和不可变引用会导致编译错误:

“`rust
fn main() {
let mut s = String::from(“hello”);

let r1 = &mut s;
// let r2 = &mut s; // <-- 错误!不能同时有两个可变引用

// println!("{}, {}", r1, r2);

let r3 = &s; // 不可变引用
let r4 = &s; // 另一个不可变引用,没问题
// let r5 = &mut s; // <-- 错误!不能在拥有不可变引用的同时拥有可变引用

// println!("{}, {}, {}", r3, r4, r5);

// 正确的做法:让引用离开作用域后再创建新的引用
{
    let r1 = &mut s;
    println!("{}", r1);
} // r1 离开作用域,现在可以创建新的引用了

let r2 = &mut s; // 没问题
println!("{}", r2);

}
“`

借用检查器是 Rust 编译器的核心部分,它在编译时分析你的代码,确保所有引用都是有效的,从而避免了运行时的数据竞争和悬垂指针等问题。理解并遵循借用规则是学习 Rust 的挑战之一,但也正是 Rust 强大安全保障的来源。

3. 生命周期 (Lifetimes)

生命周期是一个有点复杂的概念,尤其对于初学者。简单来说,生命周期的作用是确保引用是有效的——即确保引用不会比它指向的数据存在更长时间。生命周期标注告诉编译器不同引用的有效范围是如何相互关联的。

在很多情况下,编译器可以推断出生命周期(即“生命周期省略”),你不需要显式标注。但在某些复杂场景(如函数返回引用、结构体包含引用)下,你需要手动添加生命周期标注来帮助编译器理解引用的有效性。

示例(了解其目的即可):

“`rust
// 这个函数接受两个字符串切片的引用,返回其中较长的那个
// 生命周期标注 <‘a> 表示函数参数和返回值拥有相同的生命周期 ‘a
// 这意味着返回的引用不会比传入的两个引用中的任何一个活得更长
fn longest<‘a>(x: &’a str, y: &’a str) -> &’a str {
if x.len() > y.len() {
x
} else {
y
}
}

fn main() {
let string1 = String::from(“abcd”);
let string2 = “xyz”;

let result = longest(string1.as_str(), string2);
println!("The longest string is {}", result);

// 另一个例子:确保返回的引用在数据有效期间使用
let string3 = String::from("long string is long");
{
    let string4 = String::from("xyz");
    let result2 = longest(string3.as_str(), string4.as_str());
    println!("The longest string is {}", result2);
} // string4 在此处离开作用域并被释放。
  // 如果 result2 引用了 string4,那将是无效的。
  // 但由于生命周期规则,longest 函数返回的引用不可能比 string4 活得更长,
  // 所以 result2 在这里也是有效的,因为它引用的是 string3,string3 还没离开作用域。

// 假设我们试图返回一个引用到函数内部创建的数据(这是不允许的)
// fn dangle() -> &String { // <-- 编译错误!
//     let s = String::from("hello");
//     &s // s 将在函数结束时被丢弃,返回一个悬垂引用
// }
// Rust 的借用检查器和生命周期规则会阻止这种情况。

}
“`

生命周期主要用于确保引用不失效,是 Rust 内存安全的重要组成部分。虽然初学时不一定需要完全掌握所有生命周期细节,但理解其存在的目的是很有帮助的。

4. 数据类型

Rust 是一种静态类型语言,这意味着在编译时必须知道所有变量的类型。 Rust 提供了丰富的内置类型和自定义类型方式。

标量类型 (Scalar Types): 代表单个值。
* 整数类型: i8, i16, i32, i64, i128 (有符号) 和 u8, u16, u32, u64, u128 (无符号);isize, usize (与 CPU 架构相关的指针大小)。默认通常是 i32
* 浮点类型: f32 (单精度), f64 (双精度,默认)。
* 布尔类型: bool (truefalse)。
* 字符类型: char (Unicode 字符,4 字节)。

rust
let an_integer: i32 = 10;
let a_float = 3.14; // 默认 f64
let is_rust_fun = true;
let a_char = '🦀'; // Rust吉祥物

复合类型 (Compound Types): 将多个值组合成一个类型。
* 元组 (Tuple): 固定长度的有序集合,可以包含不同类型的值。
rust
let tup: (i32, f64, u8) = (500, 6.4, 1);
let (x, y, z) = tup; // 解构元组
println!("The value of y is: {}", y);
let five_hundred = tup.0; // 使用索引访问元素

* 数组 (Array): 固定长度的同类型值集合。
rust
let a: [i32; 5] = [1, 2, 3, 4, 5];
let first = a[0];
// let index = 10;
// let element = a[index]; // <-- 运行时 panic!如果索引越界,Rust 会检查并崩溃

特殊类型:
* 字符串: Rust 有两种主要的字符串类型:
* String: 在堆上分配的可增长、可变的字符串类型。通常用于需要拥有或修改字符串的场景。
* &str: 字符串切片 (string slice)。通常是固定长度、不可变的,指向其他字符串(如 String 或字符串字面量)的一部分或全部。&str 经常作为函数参数使用,因为它轻量且灵活。
理解 String&str 之间的区别及其所有权/借用关系是 Rust 学习中的一个重点。
* Slice (切片): 除了字符串切片,还可以创建其他集合(如数组或 Vec)的切片 &[T],它们允许你引用集合的一部分,而无需拥有所有权。

5. 控制流

Rust 提供了标准的控制流结构。

  • if/else 条件判断。Rust 中的 if 是一个表达式,可以返回一个值。
    “`rust
    let number = 3;
    if number < 5 {
    println!(“condition was true”);
    } else {
    println!(“condition was false”);
    }

    let condition = true;
    let number = if condition { 5 } else { 6 }; // if 作为表达式
    println!(“The value of number is: {}”, number);
    ``
    条件必须是
    bool` 类型。

  • loop 无限循环,可以使用 break 跳出循环,continue 跳过当前迭代。loop 也可以返回一个值。
    rust
    let mut counter = 0;
    let result = loop {
    counter += 1;
    if counter == 10 {
    break counter * 2; // 使用 break 返回一个值
    }
    };
    println!("The result is {}", result);

  • while 带条件的循环。
    rust
    let mut number = 3;
    while number != 0 {
    println!("{}!", number);
    number -= 1;
    }
    println!("LIFTOFF!!!");

  • for 遍历集合的循环,通常与范围 a..ba..=b 结合使用,或者遍历实现了 Iterator 特性的集合。
    “`rust
    let a = [10, 20, 30, 40, 50];
    for element in a.iter() { // 遍历数组元素
    println!(“the value is: {}”, element);
    }

    for number in (1..4).rev() { // 遍历范围并倒序
    println!(“{}!”, number);
    }
    println!(“LIFTOFF!!!”);
    “`

  • match 强大的模式匹配操作符,类似于其他语言的 switch,但更灵活和强大,并且是穷尽性的(编译器会检查你是否覆盖了所有可能的模式)。
    “`rust
    enum Coin {
    Penny,
    Nickel,
    Dime,
    Quarter(UsState), // 枚举变体可以携带数据
    }

    enum UsState { // 假设的枚举
    Alabama,
    Alaska,
    // … 很多很多州 …
    }

    fn value_in_cents(coin: Coin) -> u8 {
    match coin {
    Coin::Penny => 1,
    Coin::Nickel => 5,
    Coin::Dime => 10,
    Coin::Quarter(state) => { // 匹配包含数据的变体
    println!(“State quarter from {:?}!”, state);
    25
    },
    }
    }

    let coin = Coin::Quarter(UsState::Alabama);
    let cents = value_in_cents(coin);
    println!(“This coin is worth {} cents.”, cents);

    // match 的强大之处还在于它可以匹配 Option 和 Result 类型,用于优雅地处理可能缺失或失败的值。
    let five = Some(5);
    let six = plus_one(five);
    let none = plus_one(None);

    fn plus_one(x: Option) -> Option {
    match x {
    None => None, // 必须覆盖 None 的情况
    Some(i) => Some(i + 1),
    }
    }

    println!(“{:?}”, six); // Output: Some(6)
    println!(“{:?}”, none); // Output: None
    ``match` 结合枚举是 Rust 中处理复杂数据和状态的强大模式。

6. 函数

使用 fn 关键字定义函数。函数可以有参数和返回值。

“`rust
fn main() {
println!(“Hello, world!”);
another_function(5, ‘h’); // 调用函数

let x = five();
println!("The value of x is: {}", x);

let y = plus_one(5);
println!("The value of y is: {}", y);

}

fn another_function(x: i32, y: char) { // 参数需要类型标注
println!(“Another function.”);
println!(“The value of x is: {}”, x);
println!(“The value of y is: {}”, y);
}

fn five() -> i32 { // -> i32 表示函数返回一个 i32 类型的值
5 // Rust 中,函数体中的最后一个表达式的值会被隐式作为返回值。注意:没有分号!
// 如果这里写成 5; 就会变成一个语句,没有返回值。
}

fn plus_one(x: i32) -> i32 {
x + 1 // 表达式作为返回值
}
“`

7. 结构体 (Structs) 和枚举 (Enums)

结构体和枚举是创建自定义数据类型的方式。

  • 结构体 (Structs): 将多个相关值打包成一个有意义的结构。
    “`rust
    // 定义结构体
    struct User {
    username: String,
    email: String,
    sign_in_count: u64,
    active: bool,
    }

    fn main() {
    // 创建结构体实例
    let mut user1 = User { // 标记 mut 使整个结构体可变
    email: String::from(“[email protected]”),
    username: String::from(“someusername123”),
    active: true,
    sign_in_count: 1,
    };

    // 访问和修改字段
    user1.email = String::from("[email protected]");
    
    println!("User 1 email: {}", user1.email);
    
    // 结构体更新语法 (使用 ..)
    let user2 = User {
        email: String::from("[email protected]"),
        username: String::from("anotherusername567"),
        ..user1 // 使用 user1 的其他字段的值
    };
    
    // 元组结构体 (Tuple Structs)
    struct Color(i32, i32, i32);
    struct Point(i32, i32, i32);
    
    let black = Color(0, 0, 0);
    let origin = Point(0, 0, 0);
    // 注意:black 和 origin 是不同的类型,即使它们内部都是三个 i32
    

    }
    “`

  • 枚举 (Enums): 定义一个可以通过枚举的多个可能状态中的一个来表示的类型。枚举的强大之处在于它们的变体 (variants) 可以携带不同类型和数量的数据。
    “`rust
    enum Message {
    Quit, // 不带数据
    Move { x: i32, y: i32 }, // 带匿名结构体
    Write(String), // 带一个 String
    ChangeColor(i32, i32, i32), // 带三个 i32
    }

    fn main() {
    let msg1 = Message::Quit;
    let msg2 = Message::Move { x: 10, y: 20 };
    let msg3 = Message::Write(String::from(“hello”));
    let msg4 = Message::ChangeColor(0, 160, 255);

    // 通常结合 match 使用枚举
    fn process_message(msg: Message) {
        match msg {
            Message::Quit => {
                println!("Quitting...");
            }
            Message::Move { x, y } => {
                println!("Moving to ({}, {})", x, y);
            }
            Message::Write(text) => {
                println!("Writing: {}", text);
            }
            Message::ChangeColor(r, g, b) => {
                println!("Changing color to ({}, {}, {})", r, g, b);
            }
        }
    }
    
    process_message(msg3);
    process_message(msg2);
    

    }
    “`

8. 方法 (Methods) 和关联函数 (Associated Functions)

可以在结构体或枚举上定义方法和关联函数,使用 impl 块。

  • 方法 (Methods): 函数的第一个参数是 &self (不可变引用)、&mut self (可变引用) 或 self (获取所有权)。它们通过 . 语法调用。
    “`rust
    struct Rectangle {
    width: u32,
    height: u32,
    }

    impl Rectangle { // 为 Rectangle 实现方法和关联函数
    // 方法:计算面积
    fn area(&self) -> u32 { // 接收不可变引用
    self.width * self.height
    }

    // 方法:判断是否能容纳另一个矩形
    fn can_hold(&self, other: &Rectangle) -> bool { // 接收另一个 Rectangle 的不可变引用
        self.width > other.width && self.height > other.height
    }
    
    // 关联函数:创建一个正方形(不接收 self 参数)
    fn square(size: u32) -> Rectangle {
        Rectangle {
            width: size,
            height: size,
        }
    }
    

    }

    fn main() {
    let rect1 = Rectangle {
    width: 30,
    height: 50,
    };
    let rect2 = Rectangle {
    width: 10,
    height: 40,
    };
    let rect3 = Rectangle {
    width: 50,
    height: 20,
    };

    println!("The area of the rectangle is {} square pixels.", rect1.area()); // 调用方法
    
    println!("Can rect1 hold rect2? {}", rect1.can_hold(&rect2)); // 调用方法
    println!("Can rect1 hold rect3? {}", rect1.can_hold(&rect3));
    
    let sq = Rectangle::square(25); // 调用关联函数(使用 :: 语法)
    println!("The area of the square is {} square pixels.", sq.area());
    

    }
    “`

9. 错误处理

Rust 没有传统的异常 (exceptions)。它使用 Result<T, E>Option<T> 这两种枚举来处理可恢复和不可恢复的错误。

  • panic! 用于不可恢复的错误,程序会打印错误信息并退出。
    rust
    // panic!("crash and burn"); // 会导致程序崩溃
    // let v = vec![1, 2, 3];
    // v[99]; // 访问越界,默认会 panic!

  • Result<T, E> 用于可恢复的错误。Result 是一个枚举,有两个变体:

    • Ok(T):表示操作成功,并返回一个成功的值 T
    • Err(E):表示操作失败,并返回一个错误值 E
      “`rust
      use std::fs::File;
      use std::io::ErrorKind;

    fn main() {
    let greeting_file_result = File::open(“hello.txt”); // File::open 返回一个 Result

    let greeting_file = match greeting_file_result {
        Ok(file) => file, // 如果成功,得到 File 句柄
        Err(error) => match error.kind() { // 如果失败,根据错误类型进一步处理
            ErrorKind::NotFound => match File::create("hello.txt") { // 文件不存在,尝试创建
                Ok(fc) => fc, // 创建成功
                Err(e) => panic!("Problem creating the file: {:?}", e), // 创建失败,panic!
            },
            other_error => panic!("Problem opening the file: {:?}", other_error), // 其他错误,panic!
        },
    };
    // 现在 greeting_file 是一个有效的 File 句柄
    

    }
    “`

  • ? 运算符: 是处理 Result 的语法糖,用于传播错误。如果 ResultErr? 会立即从当前函数返回这个 Err;如果 ResultOk? 会解包 Ok 中的值继续执行。注意:? 只能用于返回 ResultOption 或实现了 FromResidual 特性的函数内部。
    “`rust
    use std::fs::File;
    use std::io::{self, Read};

    fn read_username_from_file() -> Result { // 函数返回一个 Result
    let mut username_file = File::open(“hello.txt”)?; // 使用 ? 传播错误
    let mut username = String::new();
    username_file.read_to_string(&mut username)?; // 使用 ? 传播错误
    Ok(username) // 返回成功的 username 字符串
    }

    fn main() {
    match read_username_from_file() {
    Ok(username) => println!(“Username: {}”, username),
    Err(e) => println!(“Error reading username: {}”, e),
    }
    }
    ``?` 极大地简化了错误处理代码。

  • Option<T> 用于可能缺失的值。Option 是一个枚举,有两个变体:

    • Some(T):表示存在一个值 T
    • None:表示没有值。
      “`rust
      fn main() {
      let some_number = Some(5);
      let some_char = Some(‘a’);
      let absent_number: Option = None; // 需要类型标注,因为编译器无法推断 None 的类型

      // 通常结合 match 或 if let 使用
      let x: i32 = 5;
      let y: Option = Some(5);

      // let sum = x + y; // <– 错误!不能直接将 i32 和 Option 相加

      // 使用 match 处理 Option
      match y {
      Some(i) => println!(“y is Some({})”, i),
      None => println!(“y is None”),
      }

      // 使用 if let 处理 Option(当只关心 Some 的情况时)
      if let Some(i) = y {
      println!(“y is Some({}) in if let block”, i);
      }

      // Option 也可以使用 ? 运算符,用于提前返回 None
      fn first_char(s: &str) -> Option {
      s.chars().next() // chars() 返回一个迭代器,next() 返回 Option
      }

      let first = first_char(“hello”); // Some(‘h’)
      let nothing = first_char(“”); // None
      }
      ``Option` 强制开发者考虑值可能缺失的情况,避免了空指针引用的问题。

10. 特性 (Traits) 和泛型 (Generics)

  • 特性 (Traits): 定义共享行为的抽象。类似于其他语言的接口或抽象基类,但更灵活。任何实现了特定 trait 的类型都可以使用该 trait 定义的方法。
    “`rust
    pub trait Summary {
    fn summarize(&self) -> String;

    // 可以提供默认实现
    fn summarize_author(&self) -> String {
        String::from("Read more...")
    }
    

    }

    pub struct NewsArticle {
    pub headline: String,
    pub location: String,
    pub author: String,
    pub content: String,
    }

    // 为 NewsArticle 实现 Summary trait
    impl Summary for NewsArticle {
    fn summarize(&self) -> String {
    format!(“{}, by {} ({})”, self.headline, self.author, self.location)
    }
    // 这里没有实现 summarize_author,所以使用默认实现
    }

    pub struct Tweet {
    pub username: String,
    pub content: String,
    pub reply: bool,
    pub retweet: bool,
    }

    // 为 Tweet 实现 Summary trait
    impl Summary for Tweet {
    fn summarize(&self) -> String {
    format!(“{}: {}”, self.username, self.content)
    }
    fn summarize_author(&self) -> String { // 覆盖默认实现
    format!(“@{}”, self.username)
    }
    }

    // 使用实现了 Summary trait 的类型作为函数参数
    pub fn notify(item: &impl Summary) { // trait bound 语法糖
    println!(“Breaking news! {}”, item.summarize());
    }

    // 等同于 trait bound 语法
    // pub fn notify(item: &T) {
    // println!(“Breaking news! {}”, item.summarize());
    // }

    fn main() {
    let tweet = Tweet {
    username: String::from(“horse_ebooks”),
    content: String::from(“of course, as you probably already know, people”),
    reply: false,
    retweet: false,
    };

    let article = NewsArticle {
        headline: String::from("Penguins win the Stanley Cup Championship!"),
        location: String::from("Pittsburgh, PA"),
        author: String::from("Iceburgh"),
        content: String::from("The Pittsburgh Penguins once again are the best hockey team in the NHL."),
    };
    
    println!("Tweet summary: {}", tweet.summarize());
    println!("Tweet author: {}", tweet.summarize_author()); // 使用覆盖的实现
    println!("Article summary: {}", article.summarize());
    println!("Article author: {}", article.summarize_author()); // 使用默认实现
    
    notify(&tweet); // 传入实现了 Summary 的 Tweet
    notify(&article); // 传入实现了 Summary 的 NewsArticle
    

    }
    “`
    Traits 是 Rust 实现多态和编写通用代码的关键。

  • 泛型 (Generics): 允许你在定义函数、结构体、枚举或方法时使用抽象的类型参数,从而编写可以处理多种类型的代码,同时在编译时保持类型安全。
    “`rust
    // 查找列表中最大的元素,使用泛型
    // T 必须实现 PartialOrd (可偏序) 和 Copy 或 Clone (取决于如何处理元素) trait
    // fn largest(list: &[T]) -> T { // T 实现了 PartialOrd 和 Copy
    // fn largest(list: &[T]) -> T { // T 实现了 PartialOrd 和 Clone
    fn largest(list: &[T]) -> &T { // 返回引用,不需要 Copy/Clone T 本身
    let mut largest = &list[0];

    for item in list.iter() {
        if item > largest {
            largest = item;
        }
    }
    
    largest
    

    }

    fn main() {
    let number_list = vec![34, 50, 25, 100, 65];
    let result = largest(&number_list);
    println!(“The largest number is {}”, result);

    let char_list = vec!['y', 'm', 'a', 'q'];
    let result = largest(&char_list);
    println!("The largest char is {}", result);
    

    }
    “`
    泛型提高了代码的复用性,同时通过编译器检查保证类型安全,避免了运行时的类型转换错误。

11. 模块 (Modules) 和 Crates

Rust 代码通过模块和 crates 来组织。

  • Crate: 是 Rust 中的编译单元。一个 crate 可以是一个可执行文件(二进制 crate)或一个库(库 crate)。cargo new 命令默认会创建一个二进制 crate。
  • Module: 在 crate 内部组织代码,用于控制作用域和私有性。使用 mod 关键字定义。默认情况下,所有项(函数、结构体、枚举等)在模块中是私有的。使用 pub 关键字使其公开。使用 use 关键字将其他模块中的项引入当前作用域。

“`rust
// src/main.rs 是 crate 的根
mod greetings { // 定义一个模块
pub fn english() { // 公开函数
println!(“Hello!”);
}

fn spanish() { // 私有函数
    println!("Hola!");
}

pub mod formal { // 定义一个嵌套的公开模块
    pub fn english() {
        println!("Good day!");
    }
}

mod informal { // 私有嵌套模块
    fn spanish() {
        println!("¿Qué onda?");
    }
}

}

fn main() {
greetings::english(); // 访问公开模块中的公开函数
// greetings::spanish(); // <– 错误!spanish 是私有的
greetings::formal::english(); // 访问嵌套公开模块中的公开函数
// greetings::informal::spanish(); // <– 错误!informal 是私有的

// 使用 use 引入到当前作用域
use greetings::english;
english(); // 现在可以直接使用 english()

}
“`

模块系统允许你将大型项目组织成小的、可管理的单元,并明确控制哪些部分是公共 API,哪些是内部实现细节。

Cargo:Rust 的好帮手

前面我们已经使用了 cargo newcargo run。Cargo 不仅仅是一个简单的构建工具,它还是 Rust 生态系统的核心,负责依赖管理、测试运行、文档生成等。

一些常用的 Cargo 命令:
* cargo new <project_name>:创建新的项目。
* cargo build:编译当前项目。生成的可执行文件或库位于 target/debug/ 目录下。
* cargo build --release:以发布模式编译项目,进行优化。生成的文件位于 target/release/ 目录下。
* cargo run:编译并运行项目。
* cargo check:快速检查代码,只编译但不生成可执行文件。非常适合在编写代码过程中快速检查语法和借用错误。
* cargo test:运行项目中的所有测试。
* cargo fmt:格式化 Rust 代码(需要安装 rustfmt 组件:rustup component add rustfmt)。
* cargo clippy:运行一个 linter 工具,检查代码中常见的错误和不良风格(需要安装 clippy 组件:rustup component add clippy)。
* cargo add <crate_name>:添加依赖库(需要安装 cargo-edit 工具:cargo install cargo-edit)。
* cargo publish:将库发布到 crates.io

Cargo.toml 文件用于管理项目的依赖。你可以通过在 [dependencies] 部分添加项来引入第三方库。Cargo 会自动从 crates.io 下载并构建这些依赖。

“`toml

Cargo.toml

[package]
name = “my_project”
version = “0.1.0”
edition = “2021” # Rust 版本,通常使用最新稳定版

[dependencies]

添加一个名为 rand 的依赖,版本号 ^0.8

rand = “0.8”
“`

接下来学什么?

本文仅仅是 Rust 语言的快速介绍,涵盖了最核心的概念。Rust 还有很多其他强大的特性和更高级的话题,例如:

  • 闭包 (Closures): 匿名函数。
  • 迭代器 (Iterators): 处理序列的强大模式。
  • 智能指针 (Smart Pointers): Box<T>, Rc<T>, Arc<T>, RefCell<T>, Cow<T> 等,用于更灵活的内存管理和共享所有权。
  • 并发 (Concurrency): 线程、消息传递 (channels)、共享状态并发等。
  • FFI (Foreign Function Interface): 调用其他语言(如 C)的代码。
  • 宏 (Macros): 编写元编程代码。
  • 不安全 Rust (Unsafe Rust): 在某些情况下绕过 Rust 的安全保证(需要谨慎使用)。
  • 更高级的 Trait 用法、关联类型 (Associated Types)、Trait 对象 (Trait Objects) 等。

继续深入学习 Rust 的最佳资源是官方提供的:

  1. 《Rust 程序设计语言》(The Rust Programming Language): 这是 Rust 官方提供的免费在线书籍,内容权威、全面且易于理解,被称为 Rust 的“圣经”。强烈建议仔细阅读这本书。
  2. Rust By Example: 通过大量的可运行代码示例来学习 Rust。
  3. Rustlings: 一个通过解决小型练习题来学习 Rust 的交互式教程。

实践是学习编程语言的关键。尝试使用 Rust 解决一些小问题,编写一些命令行工具,或者参与开源项目。不要害怕编译器错误,它们是你的朋友,会帮助你理解 Rust 的规则。

总结

Rust 是一门令人兴奋的编程语言,它成功地在性能、安全性和开发效率之间找到了一个优秀的平衡点。通过其独特的所有权、借用和生命周期系统,Rust 在编译时提供了强大的内存安全保证,消除了许多常见的运行时错误,同时避免了垃圾回收器的开销。

虽然入门 Rust 可能需要一些时间和精力来适应其核心概念,特别是借用检查器的工作方式,但一旦掌握了这些,你将能够编写出高性能、可靠且易于维护的代码。Cargo 工具链的强大功能也极大地提升了开发体验。

希望这篇快速介绍为你打开了通往 Rust 世界的大门。现在,勇敢地迈出下一步,开始你的 Rust 编程之旅吧!祝你学习愉快!

发表评论

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

滚动至顶部