Rust 程序员的前半年:从 hello world 到系统工具发布的历程

Rust 程序员的前半年:从 hello world 到系统工具发布的历程

一、2026 年 1 月 3 日,我的第一行 Rust 代码

那天我把 Rust 装好,打开 VS Code,照着《The Rust Programming Language》第一章写了这段代码:

fn main() {
    println!("Hello, world!");
}

编译花了 45 秒。我盯着终端的 cargo run 输出,看到那个熟悉的 Hello, world!,心里想的是:"这就完了?和 Python 也没区别啊。"

三天后我就知道错了。错的离谱。

我在写一个文件读写的程序,Python 10 行搞定的事,Rust 编译器给了我 47 行错误。关键词包括 borrowlifetimeownershipmove——我一个都不懂。那天晚上我失眠了,躺在床上想的是:"我一个写 PHP 的,为什么要来碰 Rust?"

半年后,2026 年 7 月 31 日,我的 dayuan 项目在 GitHub 上有了 270 个 star,我在 CSDN 上写了累计 40 万字的 Rust 技术文章。回头看那个失眠的夜晚,我想对当时的自己说:"这 47 行错误不是终点,是你理解计算机底层的第一课。"

这篇文章是我半年的转码历程复盘。我不打算写成"从零到精通"的成功学,而是想诚实地记录:哪些阶段最痛苦、哪些方法真的有效、哪些"常识"对来说根本不存在。

二、半年路线图

三、四个最痛苦的阶段和我是怎么熬过来的

阶段一:语法关 —— "PHP 10 行,Rust 100 行"

第一个月的最大冲击不是所有权,是类型系统。PHP 里我写 $data = json_decode($response) 从来不用管类型。Rust 里我必须明确 HashMap<String, serde_json::Value>,而且 JSON 解析失败不是抛异常而是返回 Result

// 这是我 1 月份写的第一段"完整"代码,花了两天才调通
use serde::Deserialize;

/// 定义 API 返回的数据结构
#[derive(Deserialize, Debug)]
struct ApiResponse {
    code: i32,
    message: String,
    data: Option<Vec<User>>,  // data 可能是空的!
}

/// 定义用户结构
#[derive(Deserialize, Debug)]
struct User {
    id: u64,
    name: String,
    email: String,
}

/// 调用 API 并解析返回数据
fn fetch_users() -> Result<Vec<User>, Box<dyn std::error::Error>> {
    // ① 发起 HTTP 请求,可能失败
    let response = reqwest::blocking::get("https://api.example.com/users")?;
    //                                               ?: 如果请求失败,直接返回错误
    
    // ② 解析 JSON,可能失败
    let api_response: ApiResponse = response.json()?;
    //                                           ?: 如果 JSON 格式不对,返回错误
    
    // ③ 检查业务状态码
    if api_response.code != 200 {
        return Err(format!("API 返回错误: {}", api_response.message).into());
        //   ^^^  return Err(...): 显式返回错误,不是抛异常
    }
    
    // ④ 提取数据,data 可能为空
    Ok(api_response.data.unwrap_or_default())
    // ^^ Ok(...): 显式返回成功结果
    //          unwrap_or_default(): 如果 data 是 None,返回空 Vec
}

这段代码现在看起来很简单,但 1 月份我花了整整两天。每个 ?、每个 Result、每个 Option 对我都是全新的概念。

熬过这关的方法:每弄懂一个概念就写一个最小化代码示例,存到 ~/rust-notes/ 目录。到月底这个目录有 47 个文件——每一个都是一个独立的知识点卡片。

阶段二:所有权崩溃 —— "编译器比产品经理还难伺候"

2 月份我开始写一个简单的 CLI 工具,遇到了所有权问题:

fn process_files(paths: Vec<String>) {
    let mut config = Config::default();  // config 在这里
    
    for path in &paths {
        let content = std::fs::read_to_string(path).unwrap();
        
        // 想把这个文件处理结果存到 config 里
        config.add_result(path, content);  // ❌ config 被 move 了?
        // 下一轮循环 config 就不能用了
    }
}

// 当时我唯一的解决方案就是:
// let config = config.clone(); // 到处 clone,性能差,代码丑

我不懂为什么 PHP 里 $config 随便传都不会出问题,而 Rust 里传一次就"死"了。这个问题折磨了我整整两周。

真正理解的转折点:有天我在 B 站看到一个 UP 主画了一张图解释栈和堆的区别。他说"Rust 的所有权不是设计规则,是物理规律——栈上的数据复制快,堆上的数据复制慢,所以 Rust 默认不帮你复制堆数据"。我反复听了三遍,突然明白了。

阶段三:异步编程 —— "tokio::spawn 为什么一直报错?"

3 月份我想给 dayuan 加异步功能,结果被 async/.await 和 Tokio 的组合拳打得怀疑人生:

// 当时报错的代码(精简版)
use tokio::net::TcpListener;

#[tokio::main]
async fn main() {
    let listener = TcpListener::bind("127.0.0.1:8080").await.unwrap();
    
    loop {
        let (socket, _) = listener.accept().await.unwrap();
        
        // ❌ 编译报错:future cannot be sent between threads safely
        tokio::spawn(async {
            handle_connection(socket).await;
        });
    }
}

我花了三天才明白:tokio::spawn 要求闭包是 Send + 'static,而我的 handle_connection 持有了非 Send 的类型。解决方案是确保所有跨线程的类型都实现了 Send trait。

/// ✅ 正确做法:确保所有捕获的类型都是 Send
#[tokio::main]
async fn main() {
    let listener = TcpListener::bind("127.0.0.1:8080").await.unwrap();
    
    // 把全局配置用 Arc 包装,线程安全共享
    let config = std::sync::Arc::new(load_config());
    
    loop {
        let (socket, _) = listener.accept().await.unwrap();
        let config = config.clone();  // 只 clone Arc,不 clone 内部数据
        
        tokio::spawn(async move {      // move 把 config 所有权搬进新 task
            handle_connection(socket, config).await;
        });
    }
}

阶段四:从"写对"到"写优雅" —— trait 和泛型

这个阶段没有前三个阶段那么痛,但更花时间。因为前三个阶段有明确的对错边界(编译过 or 不过),而这个阶段要问的是"这样写好还是那样写好"——没有编译器告诉你答案。

/// 4 月份的代码:到处重复
fn format_user_json(user: &User) -> String { /* ... */ }
fn format_user_toml(user: &User) -> String { /* ... */ }
fn format_user_yaml(user: &User) -> String { /* ... */ }

/// 6 月份的代码:trait 统一抽象
trait Formatter {
    fn format(&self, user: &User) -> String;
}

struct JsonFormatter;
impl Formatter for JsonFormatter {
    fn format(&self, user: &User) -> String {
        serde_json::to_string_pretty(user).unwrap()
    }
}

struct TomlFormatter;
impl Formatter for TomlFormatter {
    fn format(&self, user: &User) -> String {
        toml::to_string(user).unwrap()
    }
}

// 使用时:多态,添加新格式只需增加一个实现
fn serialize_users(users: &[User], formatter: &dyn Formatter) -> Vec<String> {
    users.iter().map(|u| formatter.format(u)).collect()
}

四、的优势和劣势:一个诚实的复盘

真正的劣势

  1. 计算机基础薄弱:不懂操作系统、不懂编译原理、不懂网络协议栈。这些问题在遇到"为什么连接超时"、"为什么内存不够"时会被放大。
  2. 缺少 mentor:没有人告诉你"这个不用学"、"那个先跳过"。我踩了很多坑——比如花了一周学 nom(解析器组合子),最后发现 serde 已经完全满足我的需求。
  3. 调试能力差:不会用 GDB/LLDB,不理解 core dump,遇到段错误就懵了。

被低估的优势

  1. 没有"应该怎么做"的预设:我不会觉得"Rust 应该像 C++ 那样"或者"异步应该像 Go 那样"。我从零开始接受 Rust 的哲学,反而少了很多阻抗。
  2. 知道用户要什么:我以前做 PHP 后端,天天和产品经理、前端打交道。这让我在设计 CLI 工具时天然关注用户体验——错误信息能不能让人看懂?配置文件放哪里最直观?
  3. 敢于用 AI 辅助:科班同学可能觉得用 AI 是"作弊",但我没有这个包袱。每天和 GPT-4 讨论代码设计已经成了我的工作流。

五、总结

这半年的路线如果用一句话总结,就是:

第 1-3 个月,你在学 Rust 的"规矩"。第 4-6 个月,你在用 Rust 的"规矩"解决真实问题。

前半段痛苦但必须经历,后半段享受但需要前半段的积累。没有那 47 行看懂的编译错误,就没有 dayuan 里流畅的 error propagation。没有对着 SendSyncPin 发呆的三天三夜,就写不出稳定的异步服务。

如果你也在转 Rust,我想给你三个建议:

  1. 第一周不要看任何高级内容。只写 main.rs,只写 loop/if/match/struct,写到闭着眼睛都能写对为止。
  2. 第三周开始看所有权,但做好心理准备:你会在接下来的两个月里反复理解同一件事,这是正常的。
  3. 第二个月开始用 Rust 改写你原来语言的代码。实战驱动理解,这比我纯看书快了至少三倍。

最后说一句私心话:转 Rust 最大的挑战不是技术,是在身边的同行都在劝你"别浪费时间"的时候,坚持继续敲下去。


资料说明

本文中的协议、版本、性能、成本和行业趋势应以可核验的一手资料为准。未标注统计口径的比例、时间表和预测仅作工程讨论,不应视为行业事实。可参考 0731 资料来源索引,并在发布前将具体来源贴到对应断言之后。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值