基础语法(上):变量与可变性、常量、基本数据类型、复合类型、函数
各位同学,欢迎来到 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 的替代品,它们有各自的用途。
避坑指南:我曾经看到有人把 const 和 let 混用,结果在多个文件中定义了同名的常量,导致编译冲突。建议把常量集中放在一个模块里,用 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 只有两种浮点类型:f32 和 f64。默认是 f64。
let a: f32 = 3.14;
let b = 2.71828; // 默认 f64
你想想看,为什么 Rust 默认用 f64?因为现代 CPU 处理 f64 和 f32 的速度几乎一样,但 f64 的精度更高。除非你是在做嵌入式开发或者需要大量浮点运算的场景,否则用 f64 准没错。
3.3 布尔类型
布尔类型就两个值:true 和 false。类型名是 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),参数名也用小写加下划线。这样代码风格统一,看起来也舒服。
本章知识体系
下面这张图帮你梳理了本章的核心知识点:
好了,这一章的内容就到这里。变量、常量、数据类型、函数——这些是 Rust 世界的「砖块」。下一章我们会继续搭建「墙壁」:控制流、所有权、引用等更高级的概念。
记住:基础不牢,地动山摇。把这些概念吃透,后面的路会越走越顺。
课后练习建议:打开你的编辑器,把本章的每个代码示例都敲一遍。不要复制粘贴,亲手敲出来。遇到编译错误也别慌,仔细看错误信息——Rust 的编译器提示是出了名的友好。