Rust 数据类型
每种语言都有数据类型,Rust 的类型系统有几个独特的设计:整数类型按位宽精确命名(不再有 C 那种含糊的 int/long);char 是 4 字节 Unicode(告别 ASCII 局限);数组长度是类型的一部分(更严格的边界检查)。这一篇我们逐一过一遍。
1. 四种标量类型
标量(scalar)就是"单个值"的类型。Rust 有四种:整数、浮点、布尔、字符。
fn main() {
// ===== 整数:有符号 i + 无符号 u =====
// i8 i16 i32 i64 i128 isize 有符号(可正可负)
// u8 u16 u32 u64 u128 usize 无符号(非负)
let a: i32 = -42; // 32 位有符号
let b: u64 = 100; // 64 位无符号
let c: usize = 0; // 与平台相关(64 位机上是 64 位)
// isize / usize 通常用作"索引"或"内存大小"
// ===== 浮点 =====
let pi: f64 = 3.14159; // 64 位(默认)
let half: f32 = 0.5; // 32 位
// ===== 布尔 =====
let ok: bool = true;
let no = false;
// ===== 字符 =====
// 注意:Rust 的 char 是 4 字节的 Unicode 单个标量值
// 不是 C/Java 那种 1 字节的 ASCII!
let letter: char = 'A';
let chinese: char = '中';
let emoji: char = '😀';
}几个要点:
- 整数按位宽命名:i 是有符号(signed),u 是无符号(unsigned),数字是位宽。这是 Rust 比 C 进步的地方——int 在不同机器上可能是 16/32/64 位,而 i32 永远是 32 位,代码可移植。
- isize / usize:位宽等于机器字长(64 位机上就是 64 位),主要用作数组索引和内存大小。
- char 是 4 字节:能装下任何 Unicode 字符,包括中文、emoji。这点和 C 的 1 字节 char、Java 的 2 字节 char 都不同。
2. 整数类型详解
// 整数类型一览(位宽决定范围)
// i8: -128 ~ 127
// u8: 0 ~ 255
// i16: -32768 ~ 32767
// u16: 0 ~ 65535
// i32: -21亿 ~ 21亿 最常用,默认推断类型
// u32: 0 ~ 42亿
// i64: 非常大,常见于时间戳/大数
// i128: 极大,慎用(性能略低)
// 整数字面量写法
let dec = 1_000; // 十进制,下划线分隔易读
let hex = 0xFF; // 十六进制,0x 前缀
let oct = 0o777; // 八进制,0o 前缀
let bin = 0b1010; // 二进制,0b 前缀
let byte = b'A'; // 字节(u8),b 前缀 + 单字符
// 整数溢出:debug 模式 panic,release 模式环绕
// 想要显式控制:用 wrapping_/checked_/saturating_ 方法
let (x, y) = (250i8, 100i8);
let sum = x.checked_add(y); // None 表示溢出,安全几个新手容易踩的坑:
- 默认推断是 i32:即使机器是 64 位,不写类型时整数默认 i32,因为"32 位足够大多数场景且性能最好"。需要更大就显式标注。
- 整数溢出:debug 模式会 panic(直接崩),release 模式会环绕(溢出后从最小值开始)。要安全溢出处理,用
checked_add(返回 Option)、saturating_add(饱和到最大值)、wrapping_add(环绕)这几个方法。 - 负数必须用有符号类型:把 -1 赋给 u32 会编译错误,这是"类型级别"的保护。
3. 浮点类型
Rust 只有两种浮点:f32(单精度,4 字节)和 f64(双精度,8 字节)。不写类型时默认 f64,因为现代 CPU 上 f64 速度和 f32 几乎一样,且精度更高。浮点遵循 IEEE 754 标准,所有语言共同的特性(精度问题、NaN、无穷)Rust 都有。
注意:浮点比较相等要小心(0.1 + 0.2 != 0.3 这种经典问题)。需要精确小数(如金融)请用第三方 crate bigdecimal 或 rust_decimal。
4. 布尔与字符
- bool:只有
true/false两个值,占 1 字节。不能像 C 那样用 0/1 替代布尔值,Rust 严格区分。 - char:单引号包裹的单个 Unicode 标量值,4 字节。注意 char 和字符串是不同类型——
'A'是 char,"A"是字符串字面量(&str)。
5. 元组(tuple)
元组是固定长度、可以装不同类型的复合类型。当你需要把几个值"打包"传递,又不想专门定义结构体时,元组很方便:
fn main() {
// 元组:固定长度,可以装不同类型
let person: (&str, i32, bool) = ("小明", 20, true);
// 用 .0 .1 .2 访问元素(从 0 开始)
println!("姓名:{}", person.0);
println!("年龄:{}", person.1);
// 解构:把元组拆开赋给多个变量
let (name, age, active) = person;
println!("{} 今年 {} 岁", name, age);
// 空元组叫 unit,写作 (),是"没有值"的类型
let nothing: () = ();
// 函数不写返回类型,实际返回的就是 ()
fn greet() { println!("hi"); }
let r = greet();
// r 的类型是 ()
}几个细节:
- 访问元素用 .0 .1 .2(从 0 开始),不能用
nums[0]那种下标。 - 解构(destructure)是常用法,把元组拆开赋给多个变量,代码很简洁。
- 空元组 ()叫 unit 类型,是"没有有意义的值"的类型。函数没显式返回值时,实际返回
()。
6. 数组(array)
数组是固定长度、同类型的集合,分配在栈上(不是堆)。和 Python list、JS array 不同,Rust 的数组一旦创建,长度永远不变——想变长请用 Vec(后面讲字符串和标准库时会涉及):
fn main() {
// 数组:固定长度,所有元素同类型
// 在栈上分配(不像 Vec 在堆上)
let nums = [1, 2, 3, 4, 5];
println!("第一个:{}", nums[0]); // 1
println!("长度:{}", nums.len()); // 5
// 显式指定类型和长度:[类型; 长度]
let bytes: [u8; 4] = [0, 1, 2, 3];
// 用同一个值初始化:[值; 长度]
let zeros = [0; 10]; // 10 个 0
println!("{}", zeros[0]); // 0
// 越界访问会 panic(debug 会检查,release 也检查)
// 数组访问是"安全"的,不像 C 会读垃圾内存
// let x = nums[10]; // 运行时 panic:索引越界
// 数组长度是类型的一部分!
// [i32; 3] 和 [i32; 4] 是不同的类型,不能互相赋值
}关键点:
- 数组长度是类型的一部分——
[i32; 3]和[i32; 4]是不同类型,不能互相赋值或传参。这是为了编译期保证边界。 - 越界访问会 panic,但不会像 C 那样读垃圾内存。Rust 在运行时插入边界检查。
- 需要动态数组请用
Vec<T>(标准库的动态数组,在堆上)。
7. 类型转换(as)
Rust 不会自动类型转换——i32 和 f64 不能直接相加,必须显式转换。这避免了 C/C++ 里大量隐式转换导致的 bug。转换用 as 关键字:
fn main() {
let a = 5; // 推断为 i32
let b = 2.5; // 推断为 f64
// 不同类型之间不能直接运算,即使"看起来应该可以"
// let c = a + b; // 编译错误!i32 和 f64 不兼容
// 必须显式转换,用 as 关键字
let c = a as f64 + b; // OK,a 转换成 f64
println!("c = {}", c); // 7.5
// 整数与整数之间也要 as
let big: i64 = 1_000_000;
let small: i32 = big as i32; // 可能截断!小心
let from_float = 3.99 as i32; // 3,直接截断小数部分
// 字符与整数互转
let ch = 'A';
let code = ch as u32; // 65
let back = code as u8 as char; // 'A'as 转换是"可能丢失数据"的,编译器要求你显式承认这一点。比如 i64 转 i32 在值很大时会被截断。这是 Rust"显式胜于隐式"哲学的体现。
8. 字符串初识
字符串在 Rust 里比较特殊(有两种类型),专门有一篇深入讲。这里先记住:
- 字符串字面量用双引号,类型是
&str(字符串切片),编译期就确定,存在程序只读区。 - String 类型是堆上的、可增长的字符串,用
String的from方法创建。 - 两者都强制是合法 UTF-8,不存在"半个中文字符"问题。
9. println! 格式化
数据类型讲了这么多,顺便讲下怎么把它们打印出来。println! 的格式化能力很强:
fn main() {
let name = "小明";
let age = 20;
let pi = 3.14159;
// {} 默认输出
println!("我叫 {},今年 {}", name, age);
// {:?} 调试格式(适合数组、结构体)
let arr = [1, 2, 3];
println!("数组: {:?}", arr); // [1, 2, 3]
// :.N 浮点保留 N 位小数
println!("pi = {:.2}", pi); // 3.14
// :>N 右对齐宽度 N,:<N 左对齐,:^N 居中
println!("{:>10}", "hi"); // 8 空格 + hi
println!("{:-^10}", "hi"); // ----hi----
// 命名参数
println!("{x} + {y} = {z}", x = 1, y = 2, z = 3);
// 二进制/八进制/十六进制
println!("bin: {:b}", 15); // 1111
println!("hex: {:x}", 255); // ff
}记住几个最常用的:{} 普通输出、{:?:} 调试输出、{:.N} 浮点 N 位小数。
小结
这一篇你认识了 Rust 的全部基础类型:整数(按位宽精确命名)、浮点、布尔、4 字节 Unicode 的 char、元组(混合类型)、数组(固定长度)。最重要的概念是"类型严格"——整数和浮点不能隐式转换,数组长度参与类型,这些约束换来的是编译期消灭大量 bug。下一篇看运算符,看看这些类型怎么参与运算。
← 上一篇 Rust 变量与可变性
下一篇 Rust 运算符 →