4. 基本数据类型:标量类型与复合类型
说到 Rust 的数据类型,我刚开始接触时也觉得有点多。但用久了你会发现,这些类型设计得其实很讲究。说白了,Rust 的类型系统就是帮你把「内存里到底存了什么」这件事说得明明白白。
Rust 是静态类型语言。这意味着每个变量在编译时就必须知道自己的类型。编译器会根据你给的值来推断,但有时候也需要你主动告诉它。
核心分类:Rust 的数据类型分为两大类——标量类型和复合类型。
4.1 整数类型
整数类型,就是不带小数点的数。Rust 提供了非常丰富的整数类型。我个人习惯用 i32 作为默认选择,因为它在大多数场景下性能最好。
| 长度 | 有符号 | 无符号 |
|---|---|---|
| 8 位 | i8 | u8 |
| 16 位 | i16 | u16 |
| 32 位 | i32 | u32 |
| 64 位 | i64 | u64 |
| 128 位 | i128 | u128 |
| 架构相关 | isize | usize |
有符号类型可以表示正数和负数,无符号类型只能表示正数。你想想看,如果你要存年龄、数量这种不可能为负的值,用 u32 更合适。
小技巧:isize 和 usize 的长度取决于你的操作系统。64 位系统上就是 64 位。我一般在处理数组索引或内存大小时用它们。
let a: i32 = -100; // 有符号,32位
let b: u8 = 255; // 无符号,8位,最大值
let c: isize = 1024; // 架构相关
let d = 42; // 默认推断为 i32
注意溢出:我曾经在项目中遇到过整数溢出的问题。Rust 在 debug 模式下会检查溢出并 panic,release 模式下会执行「环绕」操作。如果你需要明确处理溢出,可以用 wrapping_add 等方法。
4.2 浮点数类型
浮点数就是带小数点的数。Rust 只有两种:f32 和 f64。默认是 f64,因为现代 CPU 处理 64 位浮点数的速度和 32 位差不多,但精度更高。
let x: f32 = 3.14; // 32位浮点数
let y = 2.71828; // 默认推断为 f64
嗯,这里要注意:浮点数不支持 == 直接比较。因为浮点运算有精度误差。我建议用 (a - b).abs() < 1e-10 这种方式来判断是否「近似相等」。
4.3 布尔类型
布尔类型就两个值:true 和 false。大小是 1 个字节。没什么复杂的,但它是控制流的基石。
let is_ready: bool = true;
let is_done = false; // 类型推断
4.4 字符类型
Rust 的 char 类型很有意思。它不是 1 个字节,而是 4 个字节。为什么?因为它要表示 Unicode 标量值。这意味着你可以存中文、日文、甚至 emoji。
let c: char = 'R';
let zhong: char = '中';
let emoji: char = '😊';
重点:char 用单引号,字符串用双引号。别搞混了。我刚开始学 Rust 时就在这上面栽过跟头。
4.5 元组类型
元组可以把多个不同类型的值打包在一起。长度固定,一旦声明就不能增加或删除元素。
let tup: (i32, f64, char) = (42, 3.14, 'R');
// 解构(模式匹配)
let (x, y, z) = tup;
println!("x = {}, y = {}, z = {}", x, y, z);
// 用索引访问
let first = tup.0;
let second = tup.1;
我在项目中经常用元组来返回多个值。比如一个函数返回「状态码 + 错误信息」,用元组就很方便。
4.6 数组类型
数组和元组不同,数组里的所有元素必须是相同类型。长度也是固定的。
let arr: [i32; 3] = [1, 2, 3];
let first = arr[0]; // 索引从 0 开始
// 简写:初始化所有元素为相同值
let zeros = [0; 5]; // [0, 0, 0, 0, 0]
越界访问:Rust 会在运行时检查数组索引是否越界。如果越界,程序会 panic。我曾经在写循环时不小心索引越界,Rust 直接崩溃并告诉我具体是哪一行。虽然当时有点烦,但想想这比 C 语言那种「静默越界、内存被破坏」要好太多了。
4.7 类型推断与类型注解
Rust 编译器很聪明。它可以根据上下文推断出变量的类型。但有时候推断不出来,或者你想明确指定类型,就需要手动注解。
// 类型推断
let guess = "42".parse().expect("Not a number!");
// 上面这行会报错,因为编译器不知道 parse 成什么类型
// 类型注解
let guess: i32 = "42".parse().expect("Not a number!");
说白了,类型注解就是给编译器一个提示:「嘿,这个变量是 i32,你按这个来检查。」
我的习惯:在函数签名和公开接口中,我会明确写出类型注解。在函数内部的局部变量,我倾向于让编译器推断。这样代码既清晰又简洁。
类型推断不是 Rust 独有的,但 Rust 的推断能力确实很强。比如下面这个例子:
let numbers = vec![1, 2, 3];
let sum: i32 = numbers.iter().sum(); // 这里必须注解 sum 的类型
// 因为 sum() 可以返回多种数值类型,编译器需要你明确
嗯,记住一点就好:当编译器无法唯一确定类型时,你必须给出注解。其他时候,让编译器帮你推断就行。
好了,这一章的内容就到这里。数据类型是 Rust 的基石,理解透了后面学所有权、生命周期都会轻松很多。