Skip to content

Rust 快速入门与简单实操(前端工程师视角)

专为熟悉 JS/TS 的开发者定制

读者画像:日常写 Vue/React/Node,想快速建立对 Rust 的心智模型,并跑通一个最小 HTTP API。
类比思维:尽量用你熟悉的 npm、TypeScript、Promise 来对照 Rust 的工具链与语言特性。
本文目标:约 1 小时读完核心概念 + 30 分钟跟做一个小服务(无需先啃完《Rust 程序设计语言》)。


一、为什么前端值得扫一眼 Rust?

维度JavaScript / TypeScriptRust类比
执行模型解释型(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++ 的桌面开发」)。

验证:

bash
rustc --version
cargo --version

rustc 是编译器,cargo 是包管理 + 构建 + 测试工具(≈ npm + 部分 webpack/cli 职责)。

2. 第一个项目(对照 npm init

bash
cargo new hello-rust --bin
cd hello-rust
cargo run
  • Cargo.tomlpackage.json(项目元数据 + 依赖列表)。
  • src/main.rssrc/index.ts 入口
  • cargo run:编译并运行(开发期最常用)。
  • cargo build --release:优化编译,产物在 target/release/,适合部署。

三、语法速览:用 TS 脑补 Rust

1. 对照表(先混个脸熟)

TypeScriptRust备注
let x = 1let x = 1默认不可变;可变要写 let mut x = 1
const PI = 3.14const 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 | undefinedOption<T>Some(x) / None
try/catch、Promise rejectResult<T, E>Ok(v) / Err(e);错误常显式向上传
interface 多态trait类似「能力集合」,再 impl Trait for Struct

2. Hello World

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

println! 末尾的 ! 表示(编译期展开),先当成「增强版 console.log」即可。

3. 变量与可变性

rust
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. 字符串:别被两种类型吓到

rust
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,但没有继承)

rust
#[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 块:≈ 给类型挂方法
  • &selfthis 只读&mut self 才是可变借用。

四、所有权与借用:前端怎么理解?

这是 Rust 最大的心智负担,但可以用**「谁拥有这块数据、同时几个人在读/写」**来理解。

  1. 每个值只有一个所有者;所有者离开作用域时,若没有实现 Copy,会自动 drop(不用写 free,也不是 GC)。
  2. 赋值 / 传参常会发生移动(move):所有权交给对方,旧变量不能再用这个值(类似把对象引用唯一性交给别人)。
  3. 借用 &T / &mut T:暂时把只读或独占写权限借出去;编译器保证:要么多个读者,要么一个写者(与数据竞争互斥)。
rust
fn takes_ownership(s: String) {
    println!("{}", s);
} // s 在这里被 drop

fn main() {
    let s = String::from("hello");
    takes_ownership(s);
    // println!("{}", s); // 错误:s 已被 move 进函数
}
rust
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 不能同时作妖」钉死了。习惯之后,写并发会轻松很多。


五、OptionResult:告别「undefined 一声不吭」

rust
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();
}
rust
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. 新建项目与依赖

bash
cargo new rust-api-demo --bin
cd rust-api-demo

编辑根目录 Cargo.toml

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

rust
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 自测

bash
cargo run

另开终端:

bash
# 列表
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)

npmcargo
npm installcargo build(拉依赖并编译)
npm install pkgCargo.toml 写依赖后 cargo build,或 cargo add axum(需 cargo-edit
npm run devcargo run(开发默认不极致优化)
npm testcargo test
npm run buildcargo build --release

八、延伸阅读(按兴趣跳转)


小结:把 Cargo 当 npm、把编译错误当严格 linter、把所有权当「数据归属与并发纪律」,你就能较快上手;真要写业务服务,再系统补 异步、错误处理、模块拆分与测试。欢迎把本页当作「冷启动检查清单」,边写边查官方文档即可。