一、标量类型(Scalar Types)
标量类型代表一个单独的值。Rust 中有四大基本标量类型:整数(integer)、浮点数(floating-point number)、布尔(boolean)和字符(character)。这几种类型在大多数编程语言中都很常见。
1. 整数类型(Integer Types)
整数(integer)是没有小数部分的数字。在之前的猜数字游戏教程里,我们用到了 u32
。这个类型声明表示该值是一个无符号(unsigned)32 位整数(如果是有符号类型,会以 i
开头,例如 i32
)。
下表展示了 Rust 中所有内置的整数类型,每个类型要么是有符号(signed),要么是无符号(unsigned),并且有明确的位数大小。
长度 | 有符号 | 无符号 |
---|---|---|
8-bit | i8 | u8 |
16-bit | i16 | u16 |
32-bit | i32 | u32 |
64-bit | i64 | u64 |
128-bit | i128 | u128 |
arch | isize | usize |
- 有符号(signed)表示数值可能为正也可能为负,所以存储时需要符号位;
- 无符号(unsigned)则只表示非负数(0 或正数),不需要符号位。
对于有符号整数,如果类型是 i8
,它可以存储从 -128 到 127 的数值;若是 i16
,则范围会相应扩大,以此类推。无符号类型则从 0 起算。例如 u8
能表示 0 到 255。
isize
和 usize
根据系统架构的不同而变化:在 64 位架构上是 64 位,在 32 位架构上是 32 位。这些类型常用于根据系统架构进行索引或内存大小计算等场景。
1.1 整数字面量
在 Rust 中可以使用多种形式来表达整数字面量(literal),如下表所示:
数字字面量形式 | 示例 |
---|---|
十进制 | 98_222 |
十六进制 | 0xff |
八进制 | 0o77 |
二进制 | 0b1111_0000 |
字节(仅限 u8 ) | b'A' |
注意:
- 可以在数字中使用下划线
_
作为分隔符来提高可读性,例如1_000
与1000
等价。- 如果需要指定类型,可以在数字后面加上类型后缀,比如
57u8
。
通常如果不确定该用什么整数类型,Rust 默认使用 i32
。若需要根据系统架构进行索引等场景时,才考虑使用 isize
或 usize
。
1.2 整数溢出(Integer Overflow)
假设我们有一个 u8
类型的变量,它能表示的数值范围是 [0, 255]
。如果尝试将其赋值为 256,就会发生整数溢出(integer overflow),导致以下两种行为之一:
- 调试(debug)模式编译:Rust 会执行溢出检查,一旦发现溢出,就会在运行时 panic(程序崩溃并退出)。
- 发布(release)模式编译:Rust 不做溢出检查,而是进行二补码环绕(two’s complement wrapping)。换言之,超出最大可表示值时会“环绕”回最小值。例如,对于
u8
类型,256 会变成 0,257 会变成 1,等等。不会出现 panic,但是结果往往与期望不符。
在实际开发中,不应依赖整数溢出的环绕行为,这被认为是错误的做法。若需要显式处理溢出,可以使用标准库里为整数提供的以下方法族:
wrapping_*
:如wrapping_add
,始终进行环绕运算;checked_*
:如checked_add
,若溢出则返回None
;overflowing_*
:如overflowing_add
,返回一个元组(结果, bool)
,其中bool
指示是否发生溢出;saturating_*
:如saturating_add
,在溢出时结果会自动“饱和”到对应类型的最小或最大值。
2. 浮点数类型(Floating-Point Types)
Rust 提供了两种原生的浮点数类型:f32
(32 位)和 f64
(64 位)。默认使用 f64
,因为在现代 CPU 上,f64
与 f32
速度几乎相当,但精度更高。所有浮点类型都是有符号数。
rust">fn main() {let x = 2.0; // f64let y: f32 = 3.0; // f32println!("x = {}, y = {}", x, y);
}
Rust 的浮点数遵循 IEEE-754 标准。
3. 数值运算(Numeric Operations)
Rust 支持常见的数值运算:加法、减法、乘法、除法和取余。需要注意的是,整数除法会向零方向取整(截断小数部分)。示例:
rust">fn main() {// 加法let sum = 5 + 10;// 减法let difference = 95.5 - 4.3;// 乘法let product = 4 * 30;// 除法let quotient = 56.7 / 32.2;// 取余let remainder = 43 % 5;println!("sum = {}", sum);println!("difference = {}", difference);println!("product = {}", product);println!("quotient = {}", quotient);println!("remainder = {}", remainder);
}
如果需要查看 Rust 提供的所有运算符,可以参考 附录 B。
4. 布尔类型(Boolean Type)
布尔类型(bool
)只有两个可能的值:true
和 false
。它所占的大小是 1 个字节。例如:
rust">fn main() {let t = true;let f: bool = false;println!("t = {}, f = {}", t, f);
}
布尔常常用于条件判断(如 if
表达式),后面会在“控制流”一节详述。
5. 字符类型(Character Type)
Rust 的 char
类型是最基础的字母类型,用单引号包裹,支持 Unicode Scalar Value。这意味着它可以表示除 ASCII 之外更多的字符,比如带重音的拉丁字符、中文、日文、韩文、emoji、零宽空格等。例如:
rust">fn main() {let c = 'z';let z = 'ℤ';let heart_eyed_cat = '😻';println!("{}, {}, {}", c, z, heart_eyed_cat);
}
Rust 的 char
类型占 4 个字节,对应 Unicode Scalar Value 范围:U+0000
~ U+D7FF
和 U+E000
~ U+10FFFF
。需要注意的是,Unicode 的“字符”概念可能与人们直觉中的“字符”不完全一致。详情可参考第 8 章关于字符串的讨论。
二、复合类型(Compound Types)
复合类型可以将多个值组合成一个类型。Rust 提供了两种原生的复合类型:元组(tuple)和数组(array)。
1. 元组类型(Tuple Type)
元组(tuple)可以将多个类型各异的值组合到一个复合类型中,长度固定,不可增长或缩短。使用小括号 ()
包含并用逗号分隔不同的值。例如:
rust">fn main() {let tup: (i32, f64, u8) = (500, 6.4, 1);println!("tup = {:?}", tup);
}
1.1 解构(Destructuring)元组
要获取元组中的单独值,可以使用模式匹配(pattern matching)进行解构:
rust">fn main() {let tup = (500, 6.4, 1);let (x, y, z) = tup;println!("y = {}", y);
}
执行后,y
的值就是 6.4
。这里 tup
被“拆解”成了 x
, y
, z
三个变量的过程,称为解构。
1.2 使用索引访问元组
也可以直接用点号加索引来访问元组的指定元素:
rust">fn main() {let x: (i32, f64, u8) = (500, 6.4, 1);let five_hundred = x.0;let six_point_four = x.1;let one = x.2;println!("{}, {}, {}", five_hundred, six_point_four, one);
}
需要注意,索引从 0 开始。
1.3 单元类型(Unit Type)
如果元组不包含任何元素,则被称为单元元组(unit)。它写作 ()
,表示一种空值或空的返回类型。若一个表达式没有返回任何其他值,默认会返回单元元组。
2. 数组类型(Array Type)
数组(array)也是一种把多个值组合在一起的方式,但它与元组有两个主要区别:
- 数组中所有元素类型相同;
- 数组长度固定,一旦声明,长度就无法改变。
例如:
rust">fn main() {let a = [1, 2, 3, 4, 5];println!("{:?}", a);
}
数组通常存储在栈上(stack)而不是堆上(heap),这在 第 4 章 会详细解释。若需要一个可伸缩的序列,则使用标准库提供的 向量(vector,Vec<T>
)。如果你需要一个长度固定的序列,数组就非常合适。比如月份名称:
rust">let months = ["January", "February", "March", "April", "May", "June","July", "August", "September", "October", "November", "December"
];
2.1 数组的类型注解
声明数组类型时,需要在方括号里写元素类型、分号、元素个数:
rust">let a: [i32; 5] = [1, 2, 3, 4, 5];
这里 i32
是每个元素的类型,5
表示数组长度。
2.2 初始化为相同元素
如果想让数组的所有元素都相同,可以使用如下语法:
rust">let a = [3; 5];
// 等价于 let a = [3, 3, 3, 3, 3];
2.3 访问数组元素
可以使用索引来访问数组元素:
rust">fn main() {let a = [1, 2, 3, 4, 5];let first = a[0];let second = a[1];println!("first = {}, second = {}", first, second);
}
2.4 越界访问与运行时错误
如果索引超出了数组的长度,Rust 会在运行时检查到错误并 panic:
rust">fn main() {let a = [1, 2, 3, 4, 5];println!("请输入一个数组索引。");let mut index = String::new();std::io::stdin().read_line(&mut index).expect("读取失败");let index: usize = index.trim().parse().expect("输入的索引不是数字");let element = a[index];println!("你选择的元素是:{}", element);
}
如果你输入了超出 [0..4]
范围的索引,比如 10,就会引发 panic,显示类似:
thread 'main' panicked at 'index out of bounds: the len is 5 but the index is 10', src/main.rs:19:19
程序因此退出并不会执行后续的 println!
。这是 Rust 保证内存安全的体现:许多低级语言在越界索引时可能会访问非法内存地址,引发不可预料的后果,而 Rust 直接在运行时检测并退出以保证安全。
小结
在本篇文章中,我们介绍了 Rust 最常用的两种数据类型子集:标量类型和复合类型。标量类型包括整数、浮点数、布尔和字符,它们各自有不同的表示和范围;复合类型包括元组和数组,可以用于将多个值组合到一个类型中,并且在长度是否可变和类型一致性方面有所区别。
-
标量类型:
- 整数:如
i32
,u32
,i8
,u8
等,不同字长和有符号/无符号选择; - 浮点数:
f32
和f64
,默认使用f64
; - 布尔:
bool
,仅有true
和false
; - 字符:
char
,占 4 字节,可表示 Unicode Scalar Value。
- 整数:如
-
复合类型:
- 元组:可含多种类型,长度固定;可用解构或索引方式访问;
- 数组:同类型元素的集合,长度固定,存储于栈上。
对于新手而言,遇到无法自动推断类型的情形时,需要加上类型注解,尤其是在使用 parse
或其他需要指明具体数值类型的场景下。随着实践的深入,Rust 提供的多种安全检查机制(如整数溢出检查、数组越界检查等)会给予你更多信心和安全感,同时也需要你熟悉这些机制以写出高效且安全的代码。
在后续章节中,我们将会不断深入 Rust 的特性,包括所有权、引用与切片、集合类型(向量、字符串、哈希映射)以及错误处理等,希望你能继续保持对 Rust 的探索与学习。
参考与致谢
- The Rust Programming Language - By Steve Klabnik and Carol Nichols, CC BY 4.0
- 本文部分内容基于其翻译和改写,如需了解更多细节,请阅读官方文档。