FORMA

第二章:基础语法与类型

Rust 的类型系统和语法设计处处体现着“安全”与“显式”的理念。这一章你将掌握变量绑定、基本类型、复合类型、函数定义以及所有基础控制流结构,它们是你写出任何 Rust 程序的基石。

2.1 变量与绑定

let 默认不可变,let mut 可变

Rust 使用 let 关键字声明变量。与许多语言不同,let 创建的绑定默认不可变——一旦赋值,就不能再修改。

rust
let x = 5;
x = 6; // 编译错误:cannot assign twice to immutable variable

如果需要可变变量,显式加上 mut 关键字:

rust
let mut y = 5;
y = 6; // 允许

这种设计让你在代码中一眼就能区分哪些值会变化,有助于写出更清晰、更安全的逻辑。

变量隐藏(shadowing)

你可以用同一个名字重新 let 一个新变量,新的绑定会隐藏之前的同名变量。这称为变量隐藏。shadowing 的作用域结束之前都可以使用新变量,而且类型也可以改变

rust
let spaces = "   ";
let spaces = spaces.len(); // 隐藏后类型从 &str 变为 usize

这与 mut 不同:mut 改变的是值,但类型必须保持不变;shadowing 实际上是一个全新的变量,只是复用了名字。

常量与静态变量

常量const 声明,必须显式标注类型,并且值必须在编译期可以确定:

rust
const MAX_POINTS: u32 = 100_000;

常量可以在任何作用域(包括全局)声明,且永远不会改变。

静态变量static 声明,同样要求类型注解,并拥有固定的内存地址:

rust
static GREETING: &str = "Hello";
  • 访问或修改可变静态变量(static mut)需要在 unsafe 块中操作,因为编译器无法保证其线程安全。
  • 一般优先考虑常量,除非你需要表示一个全局状态(并准备处理好安全问题)。

2.2 标量类型

Rust 是一门静态类型语言,编译期必须知道所有变量的类型。大多数情况下编译器能通过类型推断自动推导,但你仍需要理解每一种标量。

整数

类型有符号无符号
8 位i8u8
16 位i16u16
32 位i32(默认)u32
64 位i64u64
128 位i128u128
指针大小isizeusize
  • 如果你不写类型,整数默认推断为 i32
  • isizeusize 依赖于目标机器的指针宽度(在 64 位系统上为 64 位),常用于索引和长度。

浮点数

  • f32:单精度浮点数。
  • f64:双精度浮点数,默认推断f64
rust
let x = 2.0;      // f64
let y: f32 = 3.0; // f32

布尔与字符

  • bool 只有 truefalse 两个值,占一个字节。
  • char 表示单个 Unicode 标量值,占 4 个字节,可以表示中文字符、emoji 等。
rust
let t = true;
let c = '🦀';

字面量与分隔符

Rust 提供多种字面量书写方式和可读性分隔符:

rust
let decimal = 1_000_000;       // 1000000,下划线纯为可读性
let hex = 0xff;                // 十六进制
let octal = 0o77;              // 八进制
let binary = 0b1111_0000;      // 二进制
let byte = b'A';               // u8 字节字面量

2.3 复合类型

复合类型可以将多个值组合成一个类型。Rust 提供了两种原生复合类型:元组和数组。

元组(tuple)

元组是固定长度的异构集合,创建后大小不可改变,但每个位置可以是不同类型。

rust
let tup: (i32, f64, char) = (500, 6.4, '🦀');

解构元组:

rust
let (x, y, z) = tup;
println!("{y}");

也可以通过点号(.)加索引直接访问:

rust
let first = tup.0;
let second = tup.1;

没有任何元素的空元组 () 被称为单元(unit),它的唯一值也是 (),常用于表示“没有返回值”。

数组(array)

数组是固定长度、同构的集合,所有元素类型必须相同,长度是类型的一部分。数组分配在栈上。

rust
let arr: [i32; 5] = [1, 2, 3, 4, 5];
// 或者用重复表达式:
let zeros = [0; 10]; // [0, 0, 0, 0, 0, 0, 0, 0, 0, 0]

访问数组元素使用 arr[index],越界访问会导致运行时的 panic(程序崩溃),Rust 会对数组访问进行边界检查。

切片(slice)

切片是某个连续序列的“视图”,它不拥有数据,只是对现有数据(如数组的一部分)的借用。切片的类型写作 &[T],长度可以动态变化。

rust
let arr = [10, 20, 30, 40, 50];
let slice: &[i32] = &arr[1..4]; // 包含元素 20,30,40

切片本质上是一个胖指针,内部保存了指向起始元素的指针和长度。你可以对数组、Vec 等类型创建切片。

类型别名

使用 type 关键字为已有类型起一个新名字,这并不会创建新类型,仅增强可读性:

rust
type Age = u32;
let my_age: Age = 30;

2.4 函数

函数定义

函数用 fn 声明,参数必须标注类型,返回值用 -> 指定类型:

rust
fn add(x: i32, y: i32) -> i32 {
    x + y   // 无分号,表达式返回
}

Rust 不关心函数定义的位置,只要在作用域内可见即可。

表达式与语句

Rust 是一门基于表达式的语言。语句执行某些操作但不返回值(如 let x = 5;),而表达式计算并产生值。

函数体内最后一个表达式不加分号,它的值会被隐式返回。加了分号则变成语句,不会作为返回值。你也可以使用 return 提前返回。

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

fn early_return(x: i32) -> i32 {
    if x > 0 {
        return x;
    }
    0
}

函数指针

函数自身也有类型,可以用函数指针来传递。函数指针类型写作 fn(参数类型) -> 返回类型

rust
fn add_one(x: i32) -> i32 {
    x + 1
}

fn apply(f: fn(i32) -> i32, val: i32) -> i32 {
    f(val)
}

let result = apply(add_one, 5); // 6

发散函数

发散函数从不真正返回,其返回类型用 ! 表示,称为“never”类型。典型例子是 panic!std::process::exit

rust
fn forever() -> ! {
    loop {
        // 无限循环,永不返回
    }
}

因为 ! 类型可以强制转换为任何类型,你可以将发散表达式用在任何需要某类型的地方,比如:

rust
let x: i32 = if true { 42 } else { panic!("error") };

2.5 控制流

if 表达式

Rust 中的 if 是表达式,可以产生值,因此可以直接把 if 放在 let 语句的右侧。每个分支返回的类型必须兼容。

rust
let number = 6;
let description = if number % 2 == 0 {
    "even"
} else {
    "odd"
};
// description 的类型为 &str

条件表达式必须是 bool,Rust 不会隐式转换非布尔值为布尔,这与 C/C++ 根本不同。

loop 循环

loop 创建一个无限循环,直到显式 break。它也是一个表达式,可以通过 break 返回值。

rust
let mut counter = 0;
let result = loop {
    counter += 1;
    if counter == 10 {
        break counter * 2; // loop 表达式将返回这个值
    }
};
// result == 20

while 循环

while 在条件为 true 时持续执行,条件同样必须是 bool

rust
let mut n = 3;
while n != 0 {
    println!("{n}");
    n -= 1;
}

for 循环

for 用来遍历迭代器,这是 Rust 中最常用的循环方式。与很多语言不同,Rust 没有 C 风格的三段式 for,而是完全依赖迭代器。

rust
let arr = [10, 20, 30];
for element in arr {
    println!("{element}");
}

使用区间的常见写法:0..n 生成 0n-1 的区间(左闭右开);0..=n 则包含 n

rust
for i in 0..5 {
    println!("{i}"); // 0,1,2,3,4
}

对集合的引用迭代时,通常使用 .iter().iter_mut(),但 for 可以直接对数组或 Vec 的引用工作(因为存在 IntoIterator 实现)。

标签(label)

当嵌套循环时,你可以给循环贴上标签,然后在 breakcontinue 后跟上标签名,来跳出或继续外层循环。

rust
'outer: for x in 0..5 {
    for y in 0..5 {
        if x + y > 6 {
            break 'outer; // 直接跳出外循环
        }
        println!("{x}, {y}");
    }
}

match 基本用法

match 是 Rust 中极为强大的控制流操作符,它允许将一个值与一系列模式比较,并执行匹配到的第一条分支。编译器强制要求穷尽所有可能,因此你不可能漏掉某个情况。

rust
let x = 1;
match x {
    1 => println!("one"),
    2 => println!("two"),
    3 => println!("three"),
    _ => println!("anything"), // 下划线匹配所有其他值
}

match 也是表达式,每个分支可以返回一个值,并且所有分支的类型必须一致:

rust
let number = 13;
let name = match number {
    1 => "one",
    2 => "two",
    _ => "many",
};

match 的强大远不止于此,结合枚举和解构,它可以处理极其复杂的模式,后续章节将更深入探讨。

至此,你已经覆盖了函数与基本流程结构,这足以支撑你写出各类“计算型”小程序。下一章请阅读 所有权与生命周期

参考文献

资料说明
Variables官方书第 3 章
Data types标量与复合
Control flow控制流

相关文章

Series

rust

10 / 10