4. 基础数据类型:从整数到元组,Rust的类型世界
聊到Rust的数据类型,我脑子里第一个蹦出来的词是「严谨」。这语言在类型上真是下了狠功夫。你想想看,一个变量是整数还是浮点数,是32位还是64位,Rust都要你清清楚楚。刚开始可能觉得烦,但我在项目里吃过亏之后,才明白这种严谨有多香。
核心要点:Rust是静态强类型语言。所有变量在编译时就必须知道类型。这能帮你把很多运行时错误扼杀在摇篮里。
4.1 标量类型:最基础的数据单元
标量类型,说白了就是单个值。Rust有四种:整数、浮点数、布尔、字符。咱们一个一个来。
4.1.1 整数类型
Rust的整数类型分有符号(i开头)和无符号(u开头)。长度从8位到128位,还有根据机器位数决定的isize和usize。
| 长度 | 有符号 | 无符号 |
|---|---|---|
| 8位 | i8 | u8 |
| 16位 | i16 | u16 |
| 32位 | i32 | u32 |
| 64位 | i64 | u64 |
| 128位 | i128 | u128 |
| 架构相关 | isize | usize |
我个人习惯,整数默认用i32。为什么?因为它在大多数CPU上效率最高。除非你有特殊需求,比如要存超大数字或者节省内存,否则i32够用了。
let a: i32 = 42; // 显式声明
let b = -128i8; // 字面量后缀
let c = 0xFF; // 十六进制
let d = 0o77; // 八进制
let e = 0b1010; // 二进制
let f = 100_000; // 下划线分隔,方便阅读
小技巧:数字里加下划线不会影响值,纯粹为了可读性。100_000和100000是一样的。我在写配置项时特别喜欢用这个。
4.1.2 浮点数类型
Rust只有两种浮点类型:f32和f64。默认是f64,因为现代CPU处理f64和f32速度差不多,但精度更高。
let x = 2.0; // f64,默认
let y: f32 = 3.14; // f32,显式声明
let z = 1.0e5; // 科学计数法,等于100000.0
注意:浮点数不能直接比较相等。我在项目中遇到过因为浮点精度问题导致的bug,排查了好久。建议用 (a - b).abs() < f64::EPSILON 这种方式来比较。
4.1.3 布尔类型
布尔类型就两个值:true和false。大小是一个字节。
let is_ok: bool = true;
let is_not = false;
if is_ok {
println!("没问题");
}
嗯,这里要注意。Rust不会自动把非布尔值转成布尔。不像C语言里0就是false,非0就是true。Rust要求条件表达式必须是bool类型。
4.1.4 字符类型
Rust的char类型很有意思。它占4个字节,能表示Unicode标量值。也就是说,中文、日文、emoji都能存。
let c = 'z';
let z: char = '中';
let emoji = '😊';
我曾经在解析用户输入时,用char类型来处理多语言文本,效果很好。但要注意,char是4字节,如果你只是处理ASCII字符,用u8更省内存。
4.2 复合类型:把多个值打包在一起
复合类型,就是把多个值组合成一个整体。Rust提供了元组和数组两种基本复合类型。
4.2.1 元组
元组可以包含不同类型的值。长度固定,一旦声明就不能改变。
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;
元组在函数返回多个值时特别有用。我经常用它来返回状态码和数据。
fn get_user_info() -> (String, u32, bool) {
(String::from("张三"), 28, true)
}
let (name, age, active) = get_user_info();
4.2.2 数组
数组和元组不同,它里面的元素必须是同一类型。长度也是固定的。
let arr: [i32; 5] = [1, 2, 3, 4, 5];
let zeros = [0; 10]; // 10个0
println!("第一个元素: {}", arr[0]);
println!("数组长度: {}", arr.len());
安全提示:Rust会在运行时检查数组越界。如果你访问arr[10],程序会直接panic。我曾经在写算法时忘了检查边界,结果程序崩溃了。后来养成了用get()方法的习惯,它返回Option类型,更安全。
// 安全的访问方式
if let Some(value) = arr.get(10) {
println!("找到了: {}", value);
} else {
println!("索引越界了");
}
4.3 类型推断与注解
Rust的编译器很聪明。它能根据上下文推断出变量的类型。但有时候,你也得明确告诉它。
let guess = "42".parse().expect("解析失败");
// 这行会报错,因为编译器不知道parse成什么类型
let guess: u32 = "42".parse().expect("解析失败");
// 加上类型注解,搞定
我个人习惯,在变量声明时尽量加上类型注解。虽然编译器能推断,但显式写出类型能让代码更清晰,也方便其他人阅读。
let count: u32 = 100; // 清晰明了
let name: String = "Rust".into(); // 一眼看出类型
4.4 类型转换:用as关键字
Rust不会做隐式类型转换。你想把i32转成f64?得用as关键字显式转换。
let a: i32 = 10;
let b: f64 = a as f64; // 10.0
let c: u8 = 255;
let d: i8 = c as i8; // -1,注意溢出
踩坑提醒:我曾经在项目里用as把大整数转成小整数,结果数据被截断了。as转换是直接截断的,不会四舍五入。如果你需要安全的转换,用TryFrom trait。
// 安全的转换方式
use std::convert::TryFrom;
let big = 256u16;
let small = u8::try_from(big);
match small {
Ok(v) => println!("转换成功: {}", v),
Err(e) => println!("转换失败: {}", e),
}
浮点数转整数也是直接截断,不是四舍五入。
let f = 3.99;
let i = f as i32; // 3,不是4
知识体系总览
下面这张图,把本章的核心内容串起来了。你可以看到数据类型的分支关系,以及它们之间的转换路径。
我的建议:刚开始学Rust,别怕类型系统。多写、多试、多犯错。编译器会告诉你哪里不对。等你习惯了这种严谨,再回头看其他语言,你会觉得Rust的类型系统真香。
公众号:蓝海资料掘金营,微信deep3321