Rust:变量、常量与数据类型
变量绑定与可变性
在大部分编程语言中,我们说"声明变量",但在Rust中,我们更准确地说是变量绑定。这种说法更能体现Rust的核心思想:将值绑定到一个标识符上。
在Rust中,变量名本身并不拥有值,值可能在栈上也可能在堆上,let 做的是把这个名字与一个具体的值绑定在一起。之所以默认不可变,是为了让并发和优化更容易:当编译器知道某个绑定不会被改写时,就能更大胆地做优化,也能在并发场景下避免数据竞争。
            
            
              rust
              
              
            
          
          let x = 5; // 将值5绑定到标识符x上
println!("The value of x is: {}", x);不可变性
Rust中的变量默认是不可变的(immutable),这是Rust安全性和并发性的基础之一。
不可变并不是说值永远不能变化,而是说这个名字一旦绑定到某个值后,就不能再指向别的值。这样可以避免"谁在何时修改了状态"的隐蔽错误,阅读和调试也更直观。
            
            
              rust
              
              
            
          
          let x = 5;
x = 6; // 错误:cannot assign twice to immutable variable以上代码会报错,因为 x 默认是不可变的。
可变性
当确实需要修改变量的值时,可以使用mut关键字:
把 mut 放在变量名之前,表示允许对这个绑定指向的内存位置进行原地修改。可变会增加负担和并发风险,优先使用不可变,只有在确实需要原地更新(如计数器、缓冲区写入)时再使用可变。
            
            
              rust
              
              
            
          
          let mut x = 5;
x = 6; // 现在可以修改了
mut关键字必须在变量绑定时就声明,不能后续添加
变量遮蔽
Rust允许用相同的名字声明新变量,新变量会遮蔽(shadow)之前的变量:
遮蔽是重新绑定,不是修改原变量。它常用在更小的作用域里用计算后的新值而不污染外层作用域。与可变不同,遮蔽可以改变类型。
            
            
              rust
              
              
            
          
          let x = 5;
let x = x + 1; // 遮蔽前一个x,创建新变量
println!("{}", x); // 6
{
    let x = x * 2; // 在内部作用域中遮蔽
    println!("{}", x); // 12
}
println!("{}", x); // 6,外层作用域的x
- 遮蔽:创建新变量,可以改变类型
- 可变性:修改同一变量的值,类型不能改变
            
            
              rust
              
              
            
          
          // 遮蔽:可以改变类型
let spaces = "   "; // &str 类型
let spaces = 10; // usize 类型
// 可变性:不能改变类型
let mut spaces = "   ";
spaces = 10; // 错误:类型不匹配遮蔽 vs 可变性的区别:
| 特性 | 遮蔽(Shadowing) | 可变性(Mutability) | 
|---|---|---|
| 机制 | 创建新变量 | 修改同一变量 | 
| 类型 | 可以改变 | 不能改变 | 
| 内存 | 新分配 | 原地修改 | 
| 作用域 | 受作用域影响 | 不受影响 | 
变量初始化机制
初始化一个变量与大多数语言一致,在声明时通过赋值 = 给变量一个初始值。
            
            
              rust
              
              
            
          
          let x = 1;延迟初始化
Rust 还允许用户延迟初始化一个变量。延迟初始化常用于先声明、后在分支或计算结果确定后再赋值的场景。
            
            
              rust
              
              
            
          
          let x;        // 只声明,不初始化
x = 42;       // 稍后初始化
println!("x = {}", x); // 合法- 即使变量是不可变的 (没有mut),延迟初始化仍然合法
- 变量的不可变性 指的是初始化后不能重新赋值
简单区别一下三个概念:
| 概念 | 说明 | 可变变量 | 不可变变量 | 
|---|---|---|---|
| 声明 | 告诉编译器变量存在 | ✅ | ✅ | 
| 初始化 | 第一次赋值 | ✅ | ✅ | 
| 重赋值 | 修改已初始化的值 | ✅ | ❌ | 
            
            
              rust
              
              
            
          
          // 不可变变量的延迟初始化
let x;        // 声明
x = 10;       // 初始化 
x = 20;    // 重赋值 ❌ 不可变变量不允许
// 可变变量的延迟初始化  
let mut y;    // 声明
y = 10;       // 初始化 ✅
y = 20;       // 重赋值 ✅ 可变变量允许编译时安全保证
Rust编译器确保变量在使用前必须被初始化:
            
            
              rust
              
              
            
          
          let y;
println!("y = {}", y); // 编译错误:use of possibly-uninitialized variable
y = 1;
println!("y = {}", y); // 初始化完后才能使用此外,还要保证每一个分支都有初始化结果,并且各个分支的初始化类型一致:
            
            
              rust
              
              
            
          
          let y;
if some_condition {
    y = 10;
} else {
    y = 20;
}
println!("y = {}", y); 此处不论走哪一个 if 分支,都可以确保使用 y 之前被正确初始化。
类型系统与类型标注
Rust是静态类型语言,编译时必须知道所有变量的类型。
静态类型不等于"处处手写类型"。Rust 借助局部类型推断让代码保持简洁,同时把类型错误尽早暴露到编译期。相比动态类型,静态类型能在大型代码库中降低运行时错误;相比一些拥有运行时反射的语言,Rust 的类型系统强调零成本抽象。
Rust提供了两种机制:
- 类型推断:编译器智能推断类型
- 显式标注:程序员明确指定类型
类型推断
Rust 的推断是局部的:不会跨函数、跨模块进行全局推断;当上下文不足时,必须显式标注类型或使用类型后缀。
编译器根据值 和使用上下文推断类型:
            
            
              rust
              
              
            
          
          let x = 5;          // 推断为 i32(整数默认类型)
let y = 3.14;       // 推断为 f64(浮点数默认类型)
let z = true;       // 推断为 bool
let s = "hello";    // 推断为 &str
// 延迟初始化时的类型推断
let number;         // 类型未知
number = 42;        // 推断为 i32
let data;           // 类型未知
data = String::from("hello"); // 推断为 String这种推断语法,可以大部分类型简单的场景下简化编码,不用为每一个变量指明类型。
显式类型标注语法
            
            
              rust
              
              
            
          
          let 变量名: 类型 = 值;当编译器无法推断或存在歧义时,必须使用显式类型标注:
            
            
              rust
              
              
            
          
          let x: i32 = 5;                    // 明确指定为 i32
let y: f32 = 3.14;                 // 明确指定为 f32常量与静态变量
常量
常量在程序运行期间值永远不变,使用 const 声明:
            
            
              rust
              
              
            
          
          const MAX_POINTS: u32 = 100_000;
const PI: f64 = 3.14159;
const MESSAGE: &str = "Hello";常量必须进行类型标注,指明类型。
与 let 的区别:常量必须是编译期可求值的表达式,通常会被内联到使用处,没有固定内存地址;可在任意作用域声明(包括全局),且总是不可变。
            
            
              rust
              
              
            
          
          let mut num = 1;
const NUM: i32 = num; // 错误比如以上代码就是错误的,因为常量 NUM 依赖了一个变量 num,变量在运行时才能确定值,导致 NUM 无法在编译期得到确定值,因此报错。
静态变量
静态变量具有'static生命周期,在程序整个运行期间有效:
            
            
              rust
              
              
            
          
          static GLOBAL_COUNT: i32 = 0;
static mut COUNTER: i32 = 0;static 具有固定内存地址,可通过引用取地址。static 也要求必须进行类型标注。
标量类型
整数类型
Rust提供了丰富的整数类型,每种都明确指定位数和符号性:
| 长度 | 有符号 | 无符号 | 范围 | 
|---|---|---|---|
| 8-bit | i8 | u8 | -128~127 / 0~255 | 
| 16-bit | i16 | u16 | -32,768~32,767 / 0~65,535 | 
| 32-bit | i32 | u32 | 约±21亿 / 0~43亿 | 
| 64-bit | i64 | u64 | 约±922万万亿 | 
| 128-bit | i128 | u128 | 超大范围 | 
| arch | isize | usize | 取决于架构(32/64位) | 
对于 isize 和 usize,它们占用的比特位取决于系统架构,系统是多少位,就占用多少位。
整数字面量与后缀
前缀
Rust 允许通过前缀不同,来决定一个字面量的进制。
- 无前缀:十进制(如 42)
- 0x:十六进制(如- 0x2A== 42)
- 0o:八进制(如- 0o52== 42)
- 0b:二进制(如- 0b101010== 42)
- 大小写均可:0xFF与0xff等价
            
            
              rust
              
              
            
          
          // 进制表示
let decimal = 98;    // 十进制
let hex = 0xff;      // 十六进制
let octal = 0o77;    // 八进制  
let binary = 0b111;  // 二进制下划线分隔符:
对于一个数值,允许通过 _ 进行分割,仅用于提升可读性,没有数值语义影响。例如 1_000_000 与 1000000 完全相同;二进制/十六进制中也可用来分组位。
字节字面量
            
            
              rust
              
              
            
          
          let byte = b'A'; // 字节字面量(u8)字节字面量 b'A':表示一个 u8(0~255)的字节值,必须是 ASCII 范围内的单字符。示例:b'A' == 65u8。非 ASCII 字符(如 b'中'、b'国')是非法的。
类型后缀
对于整数,可以通过修改后缀来决定字面量的类型:
            
            
              rust
              
              
            
          
          // 类型后缀
let typed = 123i64;            // i64 类型
let unsigned = 456u32;         // u32 类型
let long_num = 789i128;        // i128 类型类型后缀把"字面量本身"的类型在语法层面固定为某个具体整型。它发生在编译期,只影响该字面量节点的静态类型。
默认:整数字面量默认 i32,除非通过上下文或后缀指定为其他类型。
整数溢出
Rust 的程序构建时,分为两种模式,debug调试构建 和 release发布构建。调
在 debug 模式下,会有更多的调试信息输出,一般用于开发环境。而 release 下会对代码进行更大幅度的优化,运行效率更高,一般用语正式发布环境。
当一个整数发生溢出的时候,在两个环境下效果也不同。
在 debug 模式下,如果整数溢出,此时会直接报错,在 Rust 中称为 panic。这会导致整个程序直接终止。
但是在 release 下,如果溢出会发生环绕。
比如:
            
            
              rust
              
              
            
          
          let mut x: u8 = 255;
x += 1;
println!("x: {}", x);这段代码在 debug 模式下直接报错退出,但是在 release 下输出 x: 0。因为 255 已经是 u8 的最大值了,当再加一个数,就会重新变回最小的值,这个过程称为环绕。
其实环绕大部分情况下是一个非常危险的操作,在开发的时候,Rust倾向于直接把这个行为作为一个错误报告给开发者,让开发者可以修改代码逻辑,或者更改更大的类型。
但是在实际发布环境,程序崩溃往往会给用户带来不好的体验,那么Rust就不再把它当做一个错误处理了。
浮点数类型
Rust有两种浮点数类型:f32(单精度)和f64(双精度),它们都遵从 IEEE-754 标准。
            
            
              rust
              
              
            
          
          let x = 2.0;           // f64(默认,推荐)
let y: f32 = 3.0;      // f32(显式标注)
// 科学记数法
let large = 1e6;       // 1,000,000.0 (f64)
let small = 1e-6;      // 0.000001 (f64)一个浮点数类型的字面量默认为 f64,与现代CPU性能相近但精度更高。
布尔类型
布尔类型只表示两种真值,不与数字互转,这能防止很多隐式转换陷阱(例如把 0 误当做 false)。与控制流(if/while)结合时,编译器要求条件表达式必须是 bool,从而让代码更清晰、更安全。
            
            
              rust
              
              
            
          
          let t = true;                  // bool类型
let f: bool = false;           // 显式标注
// 布尔运算
let and_result = t && f;       // false
let or_result = t || f;        // true  
let not_result = !t;           // false特点 :占用1字节,只能是true或false,不能与数字隐式转换。
字符类型
char 表示一个 Unicode 标量值,它可以存储英文字母,中文字符,拉丁文等等。Rust的char类型占4字节,使用单引号''来声明。
            
            
              rust
              
              
            
          
          let c = 'z';                   // ASCII字符
let unicode = 'ℤ';             // Unicode字符
let chinese = '中';             // 中文复合类型
元组类型
元组可以存储多个不同类型的值,长度固定,元组适合把少量相关但类型不同的数据打包传递(如函数返回多个值),使用小括号()进行定义。
            
            
              rust
              
              
            
          
          let tup: (i32, f64, u8) = (500, 6.4, 1);一个元组的类型为 (type1, type2, ...),其类型也可以通过编译器自行推断,大部分时候不用手写类型标注。
访问元组的元素通过下标进行访问,从0开始:
            
            
              rust
              
              
            
          
          let first = tup.0;
let second = tup.1;
let third = tup.2;也可以通过解构赋值,把元组的元素直接赋值到变量上:
            
            
              rust
              
              
            
          
          let a;
let b;
let c;
(a, b, c) = tup;
let (x, y, z) = tup;这样元组内的元素就会自动赋值到 abc 和 xyz 上。
Rust 还允许空元组:
            
            
              rust
              
              
            
          
          let unit: () = ();空元组也称为单元类型,其类型为 () 值也为 (),可以理解为一个空值。如果一个函数没有返回值,默认就返回这个单元类型。
数组类型
数组用于存储相同类型的多个值,长度固定,适合在编译期已知大小的场景,使用方括号[]进行定义。
            
            
              rust
              
              
            
          
          let arr: [i32; 5] = [1, 2, 3, 4, 5];一个数组的类型写作 [T; N],其中 T 是元素类型,N 是长度。大多数情况下类型可由上下文推断,无需显式标注。
访问数组的元素通过下标进行访问,从0开始:
            
            
              rust
              
              
            
          
          let first = arr[0];
let len = arr.len();也可以通过解构赋值,把数组的元素直接赋值到变量上:
            
            
              rust
              
              
            
          
          let [a, b, c, d, e] = arr;Rust会在运行时检查数组边界,越界访问会panic
类型转换
Rust要求显式类型转换,不允许进行隐式转换,使用 as 关键字。
整数间转换
- 扩大转换(如 i32 → i64):安全无数据丢失
- 缩小转换(如 i32 → u8):高位截断(取模运算)
            
            
              rust
              
              
            
          
          let num: i32 = 300;
let safe: i64 = num as i64;  // 安全扩大
let truncated: u8 = num as u8; // 44 (300 % 256)浮点转整数
- 直接丢弃小数部分(向零截断)
- 浮点值超出目标整数范围时行为未定义
            
            
              rust
              
              
            
          
          let pi = 3.99f32;
let int_pi = pi as i32; // 3字符转整数
- 转换为字符的Unicode码值
            
            
              rust
              
              
            
          
          let star = '*';
let star_code = star as u32; // 42布尔值转整数
- true转换为 1
- false转换为 0
            
            
              rust
              
              
            
          
          let t = true as u8;  // 1
let f = false as i32; // 0