已经玩过变量声明了,现在该面对现实了:你那些"万能"的 int
、String
在 Rust 面前就是个笑话。
别以为换个语言就是换个语法糖。Rust 的类型系统要重新刷新你对"数据"这个概念的认知。准备好被虐了吗?
标量类型:精确到令人发指
Java 程序员最大的坏习惯是什么?类型选择恐惧症的反面——什么都用 int
。
存储年龄?int
。存储网站状态码?int
。存储人口数量?还是 int
。
停!
Rust 说:你的时代结束了。
整型:告别"万能 int"时代
看看这张表,感受一下什么叫精确:
位数 | 有符号 | 无符号 | 你以前用什么 |
---|---|---|---|
8-bit | i8 |
u8 |
int (浪费了 3/4 内存) |
16-bit | i16 |
u16 |
int (浪费了 1/2 内存) |
32-bit | i32 |
u32 |
int (这才对) |
64-bit | i64 |
u64 |
long (终于想起来了) |
128-bit | i128 |
u128 |
BigInteger (开始慌了?) |
看到 u8
了吗?8 位无符号整数,范围 0-255。完美适合 HTTP 状态码。
还在用 int
存储状态码?恭喜你,你为一个最大只需要 3 位数的值,浪费了至少 16 位的内存。在一个日处理千万请求的系统里,这种"小事"能让你的内存成本翻倍。
// 这才是专业的做法
let status_code: u16 = 404; // HTTP状态码最大999
let age: u8 = 25; // 人类年龄最大255绰绰有余
let population: u64 = 7_800_000_000; // 世界人口需要大数
每个类型都有它存在的理由。选错了,就是对内存的犯罪。
浮点型:这个和 Java 一样,没什么好说的
f32
和 f64
,分别对应 Java 的 float
和 double
。默认用 f64
,除非你真的在意那几个字节。
布尔型:还是那个老实巴交的 bool
没变化,true
和 false
。不过别想着用 int
当 bool
,Rust 不吃这一套。
字符型:这里有个大惊喜
Java 的 char
是 16 位的半吊子。Rust 的 char
是 32 位的真·Unicode支持:
let c = 'Z';
let chinese = '中';
let emoji = '😎'; // 没错,表情符号也是一个char
在 Java 里处理 Emoji 还要考虑代理对?Rust 表示这些都是小儿科。
复合类型:组合的艺术
元组:Java 程序员的新朋友
Java 里想返回多个值?要么定义个类,要么用 Object[]
。都 2025 年了,还在这么原始?
fn get_name_and_age() -> (String, u8) {
("张三".to_string(), 25)
}
let (name, age) = get_name_and_age();
// 一行代码,搞定多返回值
这种操作在 Java 里至少要写个专门的类。Rust 的元组让你用最少的代码表达最清楚的意图。
数组:栈上的性能野兽
Java 的数组?堆上分配,GC 管理,性能开销一大堆。
Rust 的数组?栈上分配,编译期确定大小,快到飞起:
let weekdays: [&str; 7] = [
"Monday", "Tuesday", "Wednesday", "Thursday",
"Friday", "Saturday", "Sunday"
];
长度固定?没错。但这种"限制"换来的是无与伦比的性能。
想要动态数组?用 Vec
。但别什么时候都想着动态,有时候固定数组才是最优解。
终极对决:String vs &str
这是最重要的部分。不理解这个,你就永远写不出地道的 Rust 代码。
Java String 的"舒适圈"
在 Java 里,String
就是一切:
String name = "张三";
String message = buildMessage(name);
// GC 帮你管理一切,你什么都不用想
舒适吗?当然。高效吗?呵呵。
Rust 的"残酷真相"
Rust 告诉你:世界上没有免费的午餐。
要么你拥有数据(String
),要么你借用数据(&str
)。没有第三条路。
// String: 我拥有这块内存
let owned: String = String::from("我是老板");
// &str: 我只是过来看看
let borrowed: &str = "我是打工的";
核心区别:拥有 vs 借用
特性 | String |
&str |
---|---|---|
内存 | 堆上分配,我说了算 | 指向别人的内存 |
性能 | 分配有开销 | 轻量到忽略不计 |
可变性 | 可变(需要mut ) |
只读 |
适用场景 | 需要拥有和修改数据时 | 只需要读取时 |
一个血淋淋的例子:
fn process_name(name: String) {
// 这个函数"夺走"了 name 的所有权
println!("Processing: {}", name);
} // name 在这里被销毁
fn read_name(name: &str) {
// 这个函数只是"借用"一下
println!("Reading: {}", name);
} // 什么都不会发生
fn main() {
let my_name = String::from("张三");
process_name(my_name); // my_name 被"吃掉"了
// println!("{}", my_name); // 编译错误!my_name 已经不存在了
let my_name2 = String::from("李四");
read_name(&my_name2); // 只是借用
println!("{}", my_name2); // 完全没问题
}
看到了吗?这就是所有权系统在起作用。
在 Java 里,你从来不用担心对象什么时候被回收。在 Rust 里,你必须明确地思考:
- 谁拥有这个数据?
- 谁在什么时候可以访问它?
- 什么时候这个数据会被清理?
实战建议:什么时候用什么
简单粗暴的规则:
- 参数优先用
&str
:更通用,性能更好 - 需要修改或拥有数据时用
String
- 函数返回值看情况:
- 返回新创建的字符串?用
String
- 返回输入的一部分?用
&str
- 返回新创建的字符串?用
// 好的设计
fn greet(name: &str) -> String { // 接收借用,返回拥有
format!("Hello, {}!", name)
}
// 糟糕的设计
fn bad_greet(name: String) -> String { // 强制调用者交出所有权
format!("Hello, {}!", name)
}
写在最后:类型不是束缚,是武器
Java 程序员习惯了"一刀切"的类型使用:int
走天下,String
包办一切。
Rust 告诉你:精确的类型选择就是性能优化的第一步。
- 用
u8
而不是i32
存储 HTTP 状态码?你节省了 75%的内存 - 用
&str
而不是String
传递参数?你避免了不必要的内存分配 - 用固定长度数组而不是动态集合?你获得了栈上分配的极致性能
这不是束缚,这是对计算机硬件的尊重。
更重要的是,通过 String
和 &str
的对比,我们第一次真正接触了所有权系统的核心思想:在编译期就确定谁拥有什么,什么时候释放。
没有 GC 的魔法,只有编译器的严格检查。听起来可怕?等你体验过零开销抽象的快感,你就再也回不去了。
下一章我们要学控制流。准备好看看 Rust 如何把你熟悉的 if
、for
变成更强大的工具,以及传说中的 match
表达式——让 Java 的 switch
颤抖的存在。
准备好了吗?真正的挑战才刚刚开始。