基础语法(上):变量与可变性、常量、基本数据类型、复合类型、函数

各位同学,欢迎来到 Rust 实战的第一章。

说实话,每次带新人入门 Rust,我都要先打个预防针:Rust 的语法,跟你之前见过的 C、Go、Python 都不太一样。它有很多「反直觉」的设计,但一旦你理解了背后的设计哲学,就会觉得——嗯,真香。

今天这一章,我们先把地基打牢。我会带着你从变量、常量、数据类型,一路走到函数定义。这些都是你写任何 Rust 程序都绕不开的基础。

1. 变量与可变性:let 和 mut

先问一个问题:变量默认是不可变的,你能接受吗?

我第一次接触 Rust 时,心里直犯嘀咕:「变量不能变,那还叫变量吗?」后来我才明白,这是 Rust 为了内存安全做出的核心设计。

核心规则:Rust 中,变量默认是不可变的。只有显式加上 mut 关键字,变量才可变。

看个例子:

fn main() {
    let x = 5;       // 不可变变量
    // x = 6;        // 编译错误!不能修改不可变变量

    let mut y = 10;  // 可变变量
    y = 20;          // 可以修改
    println!("y = {}", y);
}

你可能会问:「为什么非要这样设计?」

我个人习惯把不可变变量想象成一把锁。当你声明一个不可变变量时,就等于告诉编译器:「这个值,我保证不会改它。」编译器就可以放心地做各种优化,也不用担心多线程下的数据竞争。

我的经验:在项目中,我通常会把「不会改变」的值都声明为不可变。比如配置项、常量、计算结果。只有那些需要频繁更新的状态(比如计数器、累加器)才用 mut。这样代码的意图更清晰,也更容易排查 bug。

2. 常量:const

常量跟不可变变量有点像,但又有本质区别。

const MAX_POINTS: u32 = 100_000;

注意几个关键点:

  • 必须显式标注类型(比如 u32
  • 命名惯例是全大写加下划线
  • 可以在任何作用域声明,包括全局作用域
  • 值必须是编译期常量,不能是运行时计算的结果

我曾经在项目中犯过一个低级错误:把一个需要运行时计算的值声明为 const,结果编译报错。后来改成 let 才解决。嗯,这里要注意:const 不是 let 的替代品,它们有各自的用途

避坑指南:我曾经看到有人把 constlet 混用,结果在多个文件中定义了同名的常量,导致编译冲突。建议把常量集中放在一个模块里,用 pub const 导出。

3. 基本数据类型:标量类型

Rust 的标量类型有四种:整数、浮点、布尔、字符。咱们一个一个看。

3.1 整数类型

Rust 的整数类型非常丰富,有符号和无符号各占一半:

长度 有符号 无符号
8 位 i8 u8
16 位 i16 u16
32 位 i32 u32
64 位 i64 u64
128 位 i128 u128
架构相关 isize usize

我个人习惯:默认用 i32。除非有特殊需求(比如处理二进制数据用 u8,索引用 usize),否则 i32 在性能和兼容性上都是最稳妥的选择。

3.2 浮点类型

Rust 只有两种浮点类型:f32f64。默认是 f64

let a: f32 = 3.14;
let b = 2.71828;  // 默认 f64

你想想看,为什么 Rust 默认用 f64?因为现代 CPU 处理 f64f32 的速度几乎一样,但 f64 的精度更高。除非你是在做嵌入式开发或者需要大量浮点运算的场景,否则用 f64 准没错。

3.3 布尔类型

布尔类型就两个值:truefalse。类型名是 bool

let is_ready: bool = true;
let is_finished = false;

这里有个小细节:Rust 不会自动把非布尔值转换成布尔值。比如你不能写 if 1 { ... },必须写成 if 1 != 0 { ... }。这跟 C 语言不一样,但我觉得更安全——避免了很多隐式转换带来的坑

3.4 字符类型

Rust 的 char 类型很有意思:它占 4 个字节,可以表示任何 Unicode 字符。

let c: char = 'z';
let heart_eyed_cat = '😻';  // 没错,表情符号也是合法的 char

我记得刚学 Rust 时,看到 char 占 4 字节,第一反应是「这也太浪费了吧?」后来才明白,这是为了统一处理所有 Unicode 字符。如果你需要处理 ASCII 或者字节流,可以用 u8 或者 &[u8]

4. 复合类型:元组和数组

复合类型就是把多个值组合在一起。Rust 提供了两种最基本的复合类型:元组数组

4.1 元组

元组可以包含不同类型的值,长度固定。

let tup: (i32, f64, char) = (42, 3.14, 'R');
let (x, y, z) = tup;  // 解构
println!("x = {}, y = {}, z = {}", x, y, z);

元组在 Rust 中非常常用。比如函数返回多个值时,就可以用元组:

fn get_stats() -> (i32, i32) {
    (100, 200)
}

我个人习惯:元组适合临时组合几个值,但如果需要更复杂的结构,还是用结构体(struct)更清晰。

4.2 数组

数组跟元组类似,但所有元素必须是同一类型,长度也是固定的。

let arr: [i32; 5] = [1, 2, 3, 4, 5];
let first = arr[0];  // 访问元素
println!("first = {}", first);

数组还有一种简洁的初始化方式:

let zeros = [0; 10];  // 创建一个包含 10 个 0 的数组

这里要注意:数组越界访问会在运行时 panic。我曾经在项目中因为数组索引写错,导致程序崩溃。后来养成了习惯:访问数组前,先检查索引是否在范围内

避坑指南:如果你需要动态长度的集合,不要用数组,用 Vec(向量)。数组的长度是编译期确定的,无法在运行时改变。

5. 函数定义与参数

Rust 的函数定义很简洁:

fn add(x: i32, y: i32) -> i32 {
    x + y  // 注意:没有分号,这是表达式
}

关键点:

  • 参数必须标注类型
  • 返回值类型用 -> 指定
  • 最后一个表达式就是返回值(不需要 return
  • 语句以分号结尾,表达式没有分号

你可能会问:「为什么参数要标注类型,而变量可以省略?」

嗯,这是 Rust 的设计选择:函数签名是接口的一部分,明确标注类型可以让调用者一目了然。而变量类型通常可以从上下文推断出来,所以可以省略。

来看一个带多个参数的例子:

fn calculate_area(width: f64, height: f64) -> f64 {
    width * height
}

fn main() {
    let area = calculate_area(10.5, 20.3);
    println!("面积 = {}", area);
}

我个人习惯:函数名用蛇形命名法(snake_case),参数名也用小写加下划线。这样代码风格统一,看起来也舒服。

本章知识体系

下面这张图帮你梳理了本章的核心知识点:

第1章:基础语法(上)知识体系 Rust 基础语法 变量与可变性 let / mut 默认不可变 常量 const 编译期常量 基本数据类型 整数 / 浮点 / 布尔 / 字符 复合类型 元组 / 数组 函数定义与参数 所有知识点都是后续章节的基础,务必熟练掌握

好了,这一章的内容就到这里。变量、常量、数据类型、函数——这些是 Rust 世界的「砖块」。下一章我们会继续搭建「墙壁」:控制流、所有权、引用等更高级的概念。

记住:基础不牢,地动山摇。把这些概念吃透,后面的路会越走越顺。

课后练习建议:打开你的编辑器,把本章的每个代码示例都敲一遍。不要复制粘贴,亲手敲出来。遇到编译错误也别慌,仔细看错误信息——Rust 的编译器提示是出了名的友好。


专注资料整理