通用编程概念:变量、类型、函数、控制流¶
这一章讲什么: 变量、基本类型、函数、注释、控制流——别的语言里你早就见过的那批概念, 在 Rust 里的"默认值"和"规矩"。读完你要带走两件事: Rust 的变量默认不可变(要改必须写 mut), 以及 Rust 是表达式语言(函数体的最后一个表达式就是返回值,多写一个分号返回值就没了)。 这一章没有任何 Rust 独有的机制,它立的是后面所有章要踩的地坪。
1. 顶层全景:一张表看完全章¶
主线例子贯穿全章:variables → functions → branches → loops 四个小项目,一个概念一个项目。
变量与可变性 let x = 5(不可变)→ 编译器拒绝对 x 二次赋值;let mut x 才许改
类型 标量(整数/浮点/布尔/字符)+ 复合(元组/数组);长度都是编译期定死
函数 参数必须标类型;函数体 = 语句序列 + 最后一个表达式
控制流 if(条件必须是 bool)/ loop / while / for
图说:左列是概念,右列是 Rust 给它们的"默认值"。
每一格背后都有一次编译器拒编的经历——本章的每个特性都是拿报错学出来的。
2. 变量与可变性:默认不许改,是承诺不是限制¶
这段代码编不过:
报错信息本身给了修法(consider making this binding mutable,把 let x 改成 let mut x),但值得停一下问:为什么默认不可变? 书的理由:如果一段代码认定"这个值永不变"、另一段代码改了它,前者的行为就悄悄错了;这种 bug 事后极难追,尤其当改动"只偶尔发生"时。让编译器保证"你说过不变,它就真的不变",你就永远不用自己盯着这个值1。
加 mut 还有一个附带作用:它是对读者的信号——看到 mut 就知道"别处会改这个值",代码意图更清楚2。
常量和遮蔽:另外两个"不变相关"的机制¶
常量(const)用 const 声明,与不可变变量的三点区别:不允许 mut;必须标注类型;只能绑定编译期就能算出来的值3。例子:
命名约定全大写加下划线。常量的意义不在省内存,在给魔法数字起名字:将来要改,只改一处4。
遮蔽(shadowing):重复 let 同名 生成一个新变量,把旧的盖掉——第 2 章把 String 解析成 u32 时已经用过。它和 mut 的两点关键区别:
- 不加
let就想重新赋值,编译错误——遮蔽是"新建",不是"修改"; - 新变量可以换类型:
let spaces = " "; let spaces = spaces.len();合法;但let mut spaces之后spaces = spaces.len()报 E0308,因为 mut 不能改类型5。
3. 数据类型:静态类型,大多数时候不用你写¶
Rust 是静态类型语言:所有变量的类型编译期就必须确定。 编译器通常能推断,推断不了就逼你标:
error[E0284]: type annotations needed
2 | let guess = "42".parse().expect("Not a number!");
| ^^^^^ ----- type must be known at this point
parse 可以把字符串解析成好几种数字类型,不写 : u32 它就不知道选哪个6。
标量类型:四种¶
标量(scalar)是单个值。Rust 的四种:整数、浮点、布尔、字符。
整数:从 i8/u8 到 i128/u128,外加跟机器字长走的 isize/usize;有符号数用补码存,i8 范围 −128 到 127,u8 范围 0 到 255。不确定就用 i32,Rust 的默认选择;isize/usize 主要用在给集合编索引7。
整数溢出(integer overflow)是这一节最要紧的边界知识:u8 塞 256 会怎样?
- debug 编译:Rust 带溢出检查,溢出即 panic(程序带错退出);
- release 编译(
--release):不做检查,直接回绕(wrapping)——256 变 0,257 变 1。 书里明说:依赖回绕行为算错误8。
想显式处理溢出,标准库给了一组方法:wrapping_*(任何模式下都回绕)、checked_*(溢出返回 None)、overflowing_*(返回值 + 是否溢出的布尔)、saturating_*(钳在最大/最小值)9。
浮点:f32 和 f64,默认 f64——现代 CPU 上它和 f32 差不多快,但能表示更多有效数字;遵循 IEEE-754 标准10。
布尔:bool,值只有 true 和 false 两种,占 1 字节。
字符:char 占 4 个字节(字节是内存计量的最小单位,一个字节是 8 个二进制位)。
char 的值是 Unicode 标量值——Unicode 是一套给全世界文字统一编号的国际标准——单引号括;
重音字母、中日韩文字、emoji 都是合法 char。书里特意提醒:你直觉里的"一个字符"和
Unicode 里的"一个字符"未必是一回事,这个问题第 8 章讲字符串时展开11。
复合类型:两种¶
复合(compound)类型把多个值打包成一个。Rust 有元组和数组两种(vector 那种能长大的集合是第 8 章)。
元组(tuple):定长,各位置类型可不同。 两种取值法——模式解构:let (x, y, z) = tup; 一把拆成三个变量;或直接点索引:tup.0、tup.1。空的元组 () 有个专门名字 unit(单元),是"什么都不返回"的返回值12。
数组(array):定长,元素同类型,放栈上。 类型写作 [i32; 5](元素类型;元素个数);[3; 5] 是"5 个 3"的简写。拿不准用数组还是 vector 时用 vector;数组适合"数量永远不变"的场景,比如一年 12 个月的名字13。
数组越界:Rust 的内存安全原则第一次正面出场。 这段代码编得过,运行时输入 10 会崩:
机制:每次索引访问,运行时检查下标是否小于数组长度,不小于就 panic——为什么不是编译期查?因为编译器无法预知用户会输入什么14。书点破了这一笔的性质:很多底层语言不做这个检查,错误下标会直接读到非法内存;Rust 宁可立即退出,也不让这次内存访问发生15。注意这个检查的代价声明:它发生在运行时——这是"安全"和"零成本"之间少数没完全抹平的缝,第 4 章的所有权机制会把这个缝收得更小。
4. 函数:先立一个"别处少见"的规矩¶
函数定义无悬念:fn 名字(参数) { 函数体 },命名约定 snake_case(全小写、下划线分词)。一个值得说的宽松处:函数定义位置无所谓,只要在调用方看得见的作用域里16。
真正的规矩在参数上:函数签名里每个参数都必须写类型:
fn print_labeled_measurement(value: i32, unit_label: char) {
println!("The measurement is: {value}{unit_label}");
}
这是刻意的设计:参数标了类型,编译器几乎不需要你在别处再标;报错信息也更准17。
语句与表达式:本章最该带走的一节¶
Rust 是表达式语言(expression-based language)。 两个定义:
- 语句(statement):执行一个动作,不返回值。
let x = 6;是语句; - 表达式(expression):计算出一个值。
5 + 6、函数调用、宏调用,甚至一个{}块都是表达式18。
由此推出三个实战结论:
- 不能给语句赋值:
let x = (let y = 6);报"expected expression, foundletstatement"。C 和 Ruby 里赋值本身有值、x = y = 6合法;Rust 里不行19。 - 块表达式的值 = 它最后一个表达式的值:
let y = { let x = 3; x + 1 };之后 y 是 4。 - 分号是"把表达式变成语句"的开关:给
x + 1加上分号,它就不再返回值。
第 3 条直接决定函数返回值怎么写:
加上分号就报错,而且报错信息替你说出了机制:
error[E0308]: mismatched types
7 | fn plus_one(x: i32) -> i32 {
| -------- ^^^ expected `i32`, found `()`
| implicitly returns `()` as its body has no tail or `return`
声明了返回 i32,函数体却是一个语句;语句不返回值,返回的是 ()(unit 类型),对不上20。返回值就是函数体块表达式的值;要提前返回才用 return 关键字21。
5. 控制流:if 与三种循环¶
if 表达式:条件必须是 bool。 if number { ... }(number 是整数)报 E0308——不像 Ruby/JavaScript 会把别的类型自动转成布尔,Rust 要求你显式写 number != 022。
因为 if 是表达式,它可以出现在 let 右边:
但两条臂的值类型必须相同:{ 5 } else { "six" } 报"if and else have incompatible types"。理由书里给了:变量只能有一个类型,编译期必须确定;若类型要等运行期才定,编译器就得追踪一堆假设类型,"更复杂、保证更少"23。链式 else if 多了书建议重构,用第 6 章的 match。
三种循环,各管一件事:
| 循环 | 干什么 | 典型场景 |
|---|---|---|
loop |
永远循环,直到 break |
重试一个可能失败的操作 |
while |
条件为真就循环 | 条件驱动的循环(消除 loop+if+break 的嵌套) |
for |
逐个元素遍历集合 | 遍历数组等——Rust 里最常用的循环 |
loop 有两个不常见的细节:
- break 可以带值:
break counter * 2把值传出循环——重试操作拿结果的标准姿势(return则是直接退出整个函数)24; - 循环标签(loop label):嵌套循环时
break/continue默认作用于最内层;给外层循环起名'counting_up: loop,内层写break 'counting_up就能跳出外层。标签以单引号开头25。
for 为什么压倒 while:一个索引循环的反例。 用 while index < 5 遍历数组有两个毛病:数组改成 4 个元素、条件忘了改,就 panic(慢,且每轮都要做一次越界检查);改成 for element in a 之后,数组多长循环就走多远,安全、简洁,生成的机器码还可能更高效26。连"数三遍倒计时"这种没有集合的场景,Rust 程序员也用 for——配标准库的 Range 类型:for number in (1..4).rev(),生成 1、2、3 再倒序27。
6. 作者的判断与证据¶
- 默认不可变的理由是 bug 预防——这是设计主张,书给的证据是"值被意外改动"这一类 bug 的追踪成本,属于经验性论证,没有对照实验1。
- 参数必须标类型、"编译器几乎不需要你在别处标"——设计主张,理由是更好的报错17。
- for 优于 while 遍历集合——这个有具体机制证据:免去每轮的越界检查和"忘了同步改两处"的 panic 风险26。
判断(我们的,不是书里的): 本章最值得留意的是"默认值即文档"这个模式: 不可变、i32、f64、for——每一个默认值都把"最常见的正确选择"做成了零成本选项, 偏离默认(写 mut、用 while 遍历)则被迫在代码里留下显式痕迹。 一门语言把偏好编码进默认值,比写进风格指南有效得多,因为它由编译器强制执行。 如果错,会错在: 默认值也会变成盲区——比如 release 下整数溢出静默回绕, 默认安全网在优化构建里是撤掉的;信默认值之前得知道它什么时候不生效(本章原文对此是明说8)。
7. 边界与局限¶
- 数值类型的选择指南很简略("不确定就用 i32"),没有性能/内存的对照数据。
- Unicode 与 char 的错位只点了题,答案在第 8 章字符串一节。
- 数组越界检查的运行时成本一笔带过;它和所有权体系的关系第 4 章才闭合。
- 注释一节只讲了
//;文档注释(///)留到第 14 章发布 crate 时讲。
8. 可带走的¶
- 变量默认不可变;
mut既是许可也是"别处会改它"的信号。 - 常量
const:必须标类型、只能绑编译期值、全大写下划线命名。 - 遮蔽(
let同名再声明)≠ 可变:它是新建,可以换类型。 - 类型标不住时编译器会要(E0284);函数参数必须标类型。
- 整数溢出:debug panic、release 静默回绕;想体面处理用
checked_*一族。 - char 是 4 字节 Unicode 标量值,不是你直觉里的"字符"。
- 语句不返回值、表达式值;函数返回值 = 函数体最后一个没有分号的表达式。
- if 条件必须 bool;if 是表达式,两条臂类型必须一致。
- 遍历集合用
for,别用 while 数索引;要跳出外层循环用'label循环标签。
9. 原文地图¶
| 主题 | 原书章 | 原文位置 |
|---|---|---|
| 默认不可变的理由 | Chapter 3: Common Programming Concepts | text/10-ch03-chapter-3-common-programming-concepts.txt:78(搜「easier to reason through」) |
| mut 作为意图信号 | 同上 | text/10-ch03-chapter-3-common-programming-concepts.txt:82(搜「conveys intent」) |
| 常量与变量的区别 | 同上 | text/10-ch03-chapter-3-common-programming-concepts.txt:105(搜「Declaring Constants」) |
| 遮蔽与 mut 的区别 | 同上 | text/10-ch03-chapter-3-common-programming-concepts.txt:179(搜「different from marking」) |
| 整数溢出:debug panic / release 回绕 | 同上 | text/10-ch03-chapter-3-common-programming-concepts.txt:299(搜「INTEGER」) |
| 类型标注的必要 | 同上 | text/10-ch03-chapter-3-common-programming-concepts.txt:225(搜「type annotation」) |
| 数组越界检查 | 同上 | text/10-ch03-chapter-3-common-programming-concepts.txt:556(搜「panicked at」) · text/10-ch03-chapter-3-common-programming-concepts.txt:568(搜「memory safety principles」) |
| 参数必须标类型 | 同上 | text/10-ch03-chapter-3-common-programming-concepts.txt:649(搜「must declare the type」) |
| 语句与表达式 | 同上 | text/10-ch03-chapter-3-common-programming-concepts.txt:682(搜「Statements and Expressions」) |
| 分号吃掉返回值 | 同上 | text/10-ch03-chapter-3-common-programming-concepts.txt:825(搜「mismatched types」) |
| if 必须 bool | 同上 | text/10-ch03-chapter-3-common-programming-concepts.txt:954(搜「expected bool」) |
| for 优于 while 遍历 | 同上 | text/10-ch03-chapter-3-common-programming-concepts.txt:1288(搜「most commonly」) |
-
出处:「Chapter 3: Common Programming Concepts」第 78 段(
text/10-ch03-chapter-3-common-programming-concepts.txt:78,搜「easier to reason through」)。 ↩↩ -
出处:「Chapter 3: Common Programming Concepts」第 82 段(
text/10-ch03-chapter-3-common-programming-concepts.txt:82,搜「conveys intent」)。 ↩ -
出处:「Chapter 3: Common Programming Concepts」第 109 段(
text/10-ch03-chapter-3-common-programming-concepts.txt:109,搜「allowed to use mut with constants」)与第 367 段(text/10-ch03-chapter-3-common-programming-concepts.txt:367,搜「expression」)。 ↩ -
出处:「Chapter 3: Common Programming Concepts」第 138 段(
text/10-ch03-chapter-3-common-programming-concepts.txt:138,搜「hardcoded values」)。 ↩ -
出处:「Chapter 3: Common Programming Concepts」第 179 段(
text/10-ch03-chapter-3-common-programming-concepts.txt:179,搜「different from marking」)与第 197 段(text/10-ch03-chapter-3-common-programming-concepts.txt:197,搜「compile-time error」)。 ↩ -
出处:「Chapter 3: Common Programming Concepts」第 225 段(
text/10-ch03-chapter-3-common-programming-concepts.txt:225,搜「type annotation」)与第 235 段(text/10-ch03-chapter-3-common-programming-concepts.txt:235,搜「type annotations needed」)。 ↩ -
出处:「Chapter 3: Common Programming Concepts」第 295 段(
text/10-ch03-chapter-3-common-programming-concepts.txt:295,搜「defaults are generally good」)。 ↩ -
出处:「Chapter 3: Common Programming Concepts」第 299 段(
text/10-ch03-chapter-3-common-programming-concepts.txt:299,搜「INTEGER」)。 ↩↩ -
出处:「Chapter 3: Common Programming Concepts」第 320 段(
text/10-ch03-chapter-3-common-programming-concepts.txt:320,搜「wrapping_*」)。 ↩ -
出处:「Chapter 3: Common Programming Concepts」第 329 段(
text/10-ch03-chapter-3-common-programming-concepts.txt:329,搜「default type is f64」)。 ↩ -
出处:「Chapter 3: Common Programming Concepts」第 397 段(
text/10-ch03-chapter-3-common-programming-concepts.txt:397,搜「Unicode scalar value」)。 ↩ -
出处:「Chapter 3: Common Programming Concepts」第 425 段(
text/10-ch03-chapter-3-common-programming-concepts.txt:425,搜「destructure」)与第 455 段(text/10-ch03-chapter-3-common-programming-concepts.txt:455,搜「special name, unit」)。 ↩ -
出处:「Chapter 3: Common Programming Concepts」第 478 段(
text/10-ch03-chapter-3-common-programming-concepts.txt:478,搜「chances are you should use a vector」)。 ↩ -
出处:「Chapter 3: Common Programming Concepts」第 564 段(
text/10-ch03-chapter-3-common-programming-concepts.txt:564,搜「less than the array length」)。 ↩ -
出处:「Chapter 3: Common Programming Concepts」第 568 段(
text/10-ch03-chapter-3-common-programming-concepts.txt:568,搜「memory safety principles」)。 ↩ -
出处:「Chapter 3: Common Programming Concepts」第 602 段(
text/10-ch03-chapter-3-common-programming-concepts.txt:602,搜「doesn’t care where you define」)。 ↩ -
出处:「Chapter 3: Common Programming Concepts」第 650 段(
text/10-ch03-chapter-3-common-programming-concepts.txt:650,搜「deliberate decision」)。 ↩↩ -
出处:「Chapter 3: Common Programming Concepts」第 727 段(
text/10-ch03-chapter-3-common-programming-concepts.txt:727,搜「not return a value」)与第 737 段(text/10-ch03-chapter-3-common-programming-concepts.txt:737,搜「created with curly」)。 ↩ -
出处:「Chapter 3: Common Programming Concepts」第 707 段(
text/10-ch03-chapter-3-common-programming-concepts.txt:707,搜「do not return values」)与第 730 段(text/10-ch03-chapter-3-common-programming-concepts.txt:730,搜「both x and y」)。 ↩ -
出处:「Chapter 3: Common Programming Concepts」第 829 段(
text/10-ch03-chapter-3-common-programming-concepts.txt:829,搜「expectedi32, found()」)。 ↩ -
出处:「Chapter 3: Common Programming Concepts」第 367 段(
text/10-ch03-chapter-3-common-programming-concepts.txt:367,搜「expression」)。 ↩ -
出处:「Chapter 3: Common Programming Concepts」第 957 段(
text/10-ch03-chapter-3-common-programming-concepts.txt:957,搜「automatically try to」)。 ↩ -
出处:「Chapter 3: Common Programming Concepts」第 1064 段(
text/10-ch03-chapter-3-common-programming-concepts.txt:1064,搜「single type」)。 ↩ -
出处:「Chapter 3: Common Programming Concepts」第 1122 段(
text/10-ch03-chapter-3-common-programming-concepts.txt:1122,搜「after the break expression」)与第 1147 段(text/10-ch03-chapter-3-common-programming-concepts.txt:1147,搜「exits the current function」)。 ↩ -
出处:「Chapter 3: Common Programming Concepts」第 1148 段(
text/10-ch03-chapter-3-common-programming-concepts.txt:1148,搜「Loop Labels」)。 ↩ -
出处:「Chapter 3: Common Programming Concepts」第 1259 段(
text/10-ch03-chapter-3-common-programming-concepts.txt:1259,搜「error prone」)与第 1288 段(text/10-ch03-chapter-3-common-programming-concepts.txt:1288,搜「most commonly」)。 ↩↩ -
出处:「Chapter 3: Common Programming Concepts」第 1297 段(
text/10-ch03-chapter-3-common-programming-concepts.txt:1297,搜「rev」)。 ↩