跳转至

第一章:入门与基础

本章涵盖了开始使用 Rust 所需了解的基础知识,包括安装、Cargo 工具、基本语法和控制流。


提示:安装与项目生命周期简图

flowchart LR
  A[安装 rustup] --> B[安装工具链: stable/beta/nightly]
  B --> C[cargo new/init]
  C --> D[cargo build/check/run]
  D --> E[cargo test/doc]
  E --> F[cargo publish]

1. 如何在我的电脑上安装 Rust?

答: 你可以通过 rustup 来安装 Rust,它是 Rust 的官方工具链管理器。

在 Linux 或 macOS 上,打开终端并运行:

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

在 Windows 上,访问 rust-lang.org 并按照说明下载 rustup-init.exe 并运行它。

安装完成后,通过运行 rustc --versioncargo --version 来验证安装是否成功。


2. rustccargo 有什么区别?

答: - rustc 是 Rust 的编译器。它负责将你的 .rs 源代码文件编译成可执行文件。你通常很少直接使用它。 - cargo 是 Rust 的构建系统和包管理器。它是一个功能强大的工具,可以处理依赖管理、编译代码、运行测试、生成文档等。在实际项目中,你几乎总是通过 cargo 来工作。


3. 如何创建一个新的 Rust 项目?

答: 使用 Cargo!在终端中运行以下命令:

# 创建一个二进制程序项目
cargo new my_project

# 创建一个库项目
cargo new my_library --lib

# 在现有目录中初始化项目
cd existing_folder
cargo init

# 指定项目名称和版本
cargo new my_app --name "awesome-app"
这会创建一个包含 Cargo.toml(配置文件)和 src/main.rs(或 src/lib.rs)源文件的新目录。

生成的项目结构:

my_project/
├── Cargo.toml
├── src/
│   └── main.rs
└── .gitignore


4. 如何编译并运行我的 Rust 程序?

答: 在你的项目目录中,使用 cargo run。这个命令会处理所有事情: 1. 如果需要,它会先编译你的代码。 2. 然后运行生成的可执行文件。

cd my_project
cargo run
如果你只想编译而不运行,使用 cargo build


5. Cargo.toml 文件是做什么用的?

答: Cargo.toml 是 Cargo 的清单文件,采用 TOML 格式。它包含了项目的所有元数据和配置:

[package]
name = "my_project"
version = "0.1.0"
edition = "2021"
authors = ["Your Name <you@example.com>"]
description = "A sample Rust project"
license = "MIT"

[dependencies]
serde = { version = "1.0", features = ["derive"] }
tokio = { version = "1.0", features = ["full"] }

[dev-dependencies]
criterion = "0.5"

[[bin]]
name = "main"
path = "src/main.rs"

主要配置段: - [package]: 项目信息,如名称、版本、作者 - [dependencies]: 项目所依赖的外部库(crates) - [dev-dependencies]: 开发和测试时的依赖 - [[bin]]: 二进制目标配置


6. Rust 中的变量默认是可变的还是不可变的?

答: 默认是不可变的。这是 Rust 强调安全性的一个核心特性。如果你想让一个变量可变,必须使用 mut 关键字。

fn main() {
    let x = 5; // 不可变
    let mut y = 10; // 可变

    println!("x = {}, y = {}", x, y);

    y = 15; // 正确,y 是可变的
    println!("现在 y = {}", y);

    // x = 6; // 编译错误!不能修改不可变变量

    // 可以通过 shadowing 重新定义 x
    let x = x + 1;
    println!("通过 shadowing,x = {}", x);

    // 甚至可以改变类型
    let x = "现在是字符串";
    println!("x = {}", x);
}

7. Rust 有哪些基本的标量类型?

答: Rust 有四种主要的标量类型:

fn main() {
    // 整数类型
    let decimal = 98_222;          // 十进制
    let hex = 0xff;                // 十六进制
    let octal = 0o77;              // 八进制
    let binary = 0b1111_0000;      // 二进制
    let byte = b'A';               // 字节(仅限 u8)

    // 显式类型注解
    let x: i32 = 42;
    let y: u64 = 1000;

    // 浮点数类型
    let pi = 3.14159;              // f64(默认)
    let e: f32 = 2.71828;          // f32

    // 布尔值
    let is_rust_awesome = true;
    let is_learning: bool = false;

    // 字符类型(Unicode)
    let letter = 'A';
    let emoji = '😊';
    let chinese = '中';

    println!("整数: {}, {}, {}, {}, {}", decimal, hex, octal, binary, byte);
    println!("浮点数: {}, {}", pi, e);
    println!("布尔值: {}, {}", is_rust_awesome, is_learning);
    println!("字符: {}, {}, {}", letter, emoji, chinese);
}

类型详解: - 整数 (Integer): 有符号(i8, i16, i32, i64, i128, isize)和无符号(u8, u16, u32, u64, u128, usize) - 浮点数 (Floating-Point): f32f64f64 是默认类型,精度更高 - 布尔值 (Boolean): bool,值为 truefalse - 字符 (Character): char,表示一个 Unicode 标量值,大小为 4 个字节


8. &strString 有什么区别?

答: 这是一个常见的问题,涉及到 Rust 的所有权系统。 - String: 一个在堆上分配的、可增长的、可变的 UTF-8 编码字符串。当你需要拥有字符串数据并可能修改它时使用。 - &str: 通常称为“字符串切片”,它是一个指向 String 或字符串字面量中一部分数据的不可变引用。它没有所有权,只是“借用”数据。


9. 如何在 Rust 中定义一个函数?

答: 使用 fn 关键字。Rust 使用 snake_case 作为函数名的约定。如果函数有参数,你需要显式地声明它们的类型。如果函数返回值,你需要使用 -> 来指定返回类型。

// 没有参数,没有返回值
fn say_hello() {
    println!("Hello, Rust!");
}

// 有参数,有返回值
fn add_one(x: i32) -> i32 {
    x + 1 // 表达式作为返回值,注意没有分号
}

// 多个参数
fn calculate_area(width: u32, height: u32) -> u32 {
    width * height
}

// 带有默认参数的模拟(通过重载实现)
fn greet_default() {
    greet("World");
}

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

// 返回多个值(使用元组)
fn swap(x: i32, y: i32) -> (i32, i32) {
    (y, x)
}

fn main() {
    say_hello();

    let result = add_one(5);
    println!("5 + 1 = {}", result);

    let area = calculate_area(10, 20);
    println!("面积 = {}", area);

    greet_default();
    greet("Rust");

    let (a, b) = swap(1, 2);
    println!("交换后: a = {}, b = {}", a, b);
}

10. Rust 函数的返回值有什么特别之处?

答: Rust 是一个基于表达式的语言。函数体中的最后一个表达式会自动成为函数的返回值,前提是该表达式后面没有分号。你也可以使用 return 关键字提前返回。

fn five() -> i32 {
    5 // 返回 5
}

fn ten() -> i32 {
    return 10; // 使用 return 关键字,效果相同
}

11. Rust 中的 if 语句有什么不同?

答: if 在 Rust 中是一个表达式,而不是一个语句。这意味着 if 可以返回值,你可以用它来给 let 语句赋值。

let number = 6;

if number % 4 == 0 {
    println!("number is divisible by 4");
} else {
    println!("number is not divisible by 4");
}

// `if` 作为表达式
let condition = true;
let x = if condition { 5 } else { 6 }; // x 的值将是 5
注意: if 的每个分支返回的值类型必须相同。


12. Rust 有哪几种循环?

答: Rust 提供了三种循环结构: - loop: 创建一个无限循环,你需要使用 break 来退出。 - while: 当条件为 true 时循环。 - for: 遍历一个迭代器。这是最常用和最安全的循环方式。

fn main() {
    // for 循环 - 遍历数组
    let a = [10, 20, 30, 40, 50];
    for element in a.iter() {
        println!("数组元素: {}", element);
    }

    // 遍历数组并获取索引
    for (index, value) in a.iter().enumerate() {
        println!("索引 {}: 值 {}", index, value);
    }

    // 遍历范围
    for number in 1..4 { // 不包括 4
        println!("数字: {}!", number);
    }

    // 包含结束值的范围
    for number in 1..=3 { // 包括 3
        println!("包含范围: {}", number);
    }

    // while 循环
    let mut counter = 0;
    while counter < 3 {
        println!("计数器: {}", counter);
        counter += 1;
    }

    // loop 无限循环
    let mut x = 0;
    let result = loop {
        x += 1;
        if x == 10 {
            break x * 2; // 从 loop 中返回值
        }
    };
    println!("循环结果: {}", result);

    // 带标签的循环
    'outer: loop {
        println!("外层循环");

        'inner: loop {
            println!("内层循环");
            break 'outer; // 跳出外层循环
        }
    }
}

13. 什么是 isizeusize

答: 它们是“指针大小”的整数类型。 - usize: 无符号整数,其大小与计算机的指针大小相同。在 64 位系统上是 64 位,在 32 位系统上是 32 位。它主要用于索引集合。 - isize: 有符号整数,大小与指针相同。

当你需要索引数组或 Vector 时,应该使用 usize


14. 如何在 Rust 中写注释?

答: - 行注释: //,注释到行尾。 - 块注释: /* ... */,可以跨越多行。 - 文档注释: /////!,用于生成 HTML 文档。/// 注释它后面的项,//! 注释包含它的项(通常是模块或 crate)。

// 这是一个行注释

/*
  这是一个
  多行块注释
*/

/// 这是一个文档注释,用于描述下面的函数
fn my_function() {}

15. Rust 中的元组 (Tuple) 是什么?

答: 元组是一种将多个不同类型的值组合成一个复合类型的方式。元组的长度是固定的,一旦声明就无法改变。

fn main() {
    // 创建元组
    let tup: (i32, f64, u8) = (500, 6.4, 1);

    // 解构元组
    let (x, y, z) = tup;
    println!("解构后: x={}, y={}, z={}", x, y, z);

    // 通过索引访问
    let five_hundred = tup.0;
    let six_point_four = tup.1;
    let one = tup.2;
    println!("索引访问: {}, {}, {}", five_hundred, six_point_four, one);

    // 单元元组(空元组)
    let unit = ();
    println!("单元类型: {:?}", unit);

    // 不同类型的元组
    let mixed = ("hello", 42, true, 3.14);
    println!("混合类型元组: {:?}", mixed);

    // 嵌套元组
    let nested = ((1, 2), (3, 4));
    println!("嵌套元组: {:?}", nested);
    println!("访问嵌套元素: {}", (nested.0).1); // 输出 2

    // 元组作为函数返回值
    let coords = get_coordinates();
    println!("坐标: ({}, {})", coords.0, coords.1);
}

fn get_coordinates() -> (i32, i32) {
    (10, 20)
}

16. Rust 中的数组 (Array) 是什么?

答: 数组也是一种将多个值组合在一起的方式,但与元组不同,数组中的每个元素的类型必须相同。Rust 中的数组长度是固定的,存储在栈上。

fn main() {
    // 基本数组定义
    let a = [1, 2, 3, 4, 5]; // 编译器自动推断类型和长度
    let months: [&str; 12] = [
        "January", "February", "March", "April", "May", "June",
        "July", "August", "September", "October", "November", "December"
    ];

    // 初始化相同值的数组
    let zeros = [0; 5]; // [0, 0, 0, 0, 0]
    let threes = [3; 4]; // [3, 3, 3, 3]

    // 访问元素
    let first = a[0];
    let second = a[1];
    println!("第一个元素: {}, 第二个元素: {}", first, second);

    // 数组长度
    println!("数组 a 的长度: {}", a.len());
    println!("数组 months 的长度: {}", months.len());

    // 遍历数组
    println!("遍历数组 a:");
    for element in &a {
        println!("  {}", element);
    }

    // 使用索引遍历
    println!("使用索引遍历:");
    for i in 0..a.len() {
        println!("  a[{}] = {}", i, a[i]);
    }

    // 数组切片
    let slice = &a[1..4]; // [2, 3, 4]
    println!("切片: {:?}", slice);

    // 多维数组
    let matrix: [[i32; 3]; 2] = [[1, 2, 3], [4, 5, 6]];
    println!("矩阵: {:?}", matrix);
    println!("矩阵元素 [1][2]: {}", matrix[1][2]);
}

17. 什么是表达式和语句?

答: - 语句 (Statements): 是执行某些操作但不返回值的指令。例如,let x = 5; 是一个语句。 - 表达式 (Expressions): 会计算并产生一个值。例如,5 + 6 是一个表达式,它的值为 11if、函数调用等都是表达式。

在 Rust 中,这是一个重要的区别。例如,你不能写 let x = (let y = 6); 因为 let y = 6 是一个语句,不返回值。


18. cargo check 命令有什么用?

答: cargo check 是一个非常有用的命令。它会快速检查你的代码以确保它可以通过编译,但不会产生任何可执行文件。这比 cargo build 快得多,因为他跳过了代码生成阶段。

在你编码过程中,可以频繁使用 cargo check 来快速验证你的代码语法和类型是否正确。


19. Rust 如何处理整数溢出?

答: 这取决于构建模式: - Debug 模式 (默认): 如果发生整数溢出,Rust 会 panic(程序崩溃)。这有助于在开发阶段尽早发现 bug。 - Release 模式 (--release): 如果发生整数溢出,Rust 会执行“环绕”(two's complement wrapping)。例如,一个 u8 的值 255 再加 1 会变成 0

如果你希望明确处理溢出,可以使用标准库提供的 wrapping_*checked_*overflowing_*saturating_* 系列方法。


20. 什么是 shadowing (变量遮蔽)?

答: 在 Rust 中,你可以声明一个与之前变量同名的新变量。这个新变量会“遮蔽”前一个变量。这允许你重复使用一个更有意义的变量名,甚至改变它的类型。

let x = 5;

// 在内部作用域中遮蔽 x
let x = x + 1; 

{
    // 在这个作用域内再次遮蔽
    let x = x * 2;
    println!("The value of x in the inner scope is: {}", x); // 打印 12
}

println!("The value of x is: {}", x); // 打印 6
这与将变量标记为 mut 是不同的,因为遮蔽实际上是创建了一个全新的变量。