第 9 章:你们 Java 程序员最恨什么?

NullPointerException!那个价值"十亿美元的错误",让多少个深夜变成了调试地狱。

还有什么?try-catch 的噩梦!要么用受检异常把方法签名搞得一团糟,要么用非受检异常埋下定时炸弹。

Rust 说:这些垃圾问题,我一个都不要。

Java 错误处理:两个极端的灾难

NPE:运行时的恐怖故事

// Java 的日常灾难
public String getUserName(int id) {
    User user = findUser(id);  // 可能返回 null
    return user.getName();     // 💥 NPE炸弹!
}

你永远不知道哪一行会炸。 防御性编程?写一堆 if (obj != null) 检查?代码变得恶心不说,还不一定管用。

异常系统:要么繁琐,要么危险

// 受检异常:签名污染
public void readFile() throws IOException, FileNotFoundException, SecurityException {
    // 方法签名变成垃圾场
}

// 非受检异常:隐形炸弹
public void process() {
    // 谁知道这里会抛出什么RuntimeException?
    doSomething();  // 可能炸
    doSomethingElse();  // 也可能炸
}

两个选择都很烂:要么污染所有调用链,要么埋下隐形地雷。

Rust 革命:错误即类型

Rust 的哲学:错误不是意外,是程序状态的一部分。

Option:NPE 的终极解药

Rust 中没有 null。彻底没有。

enum Option<T> {
    Some(T),  // 有值
    None,     // 没值,但这是明确的状态
}

看看什么叫类型安全

fn find_user(id: u32) -> Option<String> {
    if id == 1 {
        Some("Alice".to_string())
    } else {
        None  // 明确表示"没找到"
    }
}

fn main() {
    let user = find_user(2);

    // 编译器强制你处理 None 的情况
    match user {
        Some(name) => println!("找到用户: {}", name),
        None => println!("用户不存在"),
    }

    // 试试直接使用?编译错误!
    // println!("{}", user);  // 类型不匹配,编译器拒绝
}

看到了吗?编译器不给你犯错的机会。

NPE?不存在的。 因为编译器强制你在使用值之前,先检查它是否存在。

Option 的实战技巧

let config_value = get_config("database_url");

// 方法1:match 表达式(推荐)
match config_value {
    Some(url) => connect_to_database(url),
    None => use_default_database(),
}

// 方法2:unwrap_or(提供默认值)
let url = config_value.unwrap_or("localhost:5432".to_string());

// 方法3:if let(简洁语法)
if let Some(url) = config_value {
    println!("使用配置的数据库: {}", url);
}

每种方法都比 Java 的 null 检查更安全、更清晰。

Result<T, E>:异常系统的优雅替代

Java 的异常往上抛,Rust 的错误往下传。

enum Result<T, E> {
    Ok(T),   // 成功,带返回值
    Err(E),  // 失败,带错误信息
}

实战例子:

use std::fs::File;
use std::io::ErrorKind;

fn open_file(filename: &str) -> Result<File, std::io::Error> {
    File::open(filename)  // 返回 Result,不抛异常
}

fn main() {
    match open_file("config.txt") {
        Ok(file) => println!("文件打开成功: {:?}", file),
        Err(error) => match error.kind() {
            ErrorKind::NotFound => println!("文件不存在"),
            ErrorKind::PermissionDenied => println!("权限不足"),
            other_error => println!("其他错误: {:?}", other_error),
        },
    }
}

对比 Java:

// Java 的丑陋做法
try {
    FileInputStream file = new FileInputStream("config.txt");
    // 成功处理
} catch (FileNotFoundException e) {
    // 文件不存在
} catch (SecurityException e) {
    // 权限不足
} catch (IOException e) {
    // 其他IO错误
}

哪个更清晰?哪个更安全?答案显而易见。

? 操作符:错误传播的语法糖

最强大的武器来了:? 操作符。

Java 的错误传播:

public String processFile() throws IOException {
    String content = readFile();      // 可能抛异常
    String processed = process(content); // 可能抛异常
    return writeFile(processed);      // 可能抛异常
}

每个调用都可能炸,方法签名被 throws 污染。

Rust 的错误传播:

fn process_file() -> Result<String, std::io::Error> {
    let content = read_file()?;      // 如果出错,直接返回错误
    let processed = process(content)?; // 如果出错,直接返回错误
    write_file(processed)            // 返回 Result
}

? 操作符的魔法:

  • 如果是 Ok(value),提取 value 继续执行
  • 如果是 Err(error),立即返回错误
  • 没有异常栈展开的开销
  • 错误类型在编译期确定

连锁调用的优雅

// 链式错误处理
fn complex_operation() -> Result<String, Box<dyn std::error::Error>> {
    let data = fetch_data()?
        .parse_json()?
        .validate()?
        .transform()?;

    Ok(format!("处理完成: {}", data))
}

一行代码,四个可能的错误点,优雅处理。

性能对比:异常 vs Result

Java 异常的隐性成本

// Java 异常的成本
try {
    return expensiveOperation();
} catch (Exception e) {
    // 异常处理需要:
    // 1. 栈展开(stack unwinding)
    // 2. 构造异常对象
    // 3. 收集调用栈信息
    return defaultValue;
}

Rust Result 的零成本

// Rust Result 是零成本抽象
fn expensive_operation() -> Result<String, MyError> {
    // 成功路径:直接返回值,无额外开销
    // 错误路径:返回错误值,无栈展开
    Ok("success".to_string())
}

Result 在成功路径上是零开销的。 错误处理不影响正常逻辑的性能。

实用技巧:Result 和 Option 的组合

fn find_and_parse_config(key: &str) -> Result<i32, String> {
    let value = get_config(key)
        .ok_or("配置项不存在")?;  // Option 转 Result

    value.parse()
        .map_err(|_| "配置值格式错误".to_string())  // 错误转换
}

// 使用
match find_and_parse_config("port") {
    Ok(port) => println!("端口: {}", port),
    Err(msg) => eprintln!("配置错误: {}", msg),
}

写在最后:错误处理的哲学革命

Java 的错误处理哲学:异常是意外,能避免就避免。

Rust 的错误处理哲学:错误是常态,必须显式处理。

这种哲学差异带来的收益是巨大的:

  • 编译期安全 - 所有错误都必须处理
  • 性能优势 - 无异常开销,无栈展开成本
  • 代码清晰 - 错误路径和成功路径同样清楚
  • 类型安全 - 错误类型在编译期确定

当你习惯了这种错误处理方式,你会发现:

  • 不再害怕 NPE,因为它不可能存在
  • 不再忘记处理错误,因为编译器会提醒你
  • 不再写防御性代码,因为类型系统就是最好的防御
  • 不再为异常性能担心,因为错误处理是零成本的

这就是现代编程语言应有的错误处理方式。

下一章我们要学习 Rust 的生态系统和包管理。准备好体验真正的现代化开发流程了吗?

因为在 Rust 的世界里,不只是语言本身优秀,整个生态系统都是为了让你写出更好的代码。