跳到主要内容

数据类型

问题

Rust 有哪些数据类型?类型系统有什么特点?

答案

Rust 是静态强类型语言——每个值在编译期都必须有确定的类型,且不支持隐式类型转换。同时 Rust 有强大的类型推断能力,大部分场景不需要手动标注类型。

标量类型(Scalar Types)

标量类型代表单个值,有四大类:

整数

长度有符号无符号范围(有符号)
8-biti8u8128-128 ~ 127127
16-biti16u1632768-32768 ~ 3276732767
32-biti32(默认)u32231-2^{31} ~ 23112^{31}-1
64-biti64u64263-2^{63} ~ 26312^{63}-1
128-biti128u1282127-2^{127} ~ 212712^{127}-1
平台相关isizeusize取决于平台(32/64 位)
fn main() {
let decimal = 98_222; // 十进制(可用 _ 分隔增强可读性)
let hex = 0xff; // 十六进制
let octal = 0o77; // 八进制
let binary = 0b1111_0000; // 二进制
let byte = b'A'; // 字节(u8)

let x: i32 = 42; // 默认整数类型是 i32
let index: usize = 0; // 数组索引必须是 usize
}
整数溢出
  • Debug 模式:整数溢出会 panic
  • Release 模式:整数溢出执行二进制补码包装(wrapping),不会 panic

如果需要显式控制溢出行为,使用标准库方法:

let x: u8 = 255;
x.wrapping_add(1); // 0(包装)
x.checked_add(1); // None(检查溢出)
x.saturating_add(1); // 255(饱和)
x.overflowing_add(1); // (0, true)(返回溢出标志)

浮点数

let x = 2.0;      // f64(默认)
let y: f32 = 3.0; // f32

// 浮点数遵循 IEEE 754 标准
assert!(0.1 + 0.2 != 0.3); // ⚠️ 浮点精度问题
assert!((0.1_f64 + 0.2 - 0.3).abs() < f64::EPSILON); // ✅ 正确比较方式

布尔与字符

let t: bool = true;
let f = false;

// char 是 4 字节 Unicode 标量值,不是 1 字节
let c: char = 'z';
let emoji: char = '🦀';
let chinese: char = '中';

assert_eq!(std::mem::size_of::<char>(), 4); // char 占 4 字节

复合类型(Compound Types)

元组(Tuple)

固定长度,各元素可以是不同类型:

fn main() {
let tup: (i32, f64, bool) = (500, 6.4, true);

// 解构
let (x, y, z) = tup;

// 索引访问(从 0 开始)
let first = tup.0; // 500
let second = tup.1; // 6.4

// 单元元组 ()(unit type)——空元组,不占内存
let unit: () = ();
}
单元类型 ()

() 是 Rust 的"空值",等价于其他语言的 void。不返回值的函数实际返回 ()

fn do_nothing() {
// 等价于 fn do_nothing() -> () { () }
}

数组(Array)

固定长度,所有元素同一类型,栈上分配:

fn main() {
let arr: [i32; 5] = [1, 2, 3, 4, 5];
let zeros = [0; 10]; // 创建 10 个元素都是 0 的数组

let first = arr[0];
let len = arr.len(); // 5

// 数组越界会在运行时 panic(编译期可能检查不到)
// let out = arr[10]; // ⚠️ index out of bounds: panic
}

切片(Slice)

对连续序列的引用视图,没有所有权:

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

let slice: &[i32] = &arr[1..4]; // [2, 3, 4]
let full: &[i32] = &arr[..]; // 全部
let from: &[i32] = &arr[2..]; // [3, 4, 5]
let to: &[i32] = &arr[..3]; // [1, 2, 3]
}

类型转换

Rust 不支持隐式类型转换,必须显式:

fn main() {
// as 关键字:基本类型之间的转换
let x: i32 = 42;
let y: f64 = x as f64;
let z: u8 = x as u8; // 截断高位

// ⚠️ as 转换可能丢失精度或截断
let big: i32 = 300;
let small: u8 = big as u8; // 300 % 256 = 44

// From/Into trait:安全的类型转换
let s = String::from("hello"); // From
let num: i64 = 42_i32.into(); // Into

// TryFrom/TryInto:可能失败的转换
let big_num: i32 = 1_000_000;
let result: Result<u16, _> = big_num.try_into(); // Err(超出 u16 范围)
}
方法安全性说明
as可能丢失精度基本类型之间的转换
From / Into安全、不会失败保证无损转换
TryFrom / TryInto返回 Result可能失败的转换

更多类型转换内容请参阅 类型转换

类型推断

Rust 编译器能根据上下文自动推断类型:

fn main() {
let x = 5; // 推断为 i32
let y = 5.0; // 推断为 f64
let v = vec![1, 2, 3]; // 推断为 Vec<i32>

// 有时需要类型标注帮助编译器
let parsed: i64 = "42".parse().unwrap(); // parse 需要知道目标类型
// 或者用 turbofish 语法
let parsed = "42".parse::<i64>().unwrap();
}

类型别名

type Kilometers = i32;
type Result<T> = std::result::Result<T, std::io::Error>;

// 类型别名不创建新类型,只是别名(可以和原类型混用)
let distance: Kilometers = 5;
let x: i32 = distance + 10; // ✅ Kilometers 就是 i32

如果想创建语义不同的新类型(阻止混用),使用 Newtype 模式:

struct Meters(f64);
struct Seconds(f64);

// Meters 和 Seconds 不能直接相加,即使底层都是 f64

更多内容请参阅 Newtype 模式

永远不返回的类型 !

! 是 never type,表示函数永远不会返回:

fn diverges() -> ! {
panic!("this function never returns");
}

// 常见于:
// - panic!()
// - loop {}(无限循环)
// - std::process::exit()
// - match 中的 unreachable!()

! 可以被强制转换为任意类型,这也是为什么 panic!() 可以出现在任意分支中:

let x: i32 = match some_option {
Some(v) => v,
None => panic!("no value"), // panic! 返回 !,可以当 i32 用
};

常见面试问题

Q1: Rust 的 i32isize 有什么区别?什么时候用哪个?

答案

  • i32:固定 32 位有符号整数,与平台无关
  • isize:平台相关的有符号整数,32 位系统上是 32 位,64 位系统上是 64 位

使用场景:

  • 数组索引必须使用 usize
  • 一般计算默认用 i32(编译器默认类型)
  • 与指针运算相关的场景isize/usize
  • FFI时匹配 C 的 size_t(对应 usize

Q2: Rust 的 char 和其他语言有什么不同?

答案

Rust 的 char4 字节的 Unicode 标量值(U+0000 ~ U+D7FF 和 U+E000 ~ U+10FFFF),而不是其他语言中常见的 1 字节 ASCII 字符。它可以表示任何 Unicode 字符,包括中文、emoji 等。

assert_eq!(std::mem::size_of::<char>(), 4);
let c = '🦀'; // 合法的 char

注意 char 和字符串中的字符不同——字符串是 UTF-8 编码,一个中文字符在 String 中占 3 字节,而 char 固定 4 字节。

Q3: 数组和 Vec 有什么区别?

答案

特性数组 [T; N]Vec<T>
大小编译期固定运行时可变
存储栈上堆上
性能更快(无间接寻址)有堆分配开销
使用场景大小已知的小数据动态集合
let arr: [i32; 3] = [1, 2, 3];  // 栈上,大小固定
let vec: Vec<i32> = vec![1, 2, 3]; // 堆上,可 push

更多集合内容请参阅 常用集合

Q4: 什么是单元类型 ()?有什么作用?

答案

() 是空元组,称为单元类型(unit type),大小为 0。它的作用类似其他语言的 void

  • 不返回有意义值的函数隐式返回 ()
  • HashMap<K, ()> 可以当 HashSet 用
  • Result<(), Error> 表示操作成功但无返回值
  • 语句的值是 ()(如 let x = 5; 返回 ()

Q5: as 转换和 From/Into 有什么区别?应该用哪个?

答案

方面asFrom/Into
安全性可能截断、溢出编译器保证无损
适用范围基本类型、裸指针任何实现了 trait 的类型
可扩展性内置操作,不可自定义可以为自定义类型实现
推荐度仅在明确知道行为时使用优先推荐

最佳实践:

  • 优先用 From/Into(安全、可读)
  • 可能失败时用 TryFrom/TryInto
  • 只在需要截断或原始类型转换时用 as

Q6: 什么是 never type !?有什么实际用途?

答案

! 表示函数永远不会正常返回(发散函数)。它可以被强制转换为任何类型,这在类型系统中有重要作用:

// 1. match 分支类型统一
let value: i32 = match input.parse::<i32>() {
Ok(n) => n, // i32
Err(_) => panic!(), // !,可作为 i32
};

// 2. loop 的返回类型
let result: String = loop {
break String::from("done"); // 用 break 返回值
};

// 3. 标记不可能的分支
enum Void {} // 没有变体,不可能被实例化

相关链接