【Rust入门练中学】第2课:变量、可变性与常量

目标:

搞懂 let、let mut、const、static、shadowing,以及它们背后的设计哲学。


一、核心概念

  1. 变量默认不可变

Rust 和其他语言最大的不同:变量默认不可变。

rust 复制代码
fn main() {
    let x = 5;
    x = 6;  // ❌ 编译错误:cannot assign twice to immutable variable
}

想改就得加 mut:

rust 复制代码
fn main() {
    let mut x = 5;
    x = 6;  // ✅
    println!("{}", x);  // 6
}

为什么这样设计? 因为大部分 bug 来自"意料之外的修改"。默认不可变让编译器帮你挡住这类问题,需要改时再显式声明,意图清晰。

  1. 常量 const
rust 复制代码
const MAX_POINTS: u32 = 100_000;

规则:

· 必须标注类型

· 值必须是编译期可计算的常量表达式

· 命名惯例:全大写加下划线

· 可以定义在任意作用域,包括全局

· 不能用 mut

  1. 静态变量 static
rust 复制代码
static GREETING: &str = "Hello";
static mut COUNTER: i32 = 0;  // 可变的 static 是 unsafe 的,先别碰

const 和 static 的区别:

· const 在编译期被"内联"到使用处,相当于文本替换

· static 有固定内存地址,全局唯一

· 大多数场景用 const 就够

  1. 变量遮蔽(shadowing)

可以用同一个名字重新 let,新变量会"遮蔽"旧变量:

rust 复制代码
fn main() {
    let x = 5;
    let x = x + 1;      // 新的 x,值为 6
    let x = x * 2;      // 又一个新的 x,值为 12
    println!("{}", x);  // 12
}

遮蔽 vs mut 的关键区别:

· mut 是同一个变量,类型不能变

· 遮蔽是新变量,可以换类型

rust 复制代码
let spaces = "   ";        // &str
let spaces = spaces.len(); // usize,类型变了,合法

而用 mut 就不行:

rust 复制代码
let mut spaces = "   ";
spaces = spaces.len();  // ❌ 类型不匹配

二、多元化练习题(含答案与解读)

🟢 练习 1(基础):让代码编译通过

rust 复制代码
fn main() {
    let x = 10;
    x = 20;
    println!("{}", x);
}

参考答案

rust 复制代码
fn main() {
    let mut x = 10;
    x = 20;
    println!("{}", x);  // 20
}

解读:只需把 let x 改成 let mut x。编译器报错信息会明确提示 "help: consider changing this to be mutable"。


🟢 练习 2(基础):常量定义

要求:定义一个常量 PI,值为 3.14159,类型 f64,并打印它。
参考答案

rust 复制代码
const PI: f64 = 3.14159;

fn main() {
    println!("PI = {}", PI);
}

解读:常量必须标类型,命名全大写。常量可以定义在 main 外面,全局可用。


🟡 练习 3(理解 shadowing):预测输出

rust 复制代码
fn main() {
    let x = 1;
    let x = x + 1;
    {
        let x = x * 10;
        println!("内层: {}", x);
    }
    println!("外层: {}", x);
}

参考答案

复制代码
内层: 20
外层: 2

解读:花括号 {} 创建了一个新的作用域。内层的 let x = x * 10 只在内层生效,出了花括号就回到外层的 x = 2。这就是遮蔽加作用域的组合效果。


🟡 练习 4(遮蔽换类型):修复代码

要求:下面代码想先存字符串长度,再存字符串本身,但顺序反了导致类型冲突。请用 shadowing 修复。

rust 复制代码
fn main() {
    let s = "hello".len();  // usize
    let s = "hello";        // &str,想改成这个顺序
    println!("{}", s);
}

参考答案

rust 复制代码
fn main() {
    let s = "hello";        // &str
    let s = s.len();        // usize,类型变了,合法
    println!("{}", s);      // 5
}

解读:shadowing 允许变量换类型。如果这里用 mut,第一次 let s = "hello" 后类型就锁定为 &str,再赋 usize 会编译失败。


🟡 练习 5(mut vs shadowing):判断对错

以下两段代码,哪段能编译?为什么?

A:

rust 复制代码
fn main() {
    let mut x = 5;
    x = "hello";
}

B:

rust 复制代码
fn main() {
    let x = 5;
    let x = "hello";
}

参考答案

A 编译失败,B 编译通过。

解读:A 用 mut,x 的类型在第一次赋值时就锁定为整数,后面赋字符串类型不匹配,报错 mismatched types。B 用 shadowing,第二次 let x 创建了一个全新的变量,类型可以自由变。


🔴 练习 6(综合):计算圆面积

要求:

· 定义常量 PI: f64 = 3.14159

· 定义变量 radius,初始为 2.0,可修改

· 先把 radius 改为 3.0

· 用 shadowing 创建 area = PI * radius * radius

· 打印面积
参考答案

rust 复制代码
const PI: f64 = 3.14159;

fn main() {
    let mut radius = 2.0;
    radius = 3.0;

    let area = PI * radius * radius;
    println!("面积 = {:.2}", area);  // 面积 = 28.27
}

解读:radius 需要改,所以用 mut。area 只算一次不再改,用普通 let。{:.2} 是格式化占位符,保留两位小数。


🔵 思考题 1:为什么 Rust 默认不可变?
参考答案

三个层面的理由:

安全性:大多数并发 bug 和逻辑 bug 来自意外修改。默认不可变让编译器在编译期拦截这些问题。

可读性:看到 let x = 5 就知道 x 不会变,读代码时不用追踪后续所有赋值。看到 let mut x 就知道要小心,修改意图是显式的。

性能:不可变数据更容易优化,编译器可以放心做内联、缓存。同时不可变引用可以安全地跨线程共享。

解读:这是 Rust "零成本抽象"和"安全优先"哲学的体现。不是限制你,而是让你在需要可变时才付可变的代价。


🔵 思考题 2:const 和 static 到底该用哪个?
参考答案

优先用 const。

const 是编译期常量,使用时被内联到代码里,没有固定内存地址,适合大多数"配置值"场景,比如 const MAX: u32 = 100;。

static 有固定内存地址,全局唯一。适合需要取地址、需要跨线程共享、或者值很大不想被多处复制的情况。

rust 复制代码
const NAME: &str = "Rust";           // 推荐
static GREETING: &str = "Hello";     // 需要地址时才用

解读:新手 99% 的场景用 const 就够了。static mut 是 unsafe 的,涉及全局可变状态,除非必要不要碰,后面讲并发时会再提到。


🔵 思考题 3:shadowing 会带来性能开销吗?
参考答案

不会。

shadowing 只是创建了一个新的栈上变量(或直接复用寄存器),编译器会做优化。旧的变量如果之后不再使用,会被直接释放或优化掉。

实际上,shadowing 常常让代码更清晰,比如把字符串转成数字:

rust 复制代码
let input = "42";
let input: i32 = input.parse().unwrap();

比 let input_str = ...; let input_num = ... 更简洁,也避免了一堆 _str、_num 后缀。

解读:shadowing 是 Rust 里非常常用的惯用法,尤其在类型转换、数据加工链中。放心用。


三、知识点总结(第2课)

默认不可变:let x = 5 后不能改,需要改用 let mut x = 5。

常量:const NAME: Type = value;,必须标类型、全大写、编译期可计算。

静态变量:static,有固定地址,全局唯一,多数场景用 const 替代。

变量遮蔽:同名 let 创建新变量,可换类型,是惯用法。

mut vs shadowing:mut 同一变量类型不变;shadowing 新变量类型可变。

作用域:花括号 {} 创建新作用域,内层遮蔽不影响外层。

格式化:{:.2} 保留两位小数,{} 默认填充。


四、下一课预告

第3课:数据类型

· 标量类型:整数、浮点、布尔、字符

· 复合类型:元组、数组

· 类型推断与显式标注

· 整数溢出与 checked_* 系列方法


📌 本课心法:

不可变是默认,可变是选择。每写一个 mut,都是在向编译器声明"我知道这里会变,我为此负责"。

相关推荐
zr想努力1 小时前
从0开始配置Perforce4环境
开发语言
rustfs2 小时前
MinIO 国产开源平替正式 GA
分布式·docker·云原生·rust
doiito2 小时前
【Agent Harness】Gliding Horse 最新进化:从“能学习”到“可验证的自主进化”
ai·rust·架构设计·ai agent
幸运小圣2 小时前
PointerEvent 常用属性与事件整理【JavaScript】
开发语言·javascript·ecmascript
长谷深风1113 小时前
评测AI Agent:三种裁判各司其职
java·大数据·开发语言·人工智能·ai agent
边境悍匪3 小时前
蜗牛学苑 Java 智能体学习 Day42|项目周开发技术汇总 1 思维导图复盘
java·开发语言·spring boot·学习·spring
Gyr03 小时前
浅刷了一遍51CTO软考题库客观题及其解析(4)
学习·软考·知识点·信息安全工程师
星栈独行4 小时前
ADK-Rust 是什么?Rust 生态新一代 AI Agent 开发套件
人工智能·后端·rust
君顾14 小时前
本地托管机构管理源码实战:需求拆解、数据建模与多端部署
java·开发语言·托管