Rust 数据类型从 0 到 1:一篇能“跟着写、跟着懂”的入门指南

适合谁:刚开始学 Rust,觉得官方介绍分散、概念多、记不住。 你会获得什么:一张“类型地图” + 一套“选型规则” + 一堆“可复制运行的小例子” + 常见坑的避坑手册。 学完你应该能:看懂变量类型、写对字面量、知道何时用 i32/usize、理解溢出/越界为何会 panic,并用标准库方法显式处理。


目录(建议按顺序读)

  1. 先把“类型地图”装进脑子
  2. 整数:范围、字面量、怎么选类型
  3. 整数溢出:debug vs release,4 组官方处理方法
  4. 浮点:f32/f64 与“为什么会不精确”
  5. 布尔:bool 的真实用法
  6. 字符:char 不是 1 字节,也不是字符串
  7. 元组:一包不同类型的值,怎么拆、怎么取
  8. 数组:固定长度的连续内存,为什么会越界 panic
  9. 数组 vs Vec:什么时候该换 Vec
  10. 选型速查表 + 练习清单

1) 先把“类型地图”装进脑子

Rust 的基础数据类型分两大类:

A. 标量类型(Scalar Types)——一次一个值

  • 整数:i* / u* / isize / usize
  • 浮点:f32 / f64
  • 布尔:bool
  • 字符:char

B. 复合类型(Compound Types)——多个值组合成一个值

  • 元组:(T1, T2, ...)
  • 数组:[T; N]

本章最重要的一句话: Rust 是静态类型语言,编译器必须在编译期知道类型。大多数情况下能推断;推断不了就需要你写 : 类型


2) 整数:范围、字面量、怎么选类型

2.1 iu:负数能不能存在

  • i 开头:有符号整数(可以为负),如 i32
  • u 开头:无符号整数(永远非负),如 u32

Rust 的整数都是固定宽度:8/16/32/64/128 位,还有跟平台相关的 isize/usize

位宽有符号无符号
8-biti8u8
16-biti16u16
32-biti32u32
64-biti64u64
128-biti128u128
依赖架构isizeusize

2.2 范围怎么理解(不用死背)

你只要记住一个规律:

  • 有符号:-(2^(n-1)) ..= 2^(n-1)-1
  • 无符号:0 ..= 2^n - 1

例如:

  • i8-128..=127
  • u80..=255

小贴士:如果你想在代码里直接用最大/最小值,不必背范围: i32::MAXi32::MINu8::MAX 等都可以直接用。

2.3 isize/usize:为什么到处都是 usize

你会在“索引”和“长度”里频繁看到 usize

  • len() 返回 usize
  • 数组/切片索引需要 usize
  • 因为 usize 与平台指针宽度一致(64 位机器通常是 64 位)

初学者选型规则(非常实用):

  • 不确定就用 i32(Rust 默认整数类型就是 i32
  • 只要是索引/长度(len()、数组下标、切片范围)就用 usize
  • 明确只会非负且可能很大(计数、容量)可以考虑 u32/u64

2.4 整数字面量:写法越多,越容易读懂别人的代码

写法示例
十进制98_222
十六进制0xff
八进制0o77
二进制0b1111_0000
字节(仅 u8b'A'
  • _ 只是视觉分隔符:1_000 = 1000
  • 字面量可以加类型后缀:57u810i64

可运行小例子:

fn main() {
let a = 10; // 默认 i32
let b = 10u8; // u8
let c: i64 = 10; // i64
let hex = 0xff; // 默认 i32
let bin = 0b1010_0001;
let byte = b'A'; // u8
println!("{a} {b} {c} {hex} {bin} {byte}");
}

3) 整数溢出:debug vs release,4 组官方处理方法

你很快会遇到一个震撼新手的问题:同样的代码,debug 会 panic,release 反而不 panic?

3.1 两种编译模式的默认行为

  • debug(默认 cargo run 溢出检查更严格,溢出会 panic
  • release(cargo run --release 默认不做会 panic 的溢出检查,溢出会 回绕(wrapping)

比如 u8 最大是 255:

  • 256 在回绕模式下会变成 0
  • 257 变成 1

官方态度:依赖回绕通常是错误。你应该“显式选择”你想要的行为。

3.2 标准库给你的 4 种“显式”策略(强烈建议掌握)

对整数原始类型,有这些方法家族:

  1. wrapping_*:总是回绕
  2. checked_*:溢出返回 None
  3. overflowing_*:返回 (值, 是否溢出)
  4. saturating_*:溢出钳到最大/最小

对比示例:

fn main() {
let x: u8 = 250;
println!("wrapping_add: {}", x.wrapping_add(10));
println!("checked_add: {:?}", x.checked_add(10));
println!("overflowing_add: {:?}", x.overflowing_add(10));
println!("saturating_add: {}", x.saturating_add(10));
}

你在写业务代码时,最常用的是 checked_*(安全计算)和 saturating_*(比如 UI 进度条/百分比)。


4) 浮点:f32/f64 与“为什么会不精确”

4.1 两种浮点类型

  • f32:32 位
  • f64:64 位(默认)
fn main() {
let x = 2.0; // f64
let y: f32 = 3.0; // f32
println!("{x} {y}");
}

4.2 你必须知道:浮点是近似

浮点遵循 IEEE-754 表示法,很多十进制小数无法用二进制精确表示(比如 0.1)。 所以 0.1 + 0.2 不一定等于“肉眼认为的 0.3”。

新手建议:

  • 金额、精确计数等场景不要直接用浮点;后续应学习定点/整数表示或专用库(这属于进阶主题)。

5) 布尔:bool 的真实用法

bool 只有 true/false,常用于条件控制:

fn main() {
let ok = true;
let fail: bool = false;
if ok && !fail {
println!("condition is true");
}
}

记住:Rust 的 if 是表达式(能产生值),但分支必须类型一致(这是很多初学者第一次被“类型系统教育”的地方)。


6) 字符:char 不是 1 字节,也不是字符串

6.1 char 的关键事实

  • 单引号'a'
  • 4 字节
  • 表示一个 Unicode 标量值:能表示中文、emoji 等
fn main() {
let a = 'z';
let b: char = 'ℤ';
let c = 'c';
let d = '中';
println!("{a} {b} {c} {d}");
}

重要提醒:char ≠ “人类直觉里的一个字形”。有些“看起来一个字符”的东西可能由多个 Unicode 单元组成。 字符串与 UTF-8 会在后续章节系统讲。


7) 元组:一包不同类型的值(怎么拆、怎么取)

元组适合“把几个相关但类型不同的值放一起”,长度固定。

7.1 创建与标注

fn main() {
let tup: (i32, f64, u8) = (500, 6.4, 1);
println!("{:?}", tup);
}

7.2 解构(destructuring)

fn main() {
let tup = (500, 6.4, 1);
let (x, y, z) = tup;
println!("x={x}, y={y}, z={z}");
}

7.3 点号取值(按位置)

fn main() {
let x: (i32, f64, u8) = (500, 6.4, 1);
println!("{} {} {}", x.0, x.1, x.2);
}

7.4 单元类型 ()

  • () 既是一个值,也是一个类型
  • 常见:函数不返回有意义的值时,返回 ()
  • 许多语句块在没有返回其他值时会“隐式产生 ()

8) 数组:固定长度的连续内存(为什么会越界 panic)

数组是 固定长度元素同类型 的集合。

8.1 创建数组

fn main() {
let a = [1, 2, 3, 4, 5];
println!("{:?}", a);
}

8.2 写出类型 [T; N]

fn main() {
let a: [i32; 5] = [1, 2, 3, 4, 5];
println!("{:?}", a);
}

8.3 快速重复初始化 [value; len]

fn main() {
let a = [3; 5]; // [3,3,3,3,3]
println!("{:?}", a);
}

8.4 索引访问与越界

fn main() {
let a = [10, 20, 30, 40, 50];
let first = a[0];
let second = a[1];
println!("{first} {second}");
}

为什么越界会 panic?

当你写 a[index],Rust 会在运行时检查 index < a.len(),越界就 panic。 这是为了安全:防止访问非法内存。

8.5 更“博客推荐”的安全写法:用 get

get 返回 Option

  • Some(&value):合法
  • None:越界
fn main() {
let a = [1, 2, 3, 4, 5];
let index: usize = 10;
match a.get(index) {
Some(v) => println!("value = {v}"),
None => println!("index out of bounds"),
}
}

新手非常推荐:这能让你自然练到 Option + match,写出不会崩溃的程序。


9) 数组 vs Vec:什么时候该换 Vec?

官方的直觉很简单:

  • 数组 [T; N]:元素数量固定(比如 12 个月)
  • 向量 Vec<T>:需要增长/缩小(绝大多数集合场景)

初学者一句话:不确定就用 Vec。数组更像“固定配置”。


10) 选型速查(记住这几条,少走 80% 弯路)

10.1 整数默认选 i32

  • 你没写类型后缀/标注时,Rust 默认整数推断为 i32
  • 大多数业务场景 i32 足够

10.2 索引/长度用 usize

  • len()usize
  • 下标 a[index]index: usize
  • 切片范围 &a[0..n]n: usize

10.3 u8 常见于字节

  • b'A'u8
  • 网络/文件/编码场景常用 u8

10.4 char 不是字符串

  • 一个 char 是 4 字节的 Unicode 标量值
  • 字符串是 &str/String(后面章节)