4. 基础数据类型:标量类型与复合类型

数据类型,说白了就是告诉编译器「这块内存我要怎么用」。Rust 是静态类型语言,每个变量在编译时就必须知道自己的类型。我刚开始学的时候觉得这很烦,后来才发现——正是这种严格,帮我挡住了无数线上 bug。

4.1 标量类型:最基础的积木

标量类型代表单个值。Rust 有四种:整数、浮点、布尔、字符。

4.1.1 整数类型

整数分有符号(i)和无符号(u),后面跟位数。比如 i32 就是 32 位有符号整数。

长度 有符号 无符号
8 位i8u8
16 位i16u16
32 位i32u32
64 位i64u64
128 位i128u128
archisizeusize

我个人习惯:默认用 i32。除非你要存超大的数,或者做底层位操作。为什么?因为 i32 在大多数 CPU 上跑得最快,而且够用。

小技巧: 数字里可以加下划线增强可读性。比如 1_000_0001000000 清楚多了。

4.1.2 浮点类型

就两种:f32f64。默认是 f64,因为现代 CPU 处理 f64 和 f32 速度几乎一样,但精度更高。

let x = 2.0;      // f64
let y: f32 = 3.0; // f32,显式注解

我在项目中遇到过一个问题:用 f32 做累加,跑了几个小时之后误差累积到不可接受。换成 f64 就没事了。所以,除非你明确需要节省内存(比如存几百万个坐标),否则就用 f64。

4.1.3 布尔类型

就两个值:truefalse。大小是 1 个字节。

let is_ready = true;
let is_done: bool = false;

嗯,这里要注意:Rust 不会自动把非布尔值当成布尔用。比如 if 1 { ... } 会编译报错。你得显式写 if x != 0 { ... }。刚开始可能觉得啰嗦,但想想看——这避免了 C 语言里那种 === 搞混的经典 bug。

4.1.4 字符类型

char 类型代表一个 Unicode 标量值。注意,它是 4 个字节,不是 1 个。

let c = 'z';
let z = 'ℤ';
let heart_eyed_cat = '😻';

为什么是 4 字节?因为 Unicode 字符太多了,1 个字节根本装不下。你想想看,一个 emoji 就可能占 4 个字节。Rust 的 char 能表示所有 Unicode 字符,这是很多语言做不到的。

注意: 字符串不是 char 数组!字符串是 UTF-8 编码的,每个字符可能占 1-4 个字节。千万别用 char 去处理字符串,否则你会遇到一堆编码问题。我曾经踩过这个坑,处理中文时直接懵了。

4.2 复合类型:把积木拼起来

复合类型把多个值组合成一个。Rust 有两种基本复合类型:元组和数组。

4.2.1 元组

元组可以包含不同类型的值。长度固定,一旦声明就不能改变。

let tup: (i32, f64, u8) = (500, 6.4, 1);

访问元组元素有两种方式:

// 模式匹配解构
let (x, y, z) = tup;
println!("y 的值是:{}", y);

// 用点号加索引
let five_hundred = tup.0;
let six_point_four = tup.1;

我个人习惯用解构,代码更清晰。但如果你只需要其中一个值,用点号索引更直接。

实用场景: 函数返回多个值时,元组特别好用。比如返回 (状态码, 消息) 这种组合。

4.2.2 数组

数组里所有元素类型必须相同。长度也是固定的。

let a = [1, 2, 3, 4, 5];
let b: [i32; 5] = [1, 2, 3, 4, 5]; // 显式注解:类型;长度
let c = [3; 5]; // 等价于 [3, 3, 3, 3, 3]

访问数组元素用索引:

let first = a[0];
let second = a[1];

这里有个关键点:Rust 会在运行时检查数组越界。如果你访问 a[10],程序会直接 panic 退出。这比 C 语言那种「越界了也不知道,继续用脏数据」要安全得多。

避坑指南: 我曾经写过一个网络协议解析器,用数组存固定长度的包头。后来协议升级,包头变长了,我忘了改数组长度……嗯,程序直接崩溃。从那以后,我只要用数组,都会再三确认长度对不对。

4.3 类型推断与注解

Rust 编译器很聪明,能根据上下文推断类型。但有时候你得帮它一把。

let guess = "42".parse().expect("不是数字"); // 报错!编译器不知道你要什么类型
let guess: u32 = "42".parse().expect("不是数字"); // 显式注解,搞定

类型推断的规则很简单:

  • 如果编译器能唯一确定类型,就不需要注解
  • 如果有歧义,编译器会报错,让你显式指定
  • 函数签名必须显式注解参数和返回类型

你想想看,这其实是个很好的设计。既保留了静态类型的安全性,又减少了不必要的啰嗦。我个人建议:在变量声明处尽量显式注解类型。虽然编译器能推断,但写出来对代码阅读者(包括未来的你)更友好。

核心原则: 类型系统是你的朋友,不是敌人。它帮你把 bug 消灭在编译阶段,而不是留到线上。

4.4 知识体系总览

下面这张图帮你理清本章的知识结构:

Rust 基础数据类型 标量类型 整数 (i32, u8...) 浮点 (f32, f64) 布尔 (bool) 字符 (char) 复合类型 元组 (tuple) 数组 (array) 类型推断与注解

这张图把本章内容串起来了。标量类型是基础积木,复合类型是组合方式,类型推断和注解是连接你与编译器的桥梁。掌握好这些,你就有了构建复杂程序的基本功。

我的建议: 别急着背类型列表。先记住「默认用 i32 和 f64」,遇到特殊需求再查文档。用得多了,自然就记住了。

公众号:蓝海资料掘金营,微信deep3321