第 3 章:还在用 `int` 走天下?醒醒吧!

已经玩过变量声明了,现在该面对现实了:你那些"万能"的 intString 在 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 一样,没什么好说的

f32f64,分别对应 Java 的 floatdouble。默认用 f64,除非你真的在意那几个字节。

布尔型:还是那个老实巴交的 bool

没变化,truefalse。不过别想着用 intbool,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 里,你必须明确地思考:

  • 谁拥有这个数据?
  • 谁在什么时候可以访问它?
  • 什么时候这个数据会被清理?

实战建议:什么时候用什么

简单粗暴的规则:

  1. 参数优先用 &str:更通用,性能更好
  2. 需要修改或拥有数据时用 String
  3. 函数返回值看情况:
    • 返回新创建的字符串?用 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 如何把你熟悉的 iffor 变成更强大的工具,以及传说中的 match 表达式——让 Java 的 switch 颤抖的存在。

准备好了吗?真正的挑战才刚刚开始。