Rust 快速入门与简单实操(前端工程师视角)
专为熟悉 JS/TS 的开发者定制
读者画像:日常写 Vue/React/Node,想快速建立对 Rust 的心智模型,并跑通一个最小 HTTP API。
类比思维:尽量用你熟悉的 npm、TypeScript、Promise 来对照 Rust 的工具链与语言特性。
本文目标:约 1 小时读完核心概念 + 30 分钟跟做一个小服务(无需先啃完《Rust 程序设计语言》)。
一、为什么前端值得扫一眼 Rust?
| 维度 | JavaScript / TypeScript | Rust | 类比 |
|---|---|---|---|
| 执行模型 | 解释型(Node/V8)或 JIT | 编译到原生机器码 | 更接近「发布即静态资源」:一次 cargo build --release,得到单个可执行文件 |
| 类型 | TS 类型可擦除,运行时仍是弱类型 | 类型即契约,编译期强制 | 比 strict 更硬:很多「能跑但不对」在编译阶段直接拦住 |
| 内存 | GC,引用随意传 | 所有权 + 借用检查 | 像「不可变数据流」+「谁负责释放」在类型系统里写死 |
| 并发 | async/await + 单线程事件循环为主 | async(Tokio)+ 真多线程无数据竞争 | 写并发时编译器帮你排雷,代价是学习曲线 |
| 生态角色 | 前后端、脚本、工具全能 | CLI、系统工具、高性能服务、WASM | 和 Node 互补:不是替代写页面,而是替代「极重 CPU / 极低延迟」那一小段 |
适合场景:边缘/网关、高性能 API、CLI(类似 esbuild 那类工具链)、把关键模块编译成 Node 插件或 WASM 给前端用。
二、环境搭建(对照 nvm + npm)
1. 安装 Rust 工具链
官方推荐用 rustup(角色类似 nvm + 指定 Node 版本):
- macOS / Linux:在终端执行官网一键脚本,或
brew install rustup-init && rustup-init。 - Windows:下载
rustup-init.exe,按提示安装;构建原生依赖时可能需要 Visual Studio Build Tools(勾选「使用 C++ 的桌面开发」)。
验证:
rustc --version
cargo --versionrustc 是编译器,cargo 是包管理 + 构建 + 测试工具(≈ npm + 部分 webpack/cli 职责)。
2. 第一个项目(对照 npm init)
cargo new hello-rust --bin
cd hello-rust
cargo runCargo.toml:≈package.json(项目元数据 + 依赖列表)。src/main.rs:≈src/index.ts入口。cargo run:编译并运行(开发期最常用)。cargo build --release:优化编译,产物在target/release/,适合部署。
三、语法速览:用 TS 脑补 Rust
1. 对照表(先混个脸熟)
| TypeScript | Rust | 备注 |
|---|---|---|
let x = 1 | let x = 1 | 默认不可变;可变要写 let mut x = 1 |
const PI = 3.14 | const PI: f64 = 3.14 | 常量;常需显式类型或能推断的上下文 |
function add(a: number, b: number) | fn add(a: i32, b: i32) -> i32 | 参数与返回值类型写在签名里 |
type User = { id: number } | struct User { id: u32 } | 结构体字段默认私有;模块外要用需 pub |
type Status = "ok" | "err" | enum Status { Ok, Err } | enum 更强:可带数据(代数数据类型) |
T | null / T | undefined | Option<T> | Some(x) / None |
try/catch、Promise reject | Result<T, E> | Ok(v) / Err(e);错误常显式向上传 |
interface 多态 | trait | 类似「能力集合」,再 impl Trait for Struct |
2. Hello World
fn main() {
println!("Hello, world!");
}println! 末尾的 ! 表示宏(编译期展开),先当成「增强版 console.log」即可。
3. 变量与可变性
fn main() {
let x = 10; // 默认不可变,类似 const 绑定
// x = 11; // 编译错误
let mut y = 10; // 可变
y += 1;
let z = 5;
let z = z + 1; // 遮蔽(shadowing),仍是不可变绑定,但名字复用
println!("y = {y}, z = {z}");
}前端直觉:默认 push 你写纯函数式风格;需要改字段时显式 mut,减少意外共享可变状态。
4. 字符串:别被两种类型吓到
fn main() {
let s: &'static str = "literal"; // 字符串切片,静态区,不可变
let owned: String = String::from("heap"); // 堆上可增长字符串,类似「自己的 buffer」
let slice: &str = &owned[..]; // 对 String 的借用视图
println!("{s} {owned} {slice}");
}记忆:String ≈ 你可拥有的 String 对象;&str ≈ 只读视图(可能指向字面量或 String 里的一段)。
5. struct 与方法(对照 class,但没有继承)
#[derive(Debug, Clone)]
struct User {
id: u32,
name: String,
}
impl User {
fn new(id: u32, name: impl Into<String>) -> Self {
Self {
id,
name: name.into(),
}
}
fn greet(&self) -> String {
format!("hi, {}", self.name)
}
}
fn main() {
let u = User::new(1, "Ada");
println!("{}", u.greet());
}impl块:≈ 给类型挂方法。&self:≈this只读;&mut self才是可变借用。
四、所有权与借用:前端怎么理解?
这是 Rust 最大的心智负担,但可以用**「谁拥有这块数据、同时几个人在读/写」**来理解。
- 每个值只有一个所有者;所有者离开作用域时,若没有实现
Copy,会自动 drop(不用写 free,也不是 GC)。 - 赋值 / 传参常会发生移动(move):所有权交给对方,旧变量不能再用这个值(类似把对象引用唯一性交给别人)。
- 借用
&T/&mut T:暂时把只读或独占写权限借出去;编译器保证:要么多个读者,要么一个写者(与数据竞争互斥)。
fn takes_ownership(s: String) {
println!("{}", s);
} // s 在这里被 drop
fn main() {
let s = String::from("hello");
takes_ownership(s);
// println!("{}", s); // 错误:s 已被 move 进函数
}fn print_len(s: &String) {
println!("{}", s.len());
}
fn main() {
let s = String::from("hello");
print_len(&s);
println!("still have {s}");
}前端类比:有点像你在 React 里坚持「单向数据流 + 不随便 mutate props」——Rust 在类型系统里把「alias 与 mutation 不能同时作妖」钉死了。习惯之后,写并发会轻松很多。
五、Option 与 Result:告别「undefined 一声不吭」
fn divide(a: f64, b: f64) -> Option<f64> {
if b == 0.0 {
None
} else {
Some(a / b)
}
}
fn main() {
match divide(10.0, 2.0) {
Some(v) => println!("{v}"),
None => println!("no result"),
}
// 简写:unwrap 会在 None 时 panic —— 仅 demo / 你确定安全时用
// let x = divide(1.0, 0.0).unwrap();
}use std::fs::read_to_string;
fn load() -> Result<String, std::io::Error> {
read_to_string("Cargo.toml")
}
fn main() {
match load() {
Ok(s) => println!("first line: {}", s.lines().next().unwrap_or("")),
Err(e) => eprintln!("read error: {e}"),
}
}习惯用法:库代码返回 Result;在 main 或边界上用 match / ? 运算符(「出错就往上返回」)处理。这比到处 try/catch 更结构化。
六、简单实操:用 Axum 起一个最小 REST API
下面示例对标「Express 起一个 JSON API」:内存里存用户列表,提供列表查询、按 id 查询、创建用户。依赖与社区常用的 Axum(Tokio 生态)。
1. 新建项目与依赖
cargo new rust-api-demo --bin
cd rust-api-demo编辑根目录 Cargo.toml:
[package]
name = "rust-api-demo"
version = "0.1.0"
edition = "2021"
[dependencies]
axum = "0.7"
tokio = { version = "1", features = ["full"] }
serde = { version = "1", features = ["derive"] }2. 替换 src/main.rs
use axum::{
extract::{Path, State},
http::StatusCode,
routing::{get, post},
Json, Router,
};
use serde::{Deserialize, Serialize};
use std::sync::{Arc, Mutex};
#[derive(Clone, Serialize)]
struct User {
id: u32,
name: String,
}
#[derive(Deserialize)]
struct CreateUser {
name: String,
}
type AppState = Arc<Mutex<Vec<User>>>;
async fn list_users(State(state): State<AppState>) -> Json<Vec<User>> {
let users = state.lock().unwrap();
Json(users.clone())
}
async fn get_user(
State(state): State<AppState>,
Path(id): Path<u32>,
) -> Result<Json<User>, StatusCode> {
let users = state.lock().unwrap();
users
.iter()
.find(|u| u.id == id)
.cloned()
.map(Json)
.ok_or(StatusCode::NOT_FOUND)
}
async fn create_user(State(state): State<AppState>, Json(body): Json<CreateUser>) -> Json<User> {
let mut users = state.lock().unwrap();
let next_id = users.iter().map(|u| u.id).max().unwrap_or(0) + 1;
let user = User {
id: next_id,
name: body.name,
};
users.push(user.clone());
Json(user)
}
#[tokio::main]
async fn main() {
let state: AppState = Arc::new(Mutex::new(vec![User {
id: 1,
name: "Alice".into(),
}]));
let app = Router::new()
.route("/api/users", get(list_users).post(create_user))
.route("/api/users/:id", get(get_user))
.with_state(state);
let listener = tokio::net::TcpListener::bind("127.0.0.1:8080")
.await
.expect("bind failed");
println!("listening on http://127.0.0.1:8080");
axum::serve(listener, app).await.expect("server error");
}3. 运行与 curl 自测
cargo run另开终端:
# 列表
curl -s http://127.0.0.1:8080/api/users
# 单个
curl -s http://127.0.0.1:8080/api/users/1
# 创建(JSON body)
curl -s -X POST http://127.0.0.1:8080/api/users \
-H "Content-Type: application/json" \
-d "{\"name\":\"Bob\"}"前端侧用 fetch / axios 与调用 Node 服务无异,注意浏览器跨域时需在 Axum 里加 CORS 中间件(进阶话题,此处从略)。
4. 这段代码里的 Rust 梗
Arc<Mutex<T>>:多线程共享可变状态的经典组合;异步 handler 可能被并发调用,所以用锁保护Vec。生产环境会换数据库连接池,但模式类似「共享资源 + 并发控制」。Json<T>:反序列化请求体;失败时 Axum 自动 400。State:依赖注入路由级状态,≈ Express 的req.app.locals或中间件挂载的 store。
七、常用 Cargo 命令(对照 npm)
| npm | cargo |
|---|---|
npm install | cargo build(拉依赖并编译) |
npm install pkg | 在 Cargo.toml 写依赖后 cargo build,或 cargo add axum(需 cargo-edit) |
npm run dev | cargo run(开发默认不极致优化) |
npm test | cargo test |
npm run build | cargo build --release |
八、延伸阅读(按兴趣跳转)
- 官方书:The Rust Programming Language(俗称 TRPL)
- 异步运行时:Tokio 教程
- Web:Axum 文档
- 与 JS 互操作: napi-rs(写 Node 原生扩展)、wasm-pack(编译到 WASM)
小结:把 Cargo 当 npm、把编译错误当严格 linter、把所有权当「数据归属与并发纪律」,你就能较快上手;真要写业务服务,再系统补 异步、错误处理、模块拆分与测试。欢迎把本页当作「冷启动检查清单」,边写边查官方文档即可。