第一章我们打通了“任督二脉”(环境配置),现在进入 第二章:通用编程概念。
这一章非常关键。虽然大部分编程语言都有变量、函数和循环,但 Rust 在这些基础概念上有一些独特的“反直觉”设计。正是这些设计,构成了 Rust 安全性的基石。
请新建一个项目用于本章练习:
cargo new rust_basics
cd rust_basics
🚀 第二章:通用编程概念
2.1 变量与可变性 (Variables & Mutability)
这是 Rust 新手遇到的第一个“坑”,也是 Rust 最重要的特性之一:默认不可变。
代码实验 1:尝试修改变量
打开 src/main.rs,输入以下代码:
fn main() {
let x = 5;
println!("x 的值是: {}", x);
x = 6; // 试图修改 x
println!("x 的值是: {}", x);
}
运行 cargo run。
结果:❌ 报错!cannot assign twice to immutable variable x(不能对不可变变量 x 二次赋值)。
核心概念:
在 Rust 中,一旦你用 let 绑定了一个值,它默认就是只读的。这为了防止你无意中修改了不该修改的数据,从而导致 bug(特别是在多线程环境下)。
修正:加上 mut 关键字
如果你想让变量可变,必须显式告诉编译器:
fn main() {
let mut x = 5; // 注意这里的 mut
println!("x 的值是: {}", x);
x = 6;
println!("x 的值是: {}", x);
}
再次运行,成功!✅
2.2 隐藏 (Shadowing)
这是一个 Rust 很有趣的特性。你可以在同一个作用域内多次声明同名变量。
fn main() {
let x = 5;
let x = x + 1; // 第二个 x "隐藏" 了第一个 x
let x = x * 2; // 第三个 x "隐藏" 了第二个 x
println!("x 的值是: {}", x); // 输出 12
}
为什么需要这个?
通常用于类型转换。比如你拿到了一个字符串类型的 "42",想把它转成数字类型的 42。
- 在其他语言你可能需要:
let input_str = "42"; let input_num = parse(input_str); - 在 Rust 中:
let input = "42"; let input = input.parse();(复用名字,代码更干净)。
2.3 数据类型 (Data Types)
Rust 是静态强类型语言。编译时必须知道所有变量的类型。
标量类型 (Scalar Types) 代表单个值。
- 整型:
i32(默认,有符号 32 位整数)u32(无符号,不能是负数)i64/u64(64 位,常用于大数)isize/usize(由你的 CPU 架构决定,64位系统就是64位,常用于索引数组)
- 浮点型:
f64(默认,双精度),f32。 - 布尔型:
bool(值为true或false)。 - 字符型:
char。- 注意:Rust 的
char是 4 字节的 Unicode 标量值,用单引号。它可以存储中文、Emoji 等。 let c = '中';(合法)let z = 'ℤ';(合法)let cat = '😻';(合法)
- 注意:Rust 的
复合类型 (Compound Types) 可以将多个值组合成一个类型。
1. 元组 (Tuple) 长度固定,元素类型可以不同。
fn main() {
// 定义元组
let tup: (i32, f64, u8) = (500, 6.4, 1);
// 访问方式 1:解构 (Destructuring)
let (x, y, z) = tup;
println!("y 的值是: {}", y);
// 访问方式 2:使用点号索引
let five_hundred = tup.0;
let six_point_four = tup.1;
println!("第一个元素: {}", five_hundred);
}
2. 数组 (Array) 长度固定,元素类型必须相同。数据存放在栈(Stack)上。 (注意:如果你需要动态长度的列表,以后我们会学 Vector,现在先学 Array)
fn main() {
let a = [1, 2, 3, 4, 5];
// 另一种写法:[类型; 长度]
let b: [i32; 5] = [1, 2, 3, 4, 5];
// 初始化包含 5 个 3 的数组
let c = [3; 5]; // 等同于 [3, 3, 3, 3, 3]
println!("a 的第一个元素: {}", a[0]);
}
2.4 函数 (Functions)
Rust 的函数定义非常清晰。但有一个超级重要的概念:语句 (Statements) vs 表达式 (Expressions)。
- 语句:执行操作,没有返回值。以分号
;结尾。 - 表达式:计算并产生一个值。没有分号。
代码实验 2:函数与返回值
fn main() {
let number = 5;
let result = plus_one(number);
println!("结果是: {}", result);
}
// 定义一个函数,参数必须标注类型
// -> i32 表示返回值类型是 i32
fn plus_one(x: i32) -> i32 {
x + 1 // <--- 注意这里没有分号!这是一个表达式,它的值将作为返回值。
}
如果你把 x + 1 改成 x + 1;(加了分号),它就变成了语句,不再返回值(默认返回空的单元类型 ()),编译器会报错说“期望返回 i32,但实际返回了 ()”。
这是 Rust 新手最容易犯的错误之一,请务必留意分号!
2.5 控制流 (Control Flow)
1. if 表达式
在 Rust 中,if 是一个表达式,这意味着它可以赋值给变量。
fn main() {
let condition = true;
// 类似于三元运算符 condition ? 5 : 6
let number = if condition { 5 } else { 6 };
println!("Number is: {}", number);
}
注意:if 和 else 返回的数据类型必须一致。
2. 循环 (Loops)
Rust 有三种循环:loop, while, for。
loop: 无限循环,直到你手动break。while: 条件循环。for: 遍历集合(最常用,最安全)。
代码实验 3:最常用的 for 循环
fn main() {
// 遍历一个数组
let a = [10, 20, 30, 40, 50];
// element 会自动获取数组中的每个值
for element in a {
println!("值为: {}", element);
}
// 使用 Range (范围)
// (1..4) 包含 1, 2, 3,不包含 4。如果想要包含 4,用 (1..=4)
// .rev() 是反转的意思
for number in (1..4).rev() {
println!("倒计时: {}!", number);
}
println!("发射!");
}
📝 第二章总结与作业
总结:
- 变量:默认不可变,改值需加
mut。 - Shadowing:同名变量可以覆盖旧变量,常用于类型转换。
- 类型:知道
i32,f64,bool,char,以及 Tuple 和 Array。 - 函数:不加分号的代码行是表达式,代表返回值。
- 循环:首选
for循环遍历数据。
作业(巩固你的知识):
- 写一个函数
fahrenheit_to_celsius(f: f64) -> f64,将华氏温度转换为摄氏温度。公式:C = (F - 32) / 1.8。 - 在
main函数中,用一个循环(比如 5 次),计算并打印不同华氏度的摄氏度值。 - 进阶挑战:写一个函数生成斐波那契数列的第 n 项。(如果 n=1 返回 1,n=2 返回 1,n=3 返回 2...)。
做完这些,你就掌握了 Rust 的基本语法框架!