基础数据类型

标量类型

Rust 是静态类型语言——每个值在编译时就有确定的类型。不用担心,Rust 的类型推断能力很强,大多数时候你不需要手动写类型,但理解它们是写出正确代码的基础。

整数类型

整数是最常用的类型。Rust 把整数分为有符号(可以是负数)和无符号(只能是非负数)两大类,并按位宽细分:

位宽有符号无符号范围(有符号)
8 位i8u8-128 ~ 127
16 位i16u16-32768 ~ 32767
32 位i32u32-约21亿 ~ 约21亿
64 位i64u64极大范围
128 位i128u128更大
指针宽度isizeusize取决于 CPU 架构(通常为32位或64位)
fn main() {
    let a: i32 = -42;      // 有符号,可以是负数
    let b: u32 = 100;      // 无符号,只能非负
    let c: u8  = 255;      // u8 最大值
    let d: i64 = 9_999_999_999; // 大数用下划线分隔,更易读

    println!("a={} b={} c={} d={}", a, b, c, d);
}

不写类型时的默认值:整数默认推断为 i32,这是最常用的整数类型。isizeusize 的宽度取决于 CPU 架构(64 位系统上是 64 位),主要用于集合的索引,如 arr[i] 中的 i 就是 usize

有符号 vs 无符号怎么选? 当值永远不会为负时用无符号(如数组长度 usize、字节数据 u8);其他情况用有符号更安全。

整型溢出u8 最大值是 255,赋值 256 会怎样?在 Debug 模式cargo build)下,Rust 会 panic——程序崩溃并报错,帮你发现 bug。在 Release 模式cargo build --release)下,Rust 不 panic,而是做”二进制补码包裹”:256 变 0,257 变 1……程序不崩溃,但值是错的。如果你需要有意地处理溢出,可以用 wrapping_addsaturating_addchecked_add 等方法。

浮点数

Rust 有两种浮点类型:

类型精度说明
f32单精度(约 7 位有效数字)性能更好,精度较低
f64双精度(约 15 位有效数字)默认类型,精度更高
fn main() {
    let x = 3.14;          // 默认 f64
    let y: f32 = 2.0;      // 显式指定 f32
    let z = 1.0_f64;       // 用后缀指定类型

    println!("{} {} {}", x, y, z);

    // 浮点运算
    println!("圆面积 = {:.4}", 3.14159 * 2.0_f64 * 2.0_f64);
}

浮点数有精度误差,不要用 == 直接比较两个浮点数是否相等,应使用差值是否足够小来判断。

布尔型与字符型

布尔型 bool 只有两个值:truefalse,常用于条件判断。

fn main() {
    let is_active: bool = true;
    let is_empty = false; // 类型推断

    println!("active: {}, empty: {}", is_active, is_empty);
    println!("AND: {}, OR: {}, NOT: {}",
             is_active && is_empty,
             is_active || is_empty,
             !is_active);
}

字符型 char 表示单个 Unicode 字符,用单引号包裹,占 4 字节

fn main() {
    let c1 = 'A';
    let c2 = '中';     // 汉字也是合法的 char
    let c3 = '😀';    // 表情符号同样可以
    let c4 = '\n';     // 转义字符

    println!("{} {} {} (换行在这里{})", c1, c2, c3, c4);
}

char 是 4 字节的 Unicode 标量值,而不是 ASCII 字节。这和 C 语言的 char(1 字节)不同。

字面量写法与类型后缀

Rust 支持多种进制的字面量,还可以在数字中插入下划线提升可读性:

字面量形式示例
十进制98_222
十六进制0xFF
八进制0o77
二进制0b1111_0000
字节(仅限 u8b'A'
fn main() {
    let decimal     = 255;          // 十进制
    let hex         = 0xFF;         // 十六进制(0x 前缀)
    let octal       = 0o377;        // 八进制(0o 前缀)
    let binary      = 0b1111_1111;  // 二进制(0b 前缀)
    let million     = 1_000_000;    // 下划线只是视觉分隔符
    let byte: u8    = b'A';         // 字节字面量,等价于 65u8

    // 类型后缀:直接写在数字后面指定类型
    let typed: u32  = 255u32;
    let also_typed  = 255_u8;       // 下划线可以放在后缀前

    println!("{} {} {} {} {} {} {} {}",
             decimal, hex, octal, binary, million, byte, typed, also_typed);
}

类型后缀的用途:当 Rust 无法从上下文推断类型时,直接在字面量后写类型:42u83.14f321000i64

复合类型

元组

元组可以把不同类型的多个值打包在一起,用圆括号 () 包裹:

fn main() {
    let t = (1i32, true, 'x', 3.14f64);

    // 用 .索引 访问元素(注意:是 .0 不是 [0])
    println!("第一个: {}", t.0);
    println!("第二个: {}", t.1);

    // 整个元组用 {:?} 打印
    println!("完整元组: {:?}", t);
}

元组用 .索引 访问,不能t[0]——这是元组和数组的重要区别。

元组解构与函数返回值

解构(destructure)可以把元组的每个值绑定到独立变量:

fn min_max(numbers: &[i32]) -> (i32, i32) {
    // 函数返回元组,实现多返回值
    (numbers.iter().copied().min().unwrap(),
     numbers.iter().copied().max().unwrap())
}

fn main() {
    let point = (10, 20);
    let (x, y) = point;  // 解构:把 10 绑定给 x,20 绑定给 y
    println!("x={}, y={}", x, y);

    // 元组作为多返回值
    let nums = [3, 1, 4, 1, 5, 9, 2, 6];
    let (min, max) = min_max(&nums);
    println!("最小值={}, 最大值={}", min, max);

    // 只有一个元素的元组需要尾随逗号以区分括号表达式
    let single = (42,);
    println!("单元素元组: {:?}", single);
    println!("这只是括号: {:?}", (42)); // 这是 i32,不是元组
}

数组

数组存储相同类型的多个值,长度编译时固定,存储在栈上:

fn main() {
    // 类型标注格式:[元素类型; 长度]
    let xs: [i32; 5] = [1, 2, 3, 4, 5];

    // 用相同值初始化:[初始值; 长度]
    let zeros: [i32; 3] = [0; 3];  // [0, 0, 0]

    println!("第一个: {}", xs[0]);
    println!("最后一个: {}", xs[xs.len() - 1]);
    println!("长度: {}", xs.len());
    println!("zeros: {:?}", zeros);

    // 越界访问会 panic(运行时错误)
    // println!("{}", xs[10]);  // panic: index out of bounds
}

数组长度是类型的一部分[i32; 5][i32; 6] 是两种不同类型,不能互相赋值。

切片

切片 &[T] 是对数组(或数组的一段)的引用视图,大小在编译时不固定:

fn sum_slice(slice: &[i32]) -> i32 {
    // 函数接受切片,可以处理任意长度的数组
    slice.iter().sum()
}

fn main() {
    let arr = [1, 2, 3, 4, 5];

    // 借用整个数组为切片
    let all: &[i32] = &arr;
    println!("全部: {:?}, 长度: {}", all, all.len());

    // 借用数组的一部分(半开区间 [1, 4),即索引 1、2、3)
    let part = &arr[1..4];
    println!("部分: {:?}", part);

    // 函数可以接受任意长度的切片
    println!("全部的和: {}", sum_slice(&arr));
    println!("部分的和: {}", sum_slice(&arr[1..4]));
}

数组 vs 切片的核心区别

数组 [T; N]切片 &[T]
大小编译时固定运行时动态
是否拥有数据否(只是引用)
用途存储固定长度序列函数参数、传递数据视图

运算符

常用运算符

Rust 支持算术、布尔、位运算等常见运算符,用法与 C 语言基本一致:

fn main() {
    // 算术运算
    println!("5 + 3 = {}", 5i32 + 3);
    println!("5 - 3 = {}", 5i32 - 3);
    println!("1 - 2 = {}", 1i32 - 2);  // 有符号才能得到负数

    // 整数除法:结果向零截断(不是向下取整)
    println!("5 / 2 = {}", 5i32 / 2);   // 2,不是 2.5
    println!("2 / 3 = {}", 2i32 / 3);   // 0!注意这个陷阱
    println!("43 % 5 = {}", 43i32 % 5); // 取余 = 3

    // 布尔运算(短路求值)
    println!("true && false = {}", true && false);
    println!("true || false = {}", true || false);

    // 位运算
    println!("0b0011 & 0b0101 = {:04b}", 0b0011u32 & 0b0101);  // AND
    println!("0b0011 | 0b0101 = {:04b}", 0b0011u32 | 0b0101);  // OR
    println!("0b0011 ^ 0b0101 = {:04b}", 0b0011u32 ^ 0b0101);  // XOR
    println!("1 << 3 = {}", 1u32 << 3);   // 左移
    println!("16 >> 2 = {}", 16u32 >> 2); // 右移
}

无符号整数减法陷阱1u32 - 2u32 会 panic(溢出)。如果可能出现负数,应使用有符号类型。

优先级完整列表

当一个表达式里有多个运算符时,谁先算、谁后算由优先级决定。优先级高的运算符先计算,同级从左到右。

Rust 的运算符优先级与 C 语言高度相似,如果你有 C/C++ 背景,大部分直觉可以直接沿用。主要差异在于:Rust 没有 C 的三目运算符 ? :as 类型转换替代了 C 的强制类型转换,以及比较运算符不支持链式写法。

下表从高到低排列 Rust 的运算符优先级:

优先级运算符说明结合性
1(最高)expr.fieldexpr.method()字段访问、方法调用左到右
2expr[index]索引左到右
3expr?错误传播
4-expr!expr*expr&expr&mut expr一元运算符(取负、逻辑非、解引用、借用)右到左
5as类型转换左到右
6*/%乘、除、取余左到右
7+-加、减左到右
8<<>>位移左到右
9&位与(Bitwise AND)左到右
10^位异或(Bitwise XOR)左到右
11|位或(Bitwise OR)左到右
12==!=<><=>=比较运算符要求括号
13&&逻辑与(短路)左到右
14||逻辑或(短路)左到右
15....=区间
16=+=-=*=/=%=&=|=^=<<=>>=赋值与复合赋值右到左
17(最低)returnbreak、闭包控制流表达式

比较运算符(==、!=、<、> 等)不支持链式比较1 < x < 10 在 Rust 中是非法的,必须写成 1 < x && x < 10

常见场景示例

fn main() {
    // 乘除优先于加减(和数学一样)
    println!("{}", 2 + 3 * 4);     // 14,不是 20

    // 位运算优先级低于算术,常需括号
    println!("{}", 1 + 2 & 3);     // 先算 1+2=3,再 3&3=3
    println!("{}", 1 + (2 & 3));   // 先算 2&3=2,再 1+2=3(不同!)

    // 比较运算符优先级低于算术
    println!("{}", 2 + 3 > 4);     // 先算 2+3=5,再 5>4=true

    // 逻辑与优先于逻辑或
    println!("{}", true || false && false); // 先算 false&&false=false,再 true||false=true

    // as 类型转换优先级较高
    let x = 3.99_f64 as i32 + 1;   // 先 as:3.99→3,再 3+1=4
    println!("{}", x);
}

最佳实践:多用括号

优先级规则记不住没关系——加括号让意图更清晰,比依赖优先级更好:

fn main() {
    let a = 5;
    let b = 3;
    let c = 2;

    // 不清晰:读者要记忆优先级
    let r1 = a + b * c & 0xFF;

    // 清晰:括号明确每步意图
    let r2 = (a + (b * c)) & 0xFF;

    println!("{} {}", r1, r2); // 结果相同,但 r2 的写法更易维护
}

Clippy 会提示:当表达式中混合了不同类的运算符(如算术和位运算)而没有括号时,cargo clippy 会建议你加上括号,这是 Rust 社区推荐的风格。

练习题

整数类型的范围

加载题目中…

默认整数类型

加载题目中…

元组的访问方式

fn main() {
    let t = (10, "hello", 3.14);
    println!("{}", t.1);
}
加载题目中…

char 的特点

加载题目中…

数组类型标注

加载题目中…

数组越界

fn main() {
    let arr = [1, 2, 3];
    println!("{}", arr[5]);
}
加载题目中…

整数除法

fn main() {
    let x: i32 = 7 / 2;
    println!("{}", x);
}
加载题目中…

整型溢出行为

加载题目中…

优先级计算

fn main() {
    println!("{}", 2 + 3 * 4 - 1);
}
加载题目中…

单元类型

fn say_hello() {
    println!("Hello!");
}

fn main() {
    let x = say_hello();
    println!("{:?}", x);
}
加载题目中…

编程练习 1

下面函数接受一个坐标元组并计算两点之间的距离,但有一处访问方式写错了,请修复:

fn distance(p1: (f64, f64), p2: (f64, f64)) -> f64 {
    let dx = p1[0] - p2[0]; // 错误的访问方式
    let dy = p1[1] - p2[1]; // 错误的访问方式
    (dx * dx + dy * dy).sqrt()
}

fn main() {
    let a = (0.0, 0.0);
    let b = (3.0, 4.0);
    println!("{}", distance(a, b));
}

编程练习 2

下面函数想计算百分比,但因为整数除法导致结果总是 0.0,请修复使其输出正确结果。

fn percentage(part: i32, total: i32) -> f64 {
    (part / total * 100) as f64
}

fn main() {
    println!("{:.1}", percentage(1, 3));
    println!("{:.1}", percentage(2, 5));
}