A.1 标量、元组与单元类型
本文集中梳理主线最常出现的几类内置基础类型。它们有一个共同特征:编译期大小全部已知,直接存放在栈上,且编译期类型实体全部实现了 Copy(赋值与传参时直接按位复制,原变量不会失效)。
一、标量类型:整数、usize、浮点、布尔与 char
Section titled “一、标量类型:整数、usize、浮点、布尔与 char”先运行下面这段短程序,实测几个核心标量在栈上占用的字节数:
源文件:scalars.rs
use std::mem::size_of;
fn main() { let count: usize = 3; let flag: bool = true; let letter: char = '中'; println!("{count} {flag} {letter}"); println!( "i32={} char={} bool={}", size_of::<i32>(), size_of::<char>(), size_of::<bool>() );}程序输出:
3 true 中i32=4 char=4 bool=11. 整数家族与为什么索引一律用 usize
Section titled “1. 整数家族与为什么索引一律用 usize”Rust 的整数按「有符号(i 开头,可表示正负数)」与「无符号(u 开头,仅表示非负数)」以及位宽精确命名:
| 位宽(占字节数) | 有符号整数 | 无符号整数 | 说明 |
|---|---|---|---|
| 8 位(1 字节) | i8 |
u8 |
u8 常用于表示原始字节数据(如网络包、UTF-8 字节流) |
| 16 位(2 字节) | i16 |
u16 |
固定宽度短整数 |
| 32 位(4 字节) | i32 |
u32 |
未写类型标注时,整数字面量默认推断为 i32 |
| 64 位(8 字节) | i64 |
u64 |
固定宽度长整数 |
| 128 位(16 字节) | i128 |
u128 |
超大整数计算 |
| 随平台字长 | isize |
usize |
在 64 位机器上固定占 8 字节(在 32 位机器上占 4 字节) |
为什么 Rust 的数组长度、集合容量和下标索引(arr[i])强制要求使用 usize,而不能直接用 i32?
因为索引和长度在底层对应内存地址的偏移量,既不能是负数,也必须能覆盖当前机器的整个内存寻址空间。在 64 位机器上,usize 固定为 8 字节无符号整数,刚好与一个内存地址(机器字)等宽。Rust 不允许不同位宽的整数隐式混用,如需转换须显式写 x as usize。
(注:整数字面量可用下划线辅助阅读,如 1_000_000;也支持直接在字面量末尾写类型后缀,如 42u8、3usize。)
2. 浮点数与布尔值
Section titled “2. 浮点数与布尔值”- 浮点数:只有
f32(4 字节单精度)和f64(8 字节双精度,浮点字面量默认推断为f64)。 - 布尔值(
bool):只有true和false两个值,在内存中固定占 1 个字节。Rust 的if条件位置只接受bool,不会像 C 或 Python 那样把整数0或空指针自动当成false。
3. 字符类型 char:为什么占 4 个字节?
Section titled “3. 字符类型 char:为什么占 4 个字节?”在 C 语言中,char 只占 1 个字节(ASCII);在 Java 中,char 占 2 个字节(UTF-16 代码单元)。
而在 Rust 中,单引号写出的字符字面量(如 'a'、'中'、'🦀')类型为 char,在栈上固定占 4 个字节。因为它代表的是一个完整的 Unicode 标量值(Unicode Scalar Value),能直接装下世界上任何语言的文字或 Emoji。
(注意区分单引号与双引号:单引号 'a' 是占 4 字节的单个字符 char;双引号 "a" 则是 UTF-8 编码的字符串字面量,在第 4 章字符串一节中会详细拆解。)
二、元组(Tuple)与单元类型 ()(Unit Type)
Section titled “二、元组(Tuple)与单元类型 ()(Unit Type)”当你想把几个不同类型的值临时打包在一起(比如让函数一次返回两个值),又不想专门定义一个结构体时,可以使用元组。
源文件:tuples-and-unit.rs
use std::mem::size_of;
fn log_message(msg: &str) { println!("{msg}");}
fn main() { let pair: (i32, bool) = (42, true); let (num, ok) = pair; let first = pair.0; let result: () = log_message("ready"); println!("{num} {ok} {first} {:?} size_of_()={}", result, size_of::<()>());}程序输出:
42 true 42 () size_of_()=01. 元组的创建、解构与按下标访问
Section titled “1. 元组的创建、解构与按下标访问”- 类型与长度编译期固定:
let pair: (i32, bool) = (42, true);把一个 4 字节整数和一个 1 字节布尔值打包成一个复合类型。一旦声明,元组的长度和每个位置上的类型都固定不变。 - 两种取出元素的方式:
- 模式解构:
let (num, ok) = pair;一次性把里面的元素分别绑定到两个新变量上; - 点号加数字下标:
pair.0取出第一个元素(42),pair.1取出第二个元素(true)。
- 模式解构:
- 复制与移动规则:当元组里的每一个元素类型都实现了
Copy(如(i32, bool))时,整个元组也自动实现Copy;只要其中包含未实现Copy的元素(如(String, i32)),整个元组赋值时就会触发移动(Move)。
2. 特殊的空元组:单元类型 ()
Section titled “2. 特殊的空元组:单元类型 ()”如果一个元组里一个元素都不装,写出来就是一对空括号 ()。它在 Rust 里有一个专门的名字,叫作单元类型(Unit Type),它唯一的值也写作 ()。
看清 () 的两个关键事实,能帮你读懂大量编译器报错:
- 用来取代其他语言里的
void:在 C 或 Java 中,不返回任何有意义结果的函数标为void(不是一个真正的类型)。而 Rust 没有void——像上面的fn log_message(msg: &str)这样没有写-> ReturnType的函数,实际上全部默认返回单元类型-> ()。 - 在内存中占 0 个字节(零大小类型 ZST):正如上面代码中
size_of::<()>()实测为0,单元类型纯粹是编译期类型实体层面的标记。到了运行时,它不占用哪怕 1 个字节的栈空间,也不会生成任何搬运数据的机器指令。
当你看到编译器报错提示 expected i32, found () 时,意思是「编译器预期这里产出一个 i32 整数,但你的代码实际交出的是空值 ()」——这通常是因为你在函数最后一行不小心多加了一个分号。下一节马上拆解这个分号规则。