阶段一:建立秩序——程序的基本骨架与数据认知

用「函数机器」与「变量盒子」的生活模型讲清程序骨架:变量为何默认不可变、等号为什么不是数学意义上的等于、类型作为度量衡的意义、语句与表达式的区别,以及 if/match/loop 控制流如何组织指令流。

发布于 6 分钟阅读

适用读者:零基础学习者。

核心目标:理解代码的基本组织单位,掌握变量、基础数据类型、判断分支与重复逻辑,能够阅读并编写简单的指令流。


1. 人生第一行代码与程序骨架解剖

打开任意 Rust 程序,最经典的起点莫过于打印一行文字:

fn main() {
    println!("Hello, world!");
}

初学者可能会觉得这几个单词和标点符号古怪突兀。让我们将其彻底拆解为直观的日常概念:

1.1 函数(fn):专能的加工机器

  • fn 是英文 function(函数)的缩写。
  • 在数学中,函数是 y=f(x)y = f(x);但在计算机的世界里,函数是一台封装好的专用自动化加工机器。
  • 这台机器有明确的名称、接收指定的原材料(参数)、按照内部规定的流水线步骤操作,并最终产出加工成品(返回值)。

1.2 main():整座工厂的总电源开关

  • 计算机在运行你的程序时,内存里可能存放着成千上万行代码。计算机从哪里开始执行第一步?
  • 操作系统约定:所有独立运行的 Rust 二进制程序,必须且只能有一个名为 main 的函数。当程序启动时,CPU 就会自动把指针定位到 main 函数的第一行代码。

1.3 花括号 {}:设立结界(作用域)

  • { 与 } 就像是一堵围墙,它们框定出的封闭空间在计算机专业中被称为作用域(Scope,又称代码块)。
  • 只有写在这堵围墙内部的指令,才属于该函数负责执行的流程;围墙之内产生的临时垃圾,走出围墙后也会被销毁。

1.4 println!:呼叫控制台信使

  • 它负责将括号中的文本或数值打印输出到你的电脑终端屏幕上。
  • 为什么名字末尾带着一个惊叹号 !?

在 Rust 中,末尾带 ! 的不是普通函数,而是宏(Macro)。

  • 普通函数要求参数的个数和类型必须提前定死(例如规定只能传 2 个数字);
  • 宏则像是一个“元代码生成器”,它能在编译阶段自动根据你传入的内容展开代码,允许接收任意数量、任意类型的参数并校验格式安全。

1.5 分号 ;:步骤终结的句号

  • 每一条执行指令的结尾都必须跟上分号 ;。
  • 它就像人类写信时的句号,明确宣告:“本项操作已吩咐完毕,请计算机准备执行下一条指令”。

2. 变量与存储:内存中的“贴标签盒子”模型

程序光有指令是不够的,它必须记录并随时改变数据。这种用来暂存数据的命名载体,在编程中被称为变量(Variable)。

2.1 盒子模型与赋值的真相

let x = 5;

这一行代码在计算机底层发生了什么?

  1. 申请空间:let 关键字告诉计算机:“请在内存工作台(RAM)上为我圈出一块空地,放一个盒子”。
  2. 命名贴签:将这个盒子命名为 x,以便人类后续可以通过名字随时找到它。
  3. 注入数据:符号 = 在编程中绝不是数学上的‘相等’!它被称为赋值运算符(Assignment Operator)。它的物理含义是:“将等号右边的值(5),单向灌入等号左边的盒子(x)中存放”。
     ┌───────────────┐
     │ 内存地址 0x7F │
     │   [   5   ]   │ <=== 变量标签名: x
     └───────────────┘

3. Rust 的核心性格:默认不可变性(Immutability)

如果你在代码中写下:

let x = 5;
x = 6; // 编译报错!无法对不可变变量进行二次赋值

编译器会断然拒绝编译!为什么?

3.1 现实类比:被盖上公章的合同

在日常生活中,如果有人给你递来一份签署好的劳动合同,默认情况下你是绝对不能在上面乱涂乱改的。如果代码中成千上万个变量都可以随时被任何角落的代码悄悄改动,程序稍大一点,你就再也无法推测某个数据此刻到底变成了什么,这就是无数严重 Bug 的根源。

Rust 坚持最严格的防呆设计:所有变量在创建时,默认盖上密封盖,永远是只读的。

3.2 显式开启可变权限:mut 关键字

如果你明确知道这个数值在后续业务中会发生变化(例如倒计时、游戏角色血量),你必须在声明时显式添加 mut(Mutable,可变的)关键字,如同撕开密封条:

let mut health = 100;
health = 90; // 合法!因为 health 被显式声明为 mut

3.3 变量隐藏(Shadowing):换个新盒子

在同一个作用域中,Rust 允许你用 let 再次声明同名变量:

let spaces = "   ";       // spaces 此时装的是一串文本
let spaces = spaces.len(); // 计算长度:spaces 现在装的是数字 3!

这并不违反不可变原则。它的物理实质是:旧的盒子被丢弃并遮蔽,新造了一个全新的盒子,恰好起了相同的名字。它允许你在转换数据形态时(如文本转数字),不用绞尽脑汁去发明临时变量名(如 spaces_str, spaces_num)。


4. 计算机的度量衡:数据类型图谱(Data Types)

正如日常生活中我们需要用升来量度液体、用克来称量重量一样,计算机必须明确每个盒子里装的是什么规格的数据。Rust 是一门静态强类型语言,这意味着所有数据在编译时必须拥有明确的类型。

4.1 标量类型(Scalar Types:装单一数值的基础容器)

4.1.1 整数(Integers:没有小数点的计数)

计算机里的整数分为两大阵营:

  • 有符号整数(Signed,以 i 开头,如 i32):类似于温度计,可正可负,最高位拿来当正负号标识。
  • 无符号整数(Unsigned,以 u 开头,如 u32):只能表示非负数(0 和正整数)。例如人的年龄、图书页码、内存大小,用无符号更精确且容量翻倍。

后面的数字(8, 16, 32, 64, 128)代表占用多少个比特的物理空间:

  • u8(8 个比特,1 字节):取值范围为 0∼2550 \sim 255。
  • i32(32 个比特,4 字节):Rust 的默认推断类型,范围约为正负 21 亿。
  • usize / isize:大小取决于你的电脑硬件架构(在 64 位操作系统上占用 64 比特),专门用来给内存和集合做索引用。

警告:什么是数值溢出(Overflow)?

如果一个 u8 盒子最大只能装 255,你硬要让它装 256,就会发生“溢出”。这就像汽车里程表到头后归零一样,在低级语言中会引发严重逻辑灾难。Rust 在开发模式下检测到溢出会直接停止程序报警,严防安全隐患。

4.1.2 浮点数(Floating-Point:带小数点的数字)

  • f64(默认,64 位双精度浮点数,高精度,现代 CPU 计算极快)。
  • f32(32 位单精度浮点数)。

4.1.3 布尔值(Boolean:逻辑真假)

  • 只有两个取值:true(真) 与 false(假)。
  • 占用 1 字节内存,专门用于控制流程做出决策。

4.1.4 字符(Character)

  • 用单引号包围:let letter = 'A'; let emoji = '😊';
  • 占用 4 个字节,基于全宇宙通用的 Unicode 编码,能无缝容纳世界上任何人类文字与表情符号。

4.2 复合类型(Compound Types:把多个盒子打包在一起)

4.2.1 元组(Tuple:不同物品的百宝箱)

将多个不同类型的值捆绑在一起,长度一旦确定不可更改:

let person: (i32, f64, char) = (25, 175.5, 'M');

// 通过索引点语法取出内部物品(从 0 开始计数)
let age = person.0;      // 25
let height = person.1;   // 175.5

4.2.2 数组(Array:规格完全一致的药丸排板)

在内存中必须紧密连续排布,每个元素的类型必须完全一致,且长度固定:

let scores = [98, 85, 100, 72, 90];
let first = scores[0]; // 获取第一个元素,值为 98
边界检查安全防护:如果你试图读取 scores[10],Rust 在运行时会瞬间触发保护并停机报错,绝不允许读到未经许可的邻近内存,这是杜绝黑客“缓冲区越界读取”攻击的关键屏障。

5. 控制流:计算机如何做决策与重复劳动

计算机之所以强大,不仅在于它能算,更在于它能根据条件自动做决定,并且不知疲倦地重复工作。

5.1 条件分支:if 表达式(人生的岔路口)

let temperature = 30;

if temperature > 28 {
    println!("天气炎热,开启空调。");
} else {
    println!("温度舒适。");
}
  • 核心规矩:if 后面的条件必须严格是布尔值(true 或 false)。在很多语言中写 if 1 可以蒙混过关,但在 Rust 中严禁此类隐式猜测。

5.2 循环:不知疲倦地重复(loop, while, for)

  1. loop:无条件永久运转

除非在内部遇到 break(打破循环并退出)或 continue(跳过本次剩余步骤直接进入下一轮),否则会一直执行。

  1. while:当条件满足时持续运转
let mut count = 3;
while count > 0 {
    println!("倒计时: {}", count);
    count -= 1;
}
println!("发射!");
  1. for in:最高效安全的清单遍历(黄金标准)
let fruits = ["苹果", "香蕉", "橙子"];
for fruit in fruits {
    println!("水果篮里有: {}", fruit);
}

为什么推荐 for in?

它消除了手动维护下标索引(如 i = i + 1)的繁琐,在彻底根除“数组越界” Bug 的同时,由于编译器知道其确切上下界,底层机器码执行速度反而最快。


6. 核心认知破局:语句 vs. 表达式

这是初学者迈向成熟程序员最重要的一道心智门槛:

  • 语句(Statement):执行了一项操作,但两手空空,不产生任何产出值。

例如:let x = 6; 是一条语句。它把 6 放进盒子里,但整句话本身没有值。因此你不能写 let a = (let b = 6);。

  • 表达式(Expression):经过计算后,必定会“吐出”一个具体的结果值。

例如:5 + 5 会吐出 10;temperature > 28 会吐出 true。

6.1 代码块 {} 也是表达式

在 Rust 中,用花括号包裹的代码块本身就是一个表达式!它的最终结果等于这块代码里最后一行表达式的值:

let score = {
    let base = 50;
    let bonus = 20;
    base + bonus // 注意!这里最后一行末尾千万不能加分号!
};
// 此时 score 的值被赋予为 70

6.2 分号 ; 的“价值吞噬魔法”

  • base + bonus 是一个吐出 70 的表达式;
  • 一旦你在末尾加上分号变成了 base + bonus;,它就瞬间被降级为一条语句!它的计算结果被抛弃,最终只返回一个毫无意义的空单元类型 ()。
  • 牢记:当你想让一个代码块或函数把算出的结果交还给外部时,最后一行绝不要加分号。