☰
Rust入门怎么走?从所有权到axum和嵌入式开发的全路径
2026/9/30 4:28:42 网站建设 项目流程

最近总有人问我:“rust入门 到底该怎么走?我看了一晚上语法,第二天全忘了,连借用和生命周期都分不清。”这问题我特别理解,因为 Rust 和 Java、Python 那类语言不一样,它不是看两篇教程就能上手写业务的,最大的拦路虎是所有权这套完全不同的心智模型。这篇博文我想把自己走通的路径完整梳理出来,从环境搭建、镜像源配置、VSCode 开发环境,到所有权语法、axum Web 开发,再到 CH32 嵌入式开发,最后附上一份避坑清单。适合三类人:写过 C/C++ 想换安全方案的老手、学过 Python/Java 想挑战系统级开发的朋友、以及正在学校准备毕设又不想只做 CRUD 的同学。

1. Rust到底值不值得入,先把账算清楚

1.1 它能解决什么:C/C++ 的痛,Rust 的解药

很多人第一次听到 Rust,都是冲着一个口号来的:内存安全,无需垃圾回收。这话放在十年前可能没人信,一个没有 GC 的语言怎么可能同时做到安全和高效?但 Rust 确实做到了,靠的是编译器手里那张“所有权”检查清单。

C/C++ 的痛点,写过的人都懂:野指针、悬垂引用、use-after-free、数据竞争,这类 bug 一旦上线,轻则偶发崩溃,重则安全漏洞被人远程利用。传统排查手段无非是 valgrind、ASan、加日志反复复现,运气不好折腾一星期。Rust 的思路是把这些问题的检查提前到编译期,让编译器在代码跑起来之前就告诉你“这里不能这么写”。

我用一个生活化类比给你解释所有权。想象你家有一个玩具,规则是同一时刻只能有一个主人。你想把玩具借给朋友玩一会儿,这不算转移所有权,只能叫“借用”;你要是直接把玩具送给朋友,那你就不能再玩了,除非朋友愿意还给你。Rust 里的String、Vec这类堆上数据,默认就是“送出去就没了”,而借用检查器就是那个盯着你不许犯规的裁判。

所以 rust入门 阶段最值得花时间理解的,不是语法糖,不是宏,而是这一套“主人与借用”的规则。它才是 Rust 区别于所有主流语言的底层逻辑,也是你后面写 axum 后端、写嵌入式代码时绕不开的地基。

1.2 行业信号:为什么连 IDE 都开始用 Rust 重写

如果只讲语言特性,说服力不够,我再补几个风向标。Linux 内核从 6.1 开始正式支持 Rust 模块,微软和 AWS 都在内部项目里引入 Rust,字节跳动的部分高性能组件也用它重写。更贴近日常的是,你每天都在用的 JetBrains 系 IDE(IDEA、PyCharm 等),内部已经有多个底层组件在用 Rust 开发,比如新的终端模拟器、文件系统监视器这类对性能敏感又容易出并发问题的模块。

这并不意味着明天 IDEA 就会整个用 Rust 重写,那件事复杂度太高、周期太长。但它释放了一个明确信号:凡是过去需要 C/C++ 才能扛住性能的场景,现在都在认真评估 Rust 作为替代品。Rust 的江湖地位不是靠营销吹起来的,是这类“把又老又危险的 C/C++ 代码替换掉”的工程慢慢攒出来的。

对入门者来说,这意味着你投入的时间有复利。你不需要成为编译器专家,只要能写稳业务代码、能接住复杂并发场景,就已经比只会写胶水代码的简历有竞争力得多。但我也要泼一盆冷水:如果你只想两个月内速成然后去找一份“Rust 工程师”的工作,现实会很骨感。市场上大量岗位其实是要你有 Rust 基础、再结合具体业务,比如做 Web 服务、做嵌入式、做跨平台 SDK。所以别抱着“学完就能进大厂”的心态,把它当成一项长期资产更合适。

2. 环境搭建:从零跑通第一个 Rust 程序

2.1 安装 rustup 和 cargo:先解决下载慢的烦恼

Rust 官方推荐用rustup安装工具链,它类似一个“版本管理器”,能让你随时切换 stable、beta、nightly 版本。装好之后你会获得三个关键命令:rustc(编译器)、cargo(包管理和构建工具)、rustup(工具链管理)。

Windows 用户直接去 rustup.rs 下载rustup-init.exe,一路默认就好。macOS 或 Linux 用户在终端执行:

curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh

安装脚本会问你怎么配置,选默认的 1 就行。装完以后让cargo命令立即生效,要么重开终端,要么手动执行source "$HOME/.cargo/env"。

这里我要特别提醒一句:如果你在国内网络环境下,安装阶段就能把人劝退。默认的下载源在国外,rustup拉取工具链分分钟超时失败。正确姿势是先设置两个环境变量,指向国内镜像站,再跑安装命令:

export RUSTUP_DIST_SERVER=https://rsproxy.cn export RUSTUP_UPDATE_ROOT=https://rsproxy.cn/rustup

用 rsproxy 或者中科大镜像都行,设置完以后工具链下载速度会快很多。注意这两个环境变量只影响rustup本身的下载,不影响依赖包下载,依赖包的事咱们下一节单独说。

装完后验证一下:cargo --version和rustc --version如果都有输出,说明工具链正常,可以继续了。

2.2 更换 crates 镜像源:让 cargo 不再卡成乌龟

cargo负责拉取你项目里用到的第三方依赖,这些依赖统一存放在 crates.io 仓库。问题在于默认索引更新慢,尤其是你刚建完项目、cargo build拉一堆依赖时,可能卡在 “Updating crates.io index” 那一步半天不动。

解决办法是给 cargo 配置国内镜像源。在用户目录下创建一个配置文件,路径是~/.cargo/config.toml,写入以下内容:

[source.crates-io] replace-with = 'rsproxy-sparse' [source.rsproxy-sparse] registry = "sparse+https://rsproxy.cn/index/"

写完后保存,再执行任何cargo build或cargo add操作,都会走 rsproxy 的 sparse 索引。这里的关键词是sparse+,这是 cargo 1.68 开始支持的新索引协议,不像老式 git 索引那样要把整个索引仓库克隆下来,而是按需访问,速度体验提升非常明显。

如果你偏好中科大镜像,可以换成:

[source.crates-io] replace-with = 'ustc-sparse' [source.ustc-sparse] registry = "sparse+https://mirrors.ustc.edu.cn/crates.io-index/"

两个都试过以后我个人的感受是:rsproxy 的实时性更好,拉新 crate 版本更快,中科大胜在稳定。你选一个就行,不用两个都写上。

改完镜像源你可以顺手建个测试项目验证一下:

cargo new hello_rust cd hello_rust cargo run

如果终端打印出 “Hello, world!”,恭喜,你的第一块 Rust 拼图已经就位。

2.3 VSCode 插件与项目初始化:配好一个顺手的 IDE

编辑器我用的是 VSCode,免费、插件生态成熟,rust入门 阶段完全够用。装好这几个插件,体验逼近 IDEA 级别的辅助:

  • rust-analyzer:核心中的核心。提供代码补全、跳转定义、类型推断展示、错误提示。没有它,写 Rust 就像闭着眼踢球。
  • crates:在Cargo.toml里显示依赖当前最新版本,顺手帮你更新版本号。
  • Even Better TOML:高亮和格式化Cargo.toml,看着舒服,改起来也方便。
  • CodeLLDB:断点调试 Rust 代码,排查逻辑问题时有用。

插件装完以后,打开刚才生成的hello_rust项目,给main.rs加几行代码试试补全和报错提示。你可能注意到 rust-analyzer 启动时会在左下角转圈,等它初始化完,代码旁边就会出现绿色波浪线之类的提示。

用 VSCode 有几个小习惯建议养成:看到错误别急着在终端重新编译,先看编辑器里的诊断信息,通常已经给了修复建议;保存时开启rustfmt格式化,代码风格统一,后面读起来不难受。

3. Rust 入门核心语法:所有权才是真正的分水岭

3.1 用一道必现报错理解 move 与 clone

很多教程把所有权放在第一章讲,但讲得太抽象,新手容易懵。我换个方式,直接给你一段必现报错的代码,让你亲眼看看“move”是怎么发生的。

fn main() { let s1 = String::from("hello"); let s2 = s1; // s1 的所有权被 move 给了 s2 println!("{}", s1); // 这里编译报错 E0382 }

编译结果会告诉你value borrowed here after move,也就是说s1已经被“搬走”了,你不能再碰它。为什么?因为String内部在堆上管理着一块内存,如果允许s1和s2同时存在,那么它们结束时会一起释放这块内存,造成 double free。Rust 的选择是:默认转移所有权,不搞浅拷贝。

想要让s1继续可用,有两条路。第一条是显式克隆底层数据:

let s2 = s1.clone();

这相当于把堆上的数据完整复制一份,s1和s2各管各的内存,互不干扰。代价是性能开销,字符串短还好,大对象频繁 clone 就肉疼了。

第二条路是借用,也就是让s2暂时“看一看”s1的数据而不拥有它:

let s2 = &s1; println!("{}", s1); // 没问题

借用不转移所有权,用完就还。println!里的字符串参数本质也是借用,所以只要你还持有s1的所有权,想怎么打印都行。

这里有个容易踩的坑:基础类型比如i32、f64、bool默认是 Copy 语义,赋值相当于把值原样拷贝一份,不存在 move 一说。所以let a = 5; let b = a; println!("{}", a);一点问题没有。新手一看“整数可以,字符串不可以”,就容易混乱。记住一句话:栈上的小数据自动拷贝,堆上数据默认搬家。

3.2 借用和生命周期:为什么编译器老是打断你

理解了 move,下一个问题是可变借用。Rust 的借用规则比“只能看不许拿”更严格:

  • 可以有多个不可变借用,大家只读数据不修改;
  • 同一时刻只能有一个可变借用;
  • 不可变借用和可变借用不能同时存在。

这三条规则的核心目的是防止 data race。数据竞争是什么?就是两个线程同时读写同一块内存,最后结果不可预期。在 C 语言里这是运行时问题,在 Rust 里是编译错误。

看这个例子:

fn main() { let mut v = vec![1, 2, 3]; let first = &v[0]; // 不可变借用 v.push(4); // 想修改 v,编译器报错 println!("{}", first); }

逻辑上你可能会想:我只是加一个元素,跟读first有什么关系?但push可能导致Vec扩容、重新分配内存,原来那个first引用指向的内存可能就失效了。Rust 编译器宁可你不舒服,也不让这种潜在风险过编译。

至于生命周期,它是 Rust 里最劝退的概念之一,但我建议你换个角度看:生命周期标注不是给编译器找茬,而是教编译器怎么安全放行。比如函数返回引用时,编译器需要知道返回的生命周期和入参的生命周期之间的关系:

fn longest(x: &str, y: &str) -> &str { // 编译报错:缺少生命周期标注 if x.len() > y.len() { x } else { y } }

这里问题大了:返回的可能是x也可能是y,编译器不知道该按谁的寿命来算。修复方式是告诉它:“返回值的生命周期跟两个入参中较短的那个一致。”

fn longest<'a>(x: &'a str, y: &'a str) -> &'a str { if x.len() > y.len() { x } else { y } }

你不用急着把生命周期吃透,rust入门 阶段只需要理解:引用必须活得比它指向的数据短。编译器多数时候会帮你省略标注,只有遇到这种“返回哪个不确定”的情况才需要你出手。

3.3 用 Result 和 ? 把错误处理变成习惯

Rust 没有 try-catch 异常机制,它选择用类型表达错误:正常结果包在Ok里,异常情况包在Err里,这个枚举叫Result<T, E>。函数可能失败就返回Result,调用者必须处理,逃不掉。

新手最常用的偷懒方法是unwrap():遇到Ok就取出值,遇到Err直接 panics 崩溃。demo 代码没问题,业务代码这么写就是埋雷。我建议从第一天开始就养成分层处理的习惯:

use std::fs::File; use std::io::Read; fn main() -> Result<(), Box<dyn std::error::Error>> { let mut f = File::open("config.txt")?; let mut content = String::new(); f.read_to_string(&mut content)?; println!("{}", content); Ok(()) }

这里的?运算符是 Rust 的错误处理利器:如果结果是Ok,直接把里面的值取出来继续往下走;如果结果是Err,直接提前返回错误。配合Box<dyn Error>这种“任意错误都行”的写法,写业务逻辑的体验跟 try-catch 已经很接近了。

上手阶段你可能觉得每次写Ok(())、配Box<dyn Error>很啰嗦。但这个啰嗦是有价值的:它逼着你把“失败”当成一条显式的路径去处理,而不是像某些语言那样,异常静默吞掉之后留下一地鸡毛。

4. 学完语法别闲着:Web 后端和嵌入式两条真香路线

4.1 用 axum 从零写一个 JSON 接口服务

语法学到能做小工具的水平,就该碰真实项目了。Web 后端是 Rust 目前应用最广泛的场景之一,框架里我推荐 axum,它是 tokio 官方团队维护的,生态好、设计现代、社区活跃。对比 actix-web,axum 的抽象更干净,用起来也更符合 Rust 的习惯。

先建项目:

cargo new rust_web && cd rust_web

然后改Cargo.toml:

[dependencies] tokio = { version = "1", features = ["full"] } axum = "0.7" serde = { version = "1", features = ["derive"] } serde_json = "1"

在main.rs里写一个最简单的服务,包含一个健康检查接口和一个返回 JSON 的任务列表接口:

use axum::{extract::State, routing::get, Json, Router}; use serde::Serialize; #[derive(Clone, Serialize)] struct Task { id: u32, title: String, } #[derive(Clone)] struct AppState { tasks: Vec<Task>, } async fn health() -> &'static str { "ok" } async fn list_tasks(State(state): State<AppState>) -> Json<Vec<Task>> { Json(state.tasks) } #[tokio::main] async fn main() { let state = AppState { tasks: vec![Task { id: 1, title: "learn rust".into(), }], }; let app = Router::new() .route("/health", get(health)) .route("/tasks", get(list_tasks)) .with_state(state); let listener = tokio::net::TcpListener::bind("127.0.0.1:3000") .await .unwrap(); axum::serve(listener, app).await.unwrap(); }

跑cargo run,浏览器访问http://127.0.0.1:3000/health会看到ok,访问/tasks会看到 JSON 数组。这就是一个最简单的 axum 服务。

你可能注意到 handler 的写法跟传统语言不太一样:async fn、State(state)这种参数叫提取器,axum 根据参数类型自动从请求里取值。新手不用钻牛角尖问“它怎么知道要传 State”,先记住这是框架约定,等你能写三个接口以上,自然会理解。

起步阶段别碰的东西我也列一下:不要一上来就研究tower中间件,不要碰async_trait,不要尝试手写Future。先把路由、状态、JSON 序列化跑顺,后面的进阶水到渠成。

4.2 在 CH32 单片机上用 Rust 体验嵌入式开发

如果你希望 Rust 不只停留在服务器端,嵌入式是一条很爽的路线。尤其 CH32 系列,比如 CH32V003、CH32V307,都是基于 RISC-V 内核的高性价比单片机。它们不像 STM32 那样官方 Rust 支持已经非常成熟,但社区里有对应的 Rust 支持工程(比如 ch32-rs 系列项目),照样能点灯、能跑外设。

嵌入式 Rust 的典型流程是这样的。先给 rustup 添加对应 RISC-V 目标:

rustup target add riscv32imac-unknown-none-elf

目标名取决于芯片内核。CH32V307 这类带 IMAC 扩展的就用上面这个,CH32V003 这种精简内核又是另一个 target。所以拿到芯片先查 datasheet,确认 RISC-V 扩展组合。

然后你还需要为裸机项目准备:链接脚本memory.x或link.x,里面描述 flash 和 RAM 的起始地址与大小;.cargo/config.toml里配置runner和target,把编译产物交给烧录工具处理。烧录常用工具是 OpenOCD 或者 probe-rs 这类,再配上 WCH-Link 调试器,就能反复刷固件。

点灯代码大致长这样:

// 概念示例,具体 API 以你选的 HAL crate 文档为准 #![no_std] #![no_main] use ch32_hal::gpio::GpioExt; let mut led = gpio.pc13.into_push_pull_output(); loop { led.set_high(); delay_ms(500); led.set_low(); delay_ms(500); }

嵌入式 Rust 的门槛确实比 Web 高,因为你要同时理解交叉编译、链接脚本、寄存器操作、中断处理这些底层概念。我建议想走这条路的同学先满足两个前置条件:一是能用 Rust 写明白所有权和Result,二是手上真有一块开发板可以折腾。纯看教程不烧板,永远停留在“好像懂了”的阶段。

4.3 给自己定一条学习路线:先工具后框架再底层

很多教程喜欢堆路线图,给你列几十本书、几百个视频,结果就是收藏夹吃灰。我更推荐按项目反推学习内容:

  • 第一个项目:写一个命令行文件批量重命名工具,练习所有权、Result、std::fs操作;
  • 第二个项目:用 axum 写一个待办事项 REST API,练习状态管理、JSON 序列化、路由拆分;
  • 第三个项目:挑一个有挑战的,比如解析某种配置文件、写一个简单爬虫,练习serde和异步流。

做这些项目的过程中,哪里卡住了再回头查基础概念,效率高得多。至于嵌入式,可以等 Web 项目跑顺之后再作为“第二曲线”探索,两条路互相之间也有正向迁移。

5. 避坑指南:那些我没写进文档里的经验

5.1 新手最容易踩的五个坑及对策

我在带朋友入门和看社区提问的过程中,总结了五个高频问题,用表格列出来方便你自查:

坑典型表现对策
死磕所有权概念看了三天书,代码一行没写打开 Rustlings 做练习,错题本身就是老师
遇借用冲突就 clone代码能跑但性能极差停手分析:是否需要重构作用域,能否用引用
版本地狱axum 0.6 的例子在 0.7 上编译不过锁定版本,读对应版本文档,别追最新
不敢读编译错误看到一屏报错直接懵只看第一条错误,再找help:提示,逐步解决
打开标准库源码就焦虑想去读 HashMap 实现,结果看不懂标准库不需要通读,用cargo doc查接口即可

这里我想重点展开第一条。Rustlings 是官方出的练习仓库,它把每个知识点都拆成了十几行的小练习,你改代码让它通过编译就算过关。这种“练习—报错—修复”的循环非常契合 Rust 的学习特点,因为你每被编译器教育一次,对所有权和借用的理解就深一层。刷完 Rustlings 再去看《The Rust Programming Language》这本书,你会觉得轻松很多。

5.2 我的实操习惯:让 Rust 学习少走弯路

最后分享几个我实际用下来的个人习惯,不一定适合所有人,但至少能让你少踩几次坑。

第一,先追求能编译,再用 clippy 优化。我写代码是“豆腐块”工程:先把功能跑通,哪怕用了unwrap也无所谓;然后执行cargo clippy把警告清一遍,再回头用?替换unwrap。这样能把“功能正确”和“风格正确”两件事分开,心理负担小很多。

第二,遇到不懂的宏先跳过。axum 的#[tokio::main]、serde 的#[derive(Serialize)],这类宏改变了代码的结构,你硬着头皮去读宏展开只会挫败。先接受“它能用”,等基础扎实了再回来看宏的内部机制。

第三,给学习加一个“即时反馈”机制。我习惯于每周写一个小工具并提交到自己的仓库里,不在乎工具本身有没有用,在乎的是“完成并发布”这个闭环。你写的第一个程序哪怕是实现一个 Fibonacci,第二步加上缓存,第三步加上多线程计算,每一步都能让你实实在在触摸到 Rust 的特性。

第四,不敢问的时候,先看错误信息的help:字段。我发现很多人遇到编译错误第一反应是复制粘贴到搜索引擎,其实 Rust 编译器的错误提示在主流语言里是做得最好的之一,它经常会直接告诉你“试试把这里改成&调用”。多听编译器的话,比瞎猜快一百倍。

第五,别只在自己舒适区里打转。如果你 Web 写顺了,就去试试嵌入式;如果你一直在做上层业务,就去读一点标准库源码。不同领域的实践会倒逼你重新审视自己以前习以为常的代码风格。Rust 这门语言最有趣的地方,就是它会把很多你在其他语言里没想过的“为什么”推到台面上来。我刚开始写的时候也觉得编译器烦人,总觉得它管太多;等我真正用它写完几个项目,再回头看以前写的 C++ 代码,背脊发凉的次数一次比一次多。rust入门 这条路,难的从来不是知识点,而是你愿不愿意在编译器的“纠正”面前多蹲几轮。蹲过去了,后面全是正反馈。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询