找回密码
立即注册
搜索
热搜: Java Python Linux Go
发回帖 发新帖

1711

积分

0

好友

215

主题
发表于 前天 06:45 | 查看: 7| 回复: 0

坦白说,我第一次尝试 Rust 的时候,真想把笔记本电脑扔出窗外。借用检查器一直对我“大喊大叫”。那些在 Python 或 JavaScript 中本可以正常运行的代码,在这里就是无法编译通过。我花了两个小时调试一个问题,结果发现是生命周期注解的问题。

Rust项目git仓库信息统计

但有件事没人告诉你:那种挫败感?其实是这门语言在保护你,让你免于犯错。

第一个星期简直是地狱

每个学习 Rust 的人都会经历这个阶段。你写下一些看起来完全合理的代码:

fn main() {
    let data = vec![1, 2, 3, 4, 5];
    let first = &data[0];
    data.push(6);
    println!("First element: {}", first);
}

然后编译器只会告诉你“不行”。当你持有一个指向 data 的引用时,你不能修改它。在大多数语言中,这段代码都能编译通过。甚至可能在大部分时间里都能正常工作。直到有一天它突然罢工,而你不得不在凌晨两点焦头烂额地调试一个生产环境的事故。

Rust 编译器基本上就是那个烦人的朋友,总是不停地指出所有可能出错的地方。但不同的是,这个朋友永远是对的。

到底是什么让 Rust 这么难?

不是语法。它的语法其实相当简洁。真正让所有人头疼的是这三个概念:

  • 所有权 (Ownership) — 每个值都有一个所有者。当所有者离开作用域时,这个值就会被清理掉。
  • 借用 (Borrowing) — 你可以把数据的引用“借”出去,但必须遵守规则。要么只有一个可变引用,要么有多个不可变引用,但绝不能同时存在。
  • 生命周期 (Lifetimes) — 有时候你需要告诉编译器,这些引用的存活时间是多久。

下面这个真实的例子能让你豁然开朗:

struct User {
    name: String,
    email: String,
}

fn main() {
    let user = User {
        name: String::from("Alice"),
        email: String::from("alice@example.com"),
    };
    process_user(user);
    // user is gone now, ownership moved
    // println!("{}", user.name); // This won't compile
}

fn process_user(u: User) {
    println!("Processing {}", u.name);
}

变量 user 被移动到了 process_user 中。在那之后,它就消失了。你再也不能使用它了。一开始这感觉很奇怪,但这意味着 Rust 能精确地知道何时释放内存。完全不需要垃圾回收器。

某个时刻,你突然就顿悟了

在和编译器“搏斗”了几周后,一件奇妙的事情发生了。你开始用不同的方式思考你的代码。你甚至在下笔之前就能预见到潜在的 bug。

我记得我顿悟的那个瞬间。当时我正在编写一个处理用户会话的 Web 服务。在我之前的 Go 代码库里,我们曾遇到一个棘手的 bug:两个 goroutine 有时会同时修改同一个会话 map。这是一个竞争条件。它很少发生,但一旦出现,调试起来就是一场噩梦。

而在 Rust 中,我根本就写不出那样的 bug。编译器直接阻止了我:

use std::collections::HashMap;

struct SessionStore {
    sessions: HashMap<String, String>,
}

impl SessionStore {
    fn new() -> Self {
        SessionStore {
            sessions: HashMap::new(),
        }
    }

    fn add_session(&mut self, id: String, data: String) {
        self.sessions.insert(id, data);
    }

    fn get_session(&self, id: &str) -> Option<&String> {
        self.sessions.get(id)
    }
}

注意到 add_session 中的 &mut selfget_session 中的 &self 了吗?这正是 Rust 在表达“当有人正在写入时,你不能读取”的方式。类型系统强制保证了线程安全。没有运行时检查,也没有那些你可能会忘记获取的锁。

生产环境才是 Rust 大放异彩的地方

关于在生产环境中使用 Rust,有件事没人告诉你:你部署了它,然后……什么事都没发生。我这么说,绝对是最好的褒义。

没有内存泄漏慢慢吞噬你的 RAM。没有空指针异常导致的随机崩溃。也没有那些只在高负载下才会出现的“在我机器上能跑”的 bug。

我们将一个服务从 Node.js 迁移到了 Rust。Node 版本大约使用 400MB 内存,偶尔会飙升到 1GB。而 Rust 版本呢?50MB。非常稳定。没有峰值。没有垃圾回收导致的停顿。

// A simple HTTP handler that just works
use actix_web::{web, App, HttpResponse, HttpServer};

async fn health_check() -> HttpResponse {
    HttpResponse::Ok().body("OK")
}

#[actix_web::main]
async fn main() -> std::io::Result<()> {
    HttpServer::new(|| {
        App::new()
            .route("/health", web::get().to(health_check))
    })
    .bind("127.0.0.1:8080")?
    .run()
    .await
}

这段代码每秒能处理数千个请求。无需调优。异步运行时是内置的。错误处理是显式的。如果某个地方可能会失败,类型系统会强制你处理它。

错误处理的故事

说到错误处理,这正是 Rust 真正征服我的地方。没有异常。没有你可能会忘记的 try-catch 代码块。只有一个 Result 类型:

use std::fs::File;
use std::io::Read;

fn read_config(path: &str) -> Result<String, std::io::Error> {
    let mut file = File::open(path)?;
    let mut contents = String::new();
    file.read_to_string(&mut contents)?;
    Ok(contents)
}

fn main() {
    match read_config("config.txt") {
        Ok(config) => println!("Config: {}", config),
        Err(e) => println!("Failed to read config: {}", e),
    }
}

那个 ? 操作符做了很多工作。它的意思是“如果这步失败了,就返回错误”。你无法忽略它,也无法忘记处理它。因为编译器不允许你这么做。

真实的学习曲线

Rust 真实的学习曲线是这样的:

Frustration Level
     ^
     |                    *
     |                  *
     |                *
     |    * * * * * *
     |  *
     | *
     |*___________________*_________> Time
     Week 1    Week 3    Week 6
  1. 第 1-2 周:万事开头难。编译器很“刻薄”。你开始怀疑人生。
  2. 第 3-4 周:有些东西开始变得清晰了。你仍然在和借用检查器“搏斗”,但频率降低了。
  3. 第 5-6 周:豁然开朗。你开始能写出一次性编译通过的代码了。
  4. 之后:你真正开始享受它了。你更加信任自己的代码。你重构时不再感到恐惧。

信心加成

在生产环境中使用 Rust 最棒的一点是什么?当你的代码编译通过时,你就有相当大的把握它能正常工作。不是 100% 确定,但比大多数语言的把握要大得多。

我用 Rust 做过一些大型重构,如果换作其他语言,我可能会望而生畏。比如移动代码、更改数据结构、更新函数签名。编译器会告诉你每一个需要修改的地方。你修复所有错误后,它就能正常运行了。

// Before: passing primitives
fn calculate_price(quantity: i32, price: f64) -> f64 {
    quantity as f64 * price
}

// After: using a proper type
struct Order {
    quantity: i32,
    unit_price: f64,
}

impl Order {
    fn total(&self) -> f64 {
        self.quantity as f64 * self.unit_price
    }
}

当我从第一个版本修改到第二个版本时,编译器指出了我调用 calculate_price 的每一个地方。我把它们全部更新了。搞定。没有出现运行时意外。

值得吗?

对于业余项目?也许不值得。人们选择 Python 或 JavaScript 来编写快速脚本是有原因的。

对于需要可靠和快速的生产系统?那绝对值得。你花在学习 Rust 上的时间,会通过减少调试时间、降低生产事故和换来安稳的睡眠而得到回报。

在过去的 18 个月里,我们的 Rust 服务从未发生过与内存相关的崩溃。一次都没有。而我们的 Go 服务呢?以前每周都会遇到。

我的建议

如果你正在考虑学习 Rust,以下是一些对我有帮助的建议:

  • 从小处着手。 不要第一天就尝试构建一个 Web 框架。可以先构建一个命令行工具,解析一些文件。首先在简单的场景中熟悉所有权和借用。
  • 克制住将所有东西都设置为可变的冲动。 那是你旧的编程习惯在作祟。Rust 默认不可变是有原因的。
  • 阅读编译器错误。 是真正地去读它们。Rust 的错误信息是我见过最好的。它们会解释哪里出了问题,并且经常会建议修复方案。
  • 当你卡住时,休息一下。 有时候你需要睡一觉,问题就解决了。你的大脑需要时间来重新调整。

总结

是的,Rust 有学习曲线。这是真实存在的。但这条学习曲线并不是为了难而难。Rust 中每一个困难的部分都是为了防止某一类真实的错误而存在的。

一旦你越过了那条曲线,你写出的代码将兼具速度和安全性。你会在编译时而不是生产环境中捕获错误。你的服务能够稳定运行,让你睡得更安稳。

关于 Rust 学习曲线的抱怨,大多来自那些还没有在生产环境中使用过它的人。一旦你用过了,你就会明白为什么存在所有这些严格的限制。老实说,当你再回头去用其他语言时,你甚至会开始怀念它。




上一篇:前端开发者必学:Chrome插件开发从入门到实战(Manifest V3)
下一篇:React 新选择:TanStack Start、shadcn v2 与 Better Auth 实践心得
您需要登录后才可以回帖 登录 | 立即注册

手机版|小黑屋|网站地图|云栈社区 ( 苏ICP备2022046150号-2 )

GMT+8, 2026-1-14 17:43 , Processed in 0.343729 second(s), 39 queries , Gzip On.

Powered by Discuz! X3.5

© 2025-2025 云栈社区.

快速回复 返回顶部 返回列表