变量定义,Rust 的基石

变量是编程的基础,就像画家的调色盘一样。但在不同的编程语言中,变量有着截然不同的"性格"。

JavaScript 的变量:像变色龙,随时可以改变类型和值,自由但危险。
Java/Dart 的变量:像严格的管家,类型固定但值可变,但变量默认可以被修改。
Rust 的变量:像严历的守护者,默认不可变但允许显式改变,保证值的安全。

在 Rust 中,变量不仅仅是存储数据的容器,更是内存安全的守护者。让我们通过彩色输出来直观地学习 Rust 的变量定义!

变量定义
不可变 let
可变 mut
常量 const
默认安全
显式可变
编译时确定

1 基本变量定义

变量有什么用呢?让我们先看看没有变量时的情况,注意观察重复出现的 \x1b[0m 重置码:

fn main() {
    // 频繁重复的重置码 - 看起来很冗余
    println!("\x1b[31m红色文本\x1b[0m");
    println!(" \x1b[32m绿色文本\x1b[0m");
    println!(" \x1b[34m蓝色文本\x1b[0m");
    println!(" \x1b[33m黄色文本\x1b[0m");
}

可以看到 \x1b[0m 重复了4次!如果要修改重置方式,需要改4个地方。现在让我们用变量来优化:
: println 中可以使用 {} 来放置一个变量名称:

fn main() {
    // 提取变量后 - 清晰且易维护
    let red = "\x1b[31m";
    let green = "\x1b[32m";
    let blue = "\x1b[34m";
    let yellow = "\x1b[33m";
    let reset = "\x1b[0m";  // 变量的价值体现!

    println!("{red}红色文本{reset}");
    println!(" {green}绿色文本{reset}");
    println!(" {blue}蓝色文本{reset}");
    println!(" {yellow}黄色文本{reset}");
}

变量的价值体现:通过提取 reset 变量,我们实现了:

  • 统一管理:一处定义,处处使用
  • 易于维护:修改重置方式只需改一个地方
  • 代码清晰:语义更明确,可读性更强

运行这段代码,你会看到彩色的文本输出。这些颜色变量一旦定义,就不能再改变了。

image.png

为什么默认不可变?

这体现了 Rust 的设计哲学:安全优于便利。就像现实生活中,我们更愿意把贵重物品放在保险箱里,而不是随意摆放。

  • 内存安全:防止意外修改
  • 并发安全:多线程环境下更安全
  • 性能优化:编译器可以做更多优化

哲学思考:不可变性让我们重新思考"改变"的含义。真正的改变不是破坏原有的,而是创造新的。


2 可变变量

当你需要改变变量的值时,使用 mut 关键字。

fn main() {
    // 使用变量后 - 统一管理重置码
    let red = "\x1b[31m";
    let green = "\x1b[32m";
    let blue = "\x1b[34m";
    let reset = "\x1b[0m";  // 一处定义,处处使用
    
    let mut current_color = red;
    println!("{current_color}初始是红色{reset}" );
    
    current_color = green;
    println!(" {current_color}现在是绿色{reset}" );
    
    current_color = blue;
    println!(" {current_color}又变成蓝色{reset}");
}

这就像有一个可以调节的调色盘,你可以随时切换颜色。

设计对比

  • 其他语言:变量默认可变,“自由但混乱”
  • Rust 语言:变量默认不可变,“约束中的自由”

这种设计让我们在需要改变时更加谨慎和明确,就像在说:“我知道我在做什么,我要改变这个变量。”


3 类型声明

Rust 有强大的类型推断,但有时我们需要明确指定类型。其中 u8 是无符号8位整数,取值范围 0-255;正好对应 RGB 颜色值的范围

fn main() {
    let r: u8 = 255;
    let g: u8 = 128;
    let b: u8 = 64;
    let reset = "\x1b[0m";  // 提取重置码变量
    
    println!("\x1b[38;2;{r};{g};{b}mRGB红色{reset}");
    println!("\x1b[38;2;0;{g};{b}m绿蓝混合{reset}");
    println!("\x1b[38;2;{r};0;{b}m红蓝混合{reset}");
}

类型声明的场景

  • 编译器无法推断时
  • 需要特定类型时(如 u8 而不是 i32
  • 提高代码可读性时

4 常量定义

常量在编译时就确定值,整个程序运行期间都不会改变。让我们看看常量如何解决重复代码问题:

fn main() {
    // 硬编码方式 - 重置码到处都是
    print!("\x1b[31m错误\x1b[0m");
    print!(" \x1b[33m警告\x1b[0m");
    print!(" \x1b[32m成功\x1b[0m");
    print!(" \x1b[34m信息\x1b[0m");
    print!(" \x1b[35m调试\x1b[0m");
    println!();
}

这里 \x1b[0m 出现了5次!用常量优化后:

fn main() {
    // 常量定义 - 统一管理,体现变量价值
    const RED: &str = "\x1b[31m";
    const YELLOW: &str = "\x1b[33m";
    const GREEN: &str = "\x1b[32m";
    const BLUE: &str = "\x1b[34m";
    const MAGENTA: &str = "\x1b[35m";
    const RESET: &str = "\x1b[0m";  // 关键变量!
    
    println!("{RED}错误{RESET}");
    println!("{YELLOW}警告{RESET}");
    println!("{GREEN}成功{RESET}");
    println!("{BLUE}信息{RESET}");
    println!("{MAGENTA}调试{RESET}");
}

常量 vs 变量

特性 常量 const 不可变变量 let
声明时机 编译时 运行时
作用域 全局或模块 块级作用域
命名规范 大写+下划线 小写+下划线
类型声明 必须 可选

5 变量遮蔽

Rust 允许用相同名字声明新变量,这叫做"遮蔽"。

fn main() {
    let reset = "\x1b[0m";  // 统一的重置码
    
    let color = "\x1b[31m";
    println!("{color}第一次是红色{reset}");
    
    let color = "\x1b[32m";  // 遮蔽前一个 color
    println!("{color}第二次是绿色{reset}");
    
    let color = "\x1b[34m";  // 再次遮蔽
    println!("{color}第三次是蓝色{reset}");
    
    // 甚至可以改变类型
    let color = color.len();
    println!("长度是{color}");
}

遮蔽的优势

  • 可以改变变量类型
  • 避免起新名字(如 color_strcolor_len
  • 保持变量不可变性

哲学思考:遮蔽体现了"重生"的概念。旧的变量"死去",新的变量"诞生",它们只是恰好有相同的名字。这不是改变,而是替换。

就像河流中的水,看似是同一条河,实际上每一刻的水都是不同的。变量遮蔽让我们能够在保持不可变性的同时,实现"变化"的效果。


6 解构赋值

可以一次性从复合数据中提取多个值。先说说元组:元组 就像一个小盒子,里面可以放不同类型的东西。比如 (255, 128, 64) 就是一个装了3个数字的小盒子,用圆括号包起来,逗号分隔。元组的好处是可以把相关的数据打包在一起,比如 RGB 颜色的三个分量。

fn main() {
    let reset = "\x1b[0m";
    
    // 元组解构 - 分解 RGB 值
    let rgb_color = (255, 128, 64);  // 创建一个元组
    let (r, g, b) = rgb_color;       // 把元组拆开,分别赋给 r、g、b
    
    println!("\x1b[38;2;{r};{g};{b}m解构得到 R={r}, G={g}, B={b}{reset}");
    
    // 数组解构 - 颜色名称
    let colors = ["红", "绿", "蓝"];
    let [first, second, third] = colors;
    
    println!("\x1b[31m{first} \x1b[32m{second} \x1b[34m{third}{reset}");
    
    // 部分解构
    let rgba = (255, 128, 64, 0.8);
    let (red, green, ..) = rgba;  // 忽略后面的值
    
    println!("\x1b[38;2;{red};{green};0m只用前两个分量: R={red}, G={green}{reset}");
}

7 变量作用域

变量的生命周期由作用域决定:

fn main() {
    let reset = "\x1b[0m";
    let outer_color = "\x1b[31m";  // 外部作用域
    
    println!("{outer_color}外部红色{reset}");
    
    {  // 内部作用域开始
        let inner_color = "\x1b[32m";  // 内部作用域
        println!("{inner_color}内部绿色{reset}");
        println!("{outer_color}内部访问外部{reset}");  // 可以访问外部
    }  // inner_color 在这里被销毁
    
    println!("{outer_color}回到外部{reset}");
    // println!("{inner_color}");  // 编译错误!
}
外部作用域
outer_color 可用
内部作用域
inner_color 可用
outer_color 也可用
内部作用域结束
只有 outer_color 可用

总结

通过彩色输出,我们学习了 Rust 变量定义的核心概念:

Rust 变量
安全性
灵活性
性能
默认不可变
内存安全
遮蔽机制
类型转换
零成本抽象
编译优化

核心要点

  • 默认不可变let 变量默认不可变,需要 mut 才能修改
  • 类型安全:强类型系统,支持类型推断和显式注解
  • 常量机制const 在编译时确定,性能更好
  • 遮蔽特性:允许重新定义同名变量,甚至改变类型
  • 解构赋值:优雅地从复合数据中提取值
  • 作用域管理:变量生命周期由作用域决定

这些特性让 Rust 在保证内存安全的同时,提供了极大的编程灵活性。变量定义虽然基础,却是 Rust 安全性和性能的重要基石。

深层思考

Rust 的变量设计体现了一种平衡的智慧:

  • 保守与激进:默认不可变(保守),但允许显式可变(激进)
  • 约束与自由:类型约束(严格),但有类型推断(灵活)
  • 安全与性能:内存安全(安全),零成本抽象(性能)

这种设计哲学告诉我们:真正的自由不是无约束的放纵,而是在合理约束下的明智选择。就像交通规则不是限制我们的自由,而是让我们能够安全、高效地到达目的地。

编程人生感悟

  • 变量的不可变性教会我们:稳定是珍贵的
  • 显式的可变性教会我们:改变需要深思熟虑
  • 变量遮蔽教会我们:有时候重新开始比修修补补更好
  • 作用域教会我们:万物皆有生命周期,珍惜当下

下一步:让我们探索 Rust 的函数的封装,看看如何用进一步简化代码!


更多文章和视频知识资讯,大家可以关注我的公众号、掘金和 B 站 。让我们一起成长,变得更强。我们下次再见~

Logo

AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。

更多推荐