尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

Rust入门怎么走?从所有权到axum和嵌入式开发的全路径

Rust入门怎么走?从所有权到axum和嵌入式开发的全路径 最近总有人问我“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 系 IDEIDEA、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_SERVERhttps://rsproxy.cn export RUSTUP_UPDATE_ROOThttps://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 sparsehttps://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 sparsehttps://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 longesta(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里这个枚举叫ResultT, E。函数可能失败就返回Result调用者必须处理逃不掉。新手最常用的偷懒方法是unwrap()遇到Ok就取出值遇到Err直接 panics 崩溃。demo 代码没问题业务代码这么写就是埋雷。我建议从第一天开始就养成分层处理的习惯use std::fs::File; use std::io::Read; fn main() - Result(), Boxdyn 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直接提前返回错误。配合Boxdyn Error这种“任意错误都行”的写法写业务逻辑的体验跟 try-catch 已经很接近了。上手阶段你可能觉得每次写Ok(())、配Boxdyn Error很啰嗦。但这个啰嗦是有价值的它逼着你把“失败”当成一条显式的路径去处理而不是像某些语言那样异常静默吞掉之后留下一地鸡毛。4. 学完语法别闲着Web 后端和嵌入式两条真香路线4.1 用 axum 从零写一个 JSON 接口服务语法学到能做小工具的水平就该碰真实项目了。Web 后端是 Rust 目前应用最广泛的场景之一框架里我推荐 axum它是 tokio 官方团队维护的生态好、设计现代、社区活跃。对比 actix-webaxum 的抽象更干净用起来也更符合 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: VecTask, } async fn health() - static str { ok } async fn list_tasks(State(state): StateAppState) - JsonVecTask { 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入门 这条路难的从来不是知识点而是你愿不愿意在编译器的“纠正”面前多蹲几轮。蹲过去了后面全是正反馈。
返回列表