第二章:工欲善其事——环境搭建与第一个程序

第二章:工欲善其事——环境搭建与第一个程序

本章导读:每一门语言的学习都始于那个神奇的"Hello, World!"时刻。但在此之前,我们需要先搭建一个称手的工作环境。本章将手把手带你安装 Rust 工具链、配置开发环境、理解 Cargo 项目结构,并最终运行你的第一个 Rust 程序。准备好了吗?让我们开始吧。


🛠️ 2.1 Rust工具链的安装

Rust 的官方安装工具叫做 rustup,它不仅仅是一个安装程序,更是一个完整的 Rust 版本管理器。通过 rustup,你可以轻松地在不同版本的 Rust 之间切换,安装额外的编译目标,以及保持工具链的最新状态。

🪟 2.1.1 Windows 系统安装

在 Windows 上安装 Rust 需要两个组件:Rust 工具链本身,以及一个 C 语言编译器(用于链接系统库)。

首先,安装 Visual Studio Build Tools。访问 Microsoft 官网下载 "Build Tools for Visual Studio",在安装器中选择"使用 C++ 的桌面开发"工作负载。这会安装 MSVC 编译器和 Windows SDK,是 Rust 在 Windows 上的默认链接器。

为什么需要 C 编译器? Rust 编译器生成的是目标代码,而不是可以直接执行的程序。最终的链接步骤需要系统提供的链接器,而 Windows 上的标准链接器是 MSVC 的一部分。如果你不想安装 Visual Studio,也可以使用 GNU 工具链(通过 MSYS2),但 MSVC 方式与 Windows 生态的兼容性更好。

接下来,访问 rustup.rs,下载并运行 rustup-init.exe。安装器会询问你想要哪种安装方式——对于新手,直接按回车选择默认选项即可。

🐧 2.1.2 Linux 和 macOS 系统安装

在 Unix 系统上,安装过程更加简洁。打开终端,运行以下命令:

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

这个命令从官方服务器下载安装脚本并执行。脚本会检测你的系统环境,下载合适的预编译二进制文件,并配置好环境变量。

安装完成后,你需要重新加载 shell 配置:

source $HOME/.cargo/env

✅ 2.1.3 验证安装

无论你使用哪个操作系统,安装完成后都可以通过以下命令验证:

rustc --version    # 显示编译器版本
cargo --version    # 显示包管理器版本
rustup --version   # 显示工具链管理器版本

如果这三个命令都能正常输出版本号,恭喜你——Rust 已经成功安装!


📦 2.2 Cargo:你的得力助手

Cargo 是 Rust 生态系统的核心。它同时扮演着多个角色:构建系统、包管理器、测试运行器、文档生成器……可以说,Cargo 是 Rust 开发体验之所以优秀的关键原因之一。

🎯 2.2.1 为什么需要 Cargo?

在没有 Cargo 之前,使用 C/C++ 库是一件令人头疼的事情。你需要手动下载源码、配置编译选项、解决依赖冲突、管理不同平台的差异。Makefile 和 CMake 虽然强大,但学习曲线陡峭,而且每个项目都有自己的"惯例"。

Cargo 的设计哲学是"约定优于配置"。它定义了一套标准的项目结构和构建流程,让你可以专注于代码本身,而不是构建配置。

🆕 2.2.2 创建新项目

让我们创建你的第一个 Rust 项目。打开终端,导航到你希望存放代码的目录,运行:

cargo new hello_rust

这个命令会创建一个名为 hello_rust 的目录,里面包含以下结构:

hello_rust/
├── Cargo.toml      # 项目配置文件
└── src/
    └── main.rs     # 源代码入口

让我们看看 Cargo 自动生成的 Cargo.toml 文件:

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

[dependencies]

这个文件被称为清单(manifest),它描述了项目的元数据和依赖。edition 字段指定了 Rust 的版本——2021 Edition 是目前的主流选择,2024 Edition 则带来了更多现代特性。

Edition vs Version:Rust 的"版本"有两个概念。编译器版本(如 1.75.0)每六周发布一次,包含新功能和 bug 修复。Edition(如 2018、2021、2024)每两三年发布一次,可能包含不兼容的语言变化。通过在 Cargo.toml 中指定 Edition,你可以选择何时采用这些变化,而不必一次性升级所有代码。

🔨 2.2.3 构建和运行

进入项目目录,运行以下命令:

cd hello_rust
cargo run

你会看到类似这样的输出:

   Compiling hello_rust v0.1.0 (/path/to/hello_rust)
    Finished dev [unoptimized + debuginfo] target(s) in 0.5s
     Running `target/debug/hello_rust`
Hello, world!

cargo run 做了三件事:编译代码、生成可执行文件、运行程序。如果代码没有变化,再次运行会直接执行已有的二进制文件,跳过编译步骤。

如果你想只编译不运行,可以使用:

cargo build            # 开发模式编译(包含调试信息)
cargo build --release  # 发布模式编译(开启优化)

开发模式编译速度快,但生成的代码未优化。发布模式编译耗时更长,但生成的二进制文件运行更快。对于最终发布给用户的产品,一定要使用 --release 模式。


💻 2.3 第一个Rust程序:逐行解析

让我们打开 src/main.rs,看看 Cargo 为我们生成的代码:

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

只有三行代码,但它们包含了 Rust 语法的几个重要元素。让我们逐行分析。

🔑 2.3.1 fn main():程序入口

fn main() {

fn 是 Rust 中定义函数的关键字。main 是一个特殊的函数名——每个可执行的 Rust 程序都必须有一个 main 函数,它是程序执行的起点。

函数名后面跟着一对括号 (),表示这个函数不接受任何参数。花括号 { 标记函数体的开始。

命名约定:Rust 使用 snakecase</code> 命名函数和变量(如 <code>calculatetotal),使用 PascalCase 命名类型(如 HashMap)。编译器不会强制这些约定,但遵循它们会让你的代码更容易被其他 Rust 开发者理解。

🖨️ 2.3.2 println!:宏的初见

    println!("Hello, world!");

这一行打印文本到标准输出。注意 println 后面的感叹号 !——它表示这不是一个普通函数,而是一个宏(macro)

宏 vs 函数:宏是一种编译时代码生成机制。与函数不同,宏可以接受可变数量的参数、进行模式匹配、甚至生成新的语法结构。println! 宏之所以需要是宏,是因为它要支持格式化字符串,而这在 Rust 的类型系统中很难用普通函数实现。

"Hello, world!" 是一个字符串字面量。在 Rust 中,双引号包围的内容是字符串,单引号包围的内容是单个字符。

行末的分号 ; 标记一个语句的结束。在 Rust 中,大多数语句都以分号结尾,这与 C 和 JavaScript 类似。

🧪 2.3.3 动手修改

让我们把程序变得更复杂一点。修改 main.rs

fn main() {
    let name = "Rustacean";  // 声明一个变量
    println!("Hello, {}!", name);  // 使用占位符格式化
}

运行 cargo run,你会看到输出变成了 Hello, Rustacean!

这里引入了两个新概念:let 关键字用于声明变量,{} 是格式化字符串中的占位符。println! 宏会将 name 的值填入占位符的位置。


🔤 2.4 变量与可变性

Rust 的变量系统有一个与众不同的特点:默认不可变。这听起来像是限制,但它是 Rust 安全哲学的重要组成部分。

🔒 2.4.1 不可变变量

fn main() {
    let x = 5;
    x = 6;  // 错误!不能对不可变变量重新赋值
}

尝试编译这段代码,你会得到一个错误:

error[E0384]: cannot assign twice to immutable variable `x`

这个设计背后的理念是:大多数变量实际上不需要改变。默认不可变可以帮助你避免意外修改,也让编译器能够进行更激进的优化。

🔓 2.4.2 可变变量

如果你确实需要改变一个变量,可以使用 mut 关键字:

fn main() {
    let mut x = 5;
    println!("x 的初始值是: {}", x);
    x = 6;
    println!("x 的新值是: {}", x);
}

这段代码可以正常编译和运行。mut 关键字就像是你的一个声明:"我打算改变这个变量,请大家注意。"

费曼技巧提问:为什么要默认不可变?想象你在读一本书。如果书的内容在你阅读时不断变化,你将很难理解它。同样,当代码阅读者看到一个没有 mut 的变量时,他们可以确信这个变量的值在声明后不会改变——这大大降低了认知负担。

🎭 2.4.3 变量遮蔽(Shadowing)

Rust 允许你声明一个与已有变量同名的新变量。这叫做遮蔽(shadowing)

fn main() {
    let x = 5;
    let x = x + 1;  // 第一个 x 被遮蔽
    let x = x * 2;  // 第二个 x 被遮蔽
    println!("x 的最终值是: {}", x);  // 输出: 12
}

遮蔽与 mut 不同:每次 let 都创建了一个新的变量,而不是修改原有变量。这意味着你甚至可以改变变量的类型:

fn main() {
    let spaces = "   ";         // spaces 是字符串类型
    let spaces = spaces.len();  // spaces 现在是数字类型
    println!("空格数量: {}", spaces);
}

如果用 mut,这是做不到的——你不能把字符串变成数字。遮蔽提供了一种安全的方式来"复用"变量名。


📐 2.5 数据类型:Rust的类型系统

Rust 是一门静态类型语言:每个值在编译时都有确定的类型,编译器会检查类型是否匹配。这可以在编译阶段捕获大量错误。

🔢 2.5.1 标量类型

Rust 有四种基本的标量类型:整数、浮点数、布尔值和字符。

整数类型有多种大小可选:

类型含义范围
i88位有符号整数-128 到 127
u88位无符号整数0 到 255
i3232位有符号整数-2^31 到 2^31-1
u6464位无符号整数0 到 2^64-1
isize指针大小有符号整数取决于平台
usize指针大小无符号整数取决于平台

技术术语isizeusize 的大小取决于目标平台的指针大小——在 64 位系统上是 64 位,在 32 位系统上是 32 位。它们主要用于索引集合和内存地址计算。

浮点数有两种:f32(单精度)和 f64(双精度)。Rust 默认使用 f64,因为现代 CPU 上它的速度与 f32 几乎相同,但精度更高。

let x = 2.0;      // f64(默认)
let y: f32 = 3.0; // f32(显式指定类型)

布尔值只有两个值:truefalse,类型是 bool

字符类型 char 代表一个 Unicode 标量值,用单引号包围:

let c = 'z';
let emoji = '😊';
let chinese = '中';

注意 Rust 的 char 是 4 字节,可以表示任何 Unicode 字符,而不仅仅是 ASCII。

📦 2.5.2 复合类型

元组(Tuple) 可以将多个不同类型的值组合成一个复合值:

let person: (&str, i32, bool) = ("Alice", 30, true);
println!("姓名: {}, 年龄: {}", person.0, person.1);

元组的元素通过索引访问(.0.1.2……),索引从零开始。

数组(Array) 存储固定数量的同类型元素:

let numbers: [i32; 5] = [1, 2, 3, 4, 5];
let first = numbers[0];  // 访问第一个元素
let months = ["一月", "二月", "三月"];

数组的大小是类型的一部分——[i32; 3][i32; 4] 是不同的类型。如果你需要一个可以动态增长的大小,应该使用向量(Vector),我们将在后面的章节介绍。


⚙️ 2.6 函数:代码的组织单元

函数是 Rust 程序的基本构建块。让我们深入了解函数的定义和使用。

📝 2.6.1 函数定义

fn greet(name: &str) {
    println!("你好, {}!", name);
}

fn add(a: i32, b: i32) -> i32 {
    a + b  // 返回值(没有分号)
}

函数参数必须声明类型,这与变量不同(变量通常可以由编译器推断)。-> 后面是返回值类型。

注意 add 函数的最后一行 a + b 没有分号——在 Rust 中,块的最后一个表达式的值就是块的返回值。如果你加上分号,它就变成了语句,函数会返回 ()(空元组),这会导致类型不匹配的错误。

表达式 vs 语句:这是一个重要的区分。表达式产生值,语句执行操作但不产生值。5 是表达式,x + 1 是表达式,代码块 { let y = 1; y + 1 } 也是表达式(值为 2)。let x = 5; 是语句,它没有值。

🔄 2.6.2 控制流

Rust 的控制流结构与其他语言类似,但有一些独特之处。

if 表达式

let number = 6;

if number % 4 == 0 {
    println!("可以被 4 整除");
} else if number % 3 == 0 {
    println!("可以被 3 整除");
} else {
    println!("既不能被 4 整除,也不能被 3 整除");
}

注意:Rust 的 if 是表达式,可以有返回值:

let condition = true;
let number = if condition { 5 } else { 6 };

循环有三种形式:loopwhilefor

// loop:无限循环,需要手动 break
let mut count = 0;
let result = loop {
    count += 1;
    if count == 10 {
        break count * 2;  // break 可以携带返回值
    }
};

// while:条件循环
let mut number = 3;
while number != 0 {
    println!("倒计时: {}!", number);
    number -= 1;
}

// for:遍历集合
let fruits = ["苹果", "香蕉", "橙子"];
for fruit in fruits {
    println!("我喜欢吃{}", fruit);
}

// for:范围遍历
for i in 1..=5 {
    println!("数字: {}", i);  // 输出 1, 2, 3, 4, 5
}

第一性原理:为什么 Rust 有 loop 关键字?它看起来就是 while true 的别名。但 loop 表达了更强的语义:"这个循环在正确的地方 break,而不是因为某个条件变为 false 而退出"。它让代码的意图更加清晰,也帮助编译器进行更好的分析(例如,检测是否所有路径都有返回值)。


🎨 2.7 开发环境配置

一个好的开发环境可以大大提升效率。让我们配置一个现代化的 Rust 开发环境。

📝 2.7.1 推荐编辑器

VS Code + rust-analyzer 是目前最流行的 Rust 开发环境组合。

  1. 安装 VS Code
  2. 安装 "rust-analyzer" 扩展(注意:不是 "Rust" 扩展,那个已经过时了)
  3. 可选:安装 "CodeLLDB" 扩展用于调试

rust-analyzer 提供了智能补全、类型提示、错误诊断、重构工具等功能,是开发 Rust 不可或缺的利器。

🔧 2.7.2 有用的 Cargo 命令

命令功能
cargo check快速检查代码是否有错误(比 build 更快)
cargo clippy运行 linter,捕获潜在问题
cargo fmt自动格式化代码
cargo doc --open生成并打开文档
cargo test运行测试
cargo add <crate>添加依赖(需要 Rust 1.62+)

工作流建议:在开发过程中,频繁使用 cargo check 而不是 cargo buildcheck 只进行类型检查,不生成二进制文件,速度更快。等你准备运行程序时,再用 cargo run


📝 本章小结

本章,我们从零开始搭建了 Rust 开发环境,认识了 Cargo 这个强大的工具,并写下了第一行 Rust 代码。我们学习了变量与可变性、基本数据类型、函数和控制流。

关键要点:

  • rustup 管理 Rust 工具链,cargo 管理项目和依赖
  • 变量默认不可变,使用 mut 声明可变变量
  • Rust 是静态类型语言,编译器在编译时检查类型
  • 函数参数需要显式类型声明,返回值可以是块的最后一个表达式
  • if 是表达式,loop 可以返回值

在下一章,我们将深入 Rust 最核心、最独特的概念:所有权、借用和生命周期。这些概念是理解 Rust 的关键,也是 Rust 区别于其他语言的根基。


动手实验

  1. 使用 cargo new 创建一个新项目,编写一个程序,打印 1 到 100 之间所有能被 3 整除的数。
  2. 编写一个函数 fibonacci(n: u32) -> u64,返回第 n 个斐波那契数。
  3. 探索 cargo clippycargo fmt,尝试让它们检查和格式化你的代码。
← 返回目录