零基础了解 Rust:一篇中文介绍 – wiki基地


零基础了解 Rust:一篇中文介绍

欢迎来到 Rust 的世界!

如果你是第一次听说 Rust,或者虽然听过一些关于它“安全”、“快速”、“难学”的传闻,但对它究竟是什么、能做什么一无所知,那么恭喜你,你来对地方了。

这篇文章旨在为你打开 Rust 的大门,用通俗易懂的语言,从零开始介绍 Rust 的基本概念、特点以及为什么它在编程界越来越受欢迎。我们不会深入到复杂的细节,但会确保你对 Rust 的核心理念有一个清晰的认识。

准备好了吗?让我们一起踏上 Rust 的探索之旅吧!

第一章:Rust 的诞生与非凡之处

1.1 Rust 的起源:为了解决实际问题而生

Rust 是一门由 Mozilla(火狐浏览器的开发者)于 2010 年发起,并在 2015 年发布 1.0 稳定版的系统级编程语言。系统级编程语言通常指的是用于编写操作系统、嵌入式系统、高性能服务器等底层软件的语言,这类软件对性能、内存控制和可靠性要求极高。

在 Rust 诞生之前,C 和 C++ 是系统级编程领域的主流语言。它们性能卓越,提供了对硬件的底层控制能力,但也带来了著名的“内存安全”问题,比如空指针引用、数据竞争、缓冲区溢出等等。这些问题是导致软件崩溃、安全漏洞的重要原因,而且往往难以调试和修复。

Mozilla 希望找到一门能够替代 C++,既拥有 C/C++ 那样的高性能底层控制能力,又能提供内存安全保障的语言。在现有语言无法满足要求的情况下,Rust 应运而生。

1.2 Rust 的核心目标:安全、性能与并发

Rust 的设计目标非常明确:

  • 内存安全 (Memory Safety): 这是 Rust 最引以为傲的特性。Rust 在编译期就能检测出大多数内存错误,无需垃圾回收器(GC)的运行时开销,从而避免了 C/C++ 中常见的安全漏洞。
  • 高性能 (Performance): Rust 的抽象是“零开销”的。这意味着你在 Rust 中使用高级特性时,不会付出运行时性能上的代价,它的性能可以媲美 C/C++。
  • 并发 (Concurrency): Rust 的所有权系统和类型系统在编译期就能帮助你防止数据竞争,使得编写安全、高效的并发程序变得更加容易,甚至可以说是“无畏的并发 (Fearless Concurrency)”。

除了这三个核心目标,Rust 还强调:

  • 可靠性 (Reliability): 强大的类型系统和错误处理机制让你的程序更加健壮。
  • 生产力 (Productivity): 拥有优秀的工具链(如 Cargo 包管理器)、清晰的文档以及友好的编译器错误提示。

总而言之,Rust 试图在一个语言中同时实现性能、安全和并发,这在传统语言中往往是相互矛盾的(比如 Java/Python 有安全和并发但牺牲了性能,C/C++ 有性能但牺牲了安全和并发的易用性)。

第二章:安装 Rust 与你的第一个程序

学习任何编程语言的第一步通常是安装环境并运行一个简单的程序。

2.1 安装 Rust 工具链:使用 rustup

Rust 官方推荐使用 rustup 这个命令行工具来安装和管理 Rust 版本。rustup 可以让你轻松安装不同版本的 Rust 编译器(rustc)和包管理器(cargo)。

安装步骤:

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

    按照提示操作即可。安装完成后,可能需要重启终端或执行 source $HOME/.cargo/env 使环境变量生效。
  • 在 Windows 上: 访问 Rust 官方网站 (https://www.rust-lang.org/),下载并运行 rustup-init.exe 安装程序。安装程序会引导你完成安装过程。

安装完成后,你可以通过运行以下命令来验证 Rust 是否安装成功:

bash
rustc --version
cargo --version

如果显示了版本信息,说明安装成功了!

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

每个编程语言的传统入门程序都是“Hello, world!”。

  1. 创建一个文件: 在你的电脑上创建一个新文件夹(比如 rust_hello),然后在这个文件夹里创建一个名为 main.rs 的文本文件。Rust 源文件的扩展名是 .rs
  2. 编写代码: 用文本编辑器打开 main.rs 文件,输入以下代码:

    rust
    fn main() {
    // 这是注释,不会被编译
    println!("Hello, world!");
    }

    • fn main():定义了一个名为 main 的函数。在 Rust 中,main 函数是程序的入口点,就像 C/C++ 的 main 函数一样。fn 关键字用于声明函数。
    • {}:定义了函数体的作用域。
    • println!:这是一个 Rust (Macro)。宏看起来像函数,但后面带有 !。它用于在控制台打印文本。注意文本内容放在双引号 "" 里。
    • ;:在 Rust 中,大多数语句以分号结尾。
  3. 编译程序: 打开终端,导航到你创建 main.rs 文件的目录。使用 Rust 编译器 rustc 来编译你的代码:

    bash
    rustc main.rs

    如果代码没有错误,rustc 会生成一个可执行文件。在 Linux/macOS 上是 main,在 Windows 上是 main.exe

  4. 运行程序: 在终端中运行生成的可执行文件:

    • 在 Linux/macOS 上:
      bash
      ./main
    • 在 Windows 上:
      bash
      .\main.exe

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

    Hello, world!

    恭喜!你成功运行了你的第一个 Rust 程序。

第三章:Cargo:Rust 的构建工具和包管理器

手动使用 rustc 编译简单的单文件程序是可以的,但对于更复杂的项目,你需要一个更好的工具。这就是 Cargo 的作用。Cargo 是 Rust 的官方构建工具和包管理器,它可以帮助你管理依赖、构建项目、运行测试等等。

Cargo 已经随着 rustup 一起安装好了。

3.1 创建一个新项目:cargo new

使用 Cargo 创建新项目非常简单。在终端中,导航到你希望创建项目的父目录,然后运行:

bash
cargo new hello_cargo --bin

  • cargo new:创建一个新的 Cargo 项目。
  • hello_cargo:项目的名称。
  • --bin:指定这是一个二进制项目(可执行程序)。如果省略,会创建一个库项目。

运行这个命令后,Cargo 会创建一个名为 hello_cargo 的新文件夹,里面的结构是这样的:

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

  • Cargo.toml:这是 Cargo 的清单文件,使用 TOML (Tom’s Obvious, Minimal Language) 格式编写。它包含了项目的元信息(如名称、版本)以及项目依赖的库。
  • src/main.rs:这是项目的源代码文件。对于二进制项目,入口点通常是 src/main.rs。Cargo 会自动生成一个默认的 “Hello, world!” 代码。

3.2 使用 Cargo 构建和运行项目

进入 hello_cargo 文件夹:

bash
cd hello_cargo

  • 构建项目:

    bash
    cargo build

    这个命令会编译你的项目。编译成功后,可执行文件会生成在 target/debug/ 目录下。

  • 运行项目:

    bash
    cargo run

    这个命令是 cargo build 和运行生成的可执行文件的结合。它会先检查代码是否有改动并重新编译,然后运行程序。

  • 检查代码:

    bash
    cargo check

    这个命令只检查代码是否有编译错误,但不生成可执行文件。这比 cargo build 要快得多,非常适合在编写代码时快速检查错误。

  • 发布构建:

    bash
    cargo build --release

    这个命令会进行优化编译,生成的可执行文件在 target/release/ 目录下。发布构建通常用于最终发布给用户的程序,速度更快,但编译时间较长。

Cargo 是 Rust 开发中不可或缺的工具,它极大地提高了开发效率。

第四章:Rust 的核心概念:所有权 (Ownership)

现在我们来谈谈 Rust 最独特、最核心、也是对初学者来说最具挑战性的概念:所有权

所有权是 Rust 用来管理内存而无需垃圾回收器的核心机制。它通过一套规则在编译期检查程序的内存使用,确保内存安全。理解所有权是掌握 Rust 的关键。

所有权有三条简单的规则:

  1. 每个值都有一个变量作为它的所有者 (Owner)。
  2. 在任意时刻,每个特定值只能有一个所有者。
  3. 当所有者离开作用域 (Scope) 时,这个值就会被丢弃 (Drop)

让我们用代码示例来理解这些规则。

4.1 作用域

作用域是程序中一个有效的范围,变量在这个范围内是有效的。在 Rust 中,作用域通常由花括号 {} 定义。

“`rust
fn main() {
// s 在这里无效,因为它还没有被声明

let s = String::from("hello"); // s 在这里生效

// 可以使用 s

} // 作用域结束,s 不再有效,内存被释放 (drop)
“`

s 进入作用域时,它是有效的。当它离开作用域时,Rust 会自动调用一个特殊的函数 drop 来释放 s 占用的内存。这是 Rust 自动管理内存的方式,无需 GC。

4.2 Move (移动)

Rust 默认的行为是“移动”,而不是“复制”。这与许多其他语言不同。

考虑 String 类型:

“`rust
let s1 = String::from(“hello”); // s1 是 “hello” 的所有者
let s2 = s1; // s1 的所有权移动到了 s2

// println!(“{}”, s1); // 错误!s1 的所有权已经移动,s1 不再有效!
println!(“{}”, s2); // 成功!s2 是新的所有者
“`

这里发生了什么?当我们将 s1 赋值给 s2 时,Rust 并没有复制 s1 的数据(包括堆上的实际字符串数据),而是将所有权s1 转移给了 s2。此时,s1 不再拥有那块内存,尝试使用 s1 会导致编译错误。这被称为移动 (Move)

为什么要这样做?因为如果 s1s2 都拥有同一块内存,当它们各自离开作用域时,就会尝试释放同一块内存两次,导致“二次释放 (double free)”错误,这是一个经典的内存安全问题。Rust 通过所有权系统避免了这个问题。

4.3 Copy (复制)

并非所有类型都会发生移动。有些类型在赋值或传递给函数时会发生复制 (Copy)。这些类型是实现了 Copy Trait 的类型。通常是存储在栈上的简单基本类型,比如:

  • 整数类型(i32, u64 等)
  • 布尔类型(bool
  • 浮点数类型(f64 等)
  • 字符类型(char
  • 元组 (tuple),如果其包含的所有类型都实现了 Copy Trait

“`rust
let x = 5; // x 是 5 的所有者
let y = x; // 由于 i32 实现了 Copy Trait,这里发生了复制,而不是移动

println!(“x = {}, y = {}”, x, y); // 成功!x 和 y 都有效
“`

这里,x 的值被复制了一份赋给了 yx 仍然有效,因为它们各自拥有栈上不同的 5 的副本。

4.4 函数与所有权

将值传递给函数或从函数返回值时,所有权也会发生转移。

“`rust
fn main() {
let s = String::from(“hello”); // s 进入作用域

takes_ownership(s);             // s 的所有权移动到函数内部
// println!("{}", s);         // 错误!s 不再有效

let x = 5;                      // x 进入作用域

makes_copy(x);                  // x 的值被复制到函数内部
println!("{}", x);              // 成功!x 仍然有效,因为 i32 是 Copy 类型

} // x 离开作用域,drop。但 s 已经在 takes_ownership 函数内被 drop 了。

fn takes_ownership(some_string: String) { // some_string 进入作用域
println!(“{}”, some_string);
} // some_string 离开作用域,内存被释放 (drop)

fn makes_copy(some_integer: i32) { // some_integer 进入作用域
println!(“{}”, some_integer);
} // some_integer 离开作用域,drop。
“`

理解所有权转移是 Rust 编程的基础。但只靠所有权,很多常见的编程模式(比如在函数中使用数据而不获取其所有权)会非常笨拙。这就引出了下一个核心概念:借用。

第五章:Rust 的核心概念:借用与引用 (Borrowing and References)

为了能够在不转移所有权的情况下访问数据,Rust 引入了引用 (Reference) 的概念。通过引用,你可以“借用”数据的所有权,而不是将其拿走。

5.1 引用 (References)

引用类似于其他语言中的指针,但 Rust 的引用是安全的,由编译器强制执行规则。创建一个引用称为借用 (Borrowing)

使用 & 符号来创建引用。

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

let len = calculate_length(&s1); // 将 s1 的引用传递给函数

println!("The length of '{}' is {}.", s1, len); // s1 仍然有效!

}

fn calculate_length(s: &String) -> usize { // s 是对 String 的引用
// s.push_str(“, world”); // 错误!不能修改借用的值(默认是不可变的)
s.len() // 可以读取引用的值
} // s 离开作用域。因为它只是一个引用,它指向的值(s1)不会被 drop。
“`

calculate_length 函数中,参数 s 是一个 &String,表示它是一个不可变引用。这意味着你可以读取引用指向的数据,但不能修改它。当 s 离开作用域时,它只是一个引用,并没有拥有实际的数据,所以不会释放 s1 拥有的内存。

5.2 可变引用 (Mutable References)

如果你需要修改借用的值,可以使用可变引用,用 &mut 符号表示。

“`rust
fn main() {
let mut s = String::from(“hello”); // 需要将 s 声明为 mut,才能获取它的可变引用

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

println!("{}", s); // s 仍然有效,并且已经被修改为 "hello, world"

}

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

5.3 借用规则 (Borrowing Rules)

Rust 在编译期 enforces 严格的借用规则,以防止数据竞争:

  1. 在任意给定时间,你只能拥有以下两者之一:
    • 一个可变引用 (&mut T)
    • 任意数量的不可变引用 (&T)
  2. 引用必须始终有效。 (这个规则主要通过所有权和作用域来保证,例如不能引用一个已经被释放的内存)

这些规则是 Rust 实现内存安全和无畏并发的关键。让我们看一个违反规则的例子:

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

let r1 = &s; // 第一个不可变引用
let r2 = &s; // 第二个不可变引用
println!(“{} and {}”, r1, r2); // 可以同时存在多个不可变引用

let r3 = &mut s; // 错误!在存在不可变引用 r1 和 r2 的同时,不能创建可变引用 r3
println!(“{}”, r3);

// 或者

let mut s = String::from(“hello”);

let r1 = &mut s; // 第一个可变引用
// let r2 = &mut s; // 错误!在存在可变引用 r1 的同时,不能创建第二个可变引用
// println!(“{} and {}”, r1, r2);

println!(“{}”, r1); // r1 在这里使用后,它的作用域结束,下一个可变引用就可以创建了

let r2 = &mut s; // 成功!因为 r1 的作用域已经结束了
println!(“{}”, r2);
“`

这些规则可能一开始会让你感到束缚,但它们是 Rust 安全性的基石。编译器中的“借用检查器 (Borrow Checker)” 会在编译期检查这些规则。如果你违反了规则,编译器会给出清晰的错误信息,指导你修改代码。一旦你的代码通过了借用检查器的检查,你就可以非常有信心地说,它不存在数据竞争等内存安全问题。

第六章:Rust 的其他重要特性 (速览)

除了所有权和借用,Rust 还有许多其他强大的特性,虽然我们不会在此深入,但值得提及,让你知道 Rust 还能做什么:

  • 结构体 (Structs) 和枚举 (Enums): Rust 提供了结构体用于创建自定义复合数据类型,以及枚举用于定义一个类型可能有的多种可能状态。
  • 模式匹配 (Pattern Matching): Rust 的 match 表达式非常强大,可以优雅地处理枚举、结构体等数据的不同情况,类似于其他语言的 switch,但功能更丰富。
  • 错误处理 (Error Handling): Rust 没有异常处理机制,而是使用 Result<T, E>Option<T> 这两个枚举来表示可能成功(并返回一个值 T)或失败(并返回一个错误 E),以及值可能存在 (Some(T)) 或不存在 (None) 的情况。这种显式的错误处理方式提高了程序的健壮性。
  • 特性 (Traits): Traits 类似于其他语言的接口或抽象基类,它定义了某种类型应该拥有的行为(方法)。通过实现 Traits,不同的类型可以共享相同的行为,实现多态。
  • 模块系统 (Modules): Rust 有一个强大的模块系统来组织代码,可以将代码分割成逻辑单元,提高可维护性。
  • 泛型 (Generics): 允许你编写可以处理多种数据类型的代码,而不需要为每种类型重复编写相同的逻辑,同时保持类型安全。

这些特性共同构成了 Rust 强大而富有表现力的语言能力。

第七章:学习资源与下一步

零基础入门 Rust,最重要的是找到合适的学习资源并坚持实践。

  • 《Rust 程序设计语言》(The Book): 这是 Rust 官方的权威教程,内容全面,讲解深入浅出,强烈推荐。在线版本有中文翻译:https://book.rust-lang.org/
  • Rust by Example: 通过大量的代码示例来学习 Rust 的概念和标准库,非常适合实践导向的学习者:https://doc.rust-lang.org/rust-by-example/
  • Rustlings: 一套通过完成小型练习来学习 Rust 的命令行工具。安装后,运行命令,它会提示你完成一个又一个的小程序:https://github.com/rust-lang/rustlings/
  • Rust 官方网站和社区: 访问 https://www.rust-lang.org/,找到官方论坛、Discord、Reddit 等社区,与其他 Rust 开发者交流学习经验。

学习 Rust 需要时间和耐心,特别是理解所有权和借用这样的独特概念。不要害怕编译器错误,Rust 编译器通常会提供非常有帮助的提示,告诉你哪里出错了以及如何修复。

总结

通过这篇文章,你应该对 Rust 有了一个初步的了解:

  • 它是一门注重内存安全、高性能和并发的系统级编程语言。
  • 它通过所有权借用这两套独特的机制在编译期保证内存安全,而无需垃圾回收。
  • Cargo 是它强大的构建工具和包管理器。
  • 它还有结构体、枚举、模式匹配、错误处理、Traits 等许多其他重要的特性。

Rust 的学习曲线可能比一些脚本语言要陡峭,但一旦你掌握了它的核心概念,你就能编写出既安全又高效的代码,并且能够处理以往只有 C/C++ 才能胜任的任务。

希望这篇介绍能激发你深入学习 Rust 的兴趣。祝你在 Rust 的学习旅途中一切顺利!


发表评论

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

滚动至顶部