Rust 入门:前端开发者的视角
今年开始学 Rust,背景是 TypeScript/Node.js。记录学习过程。
为什么学 Rust
| 动机 | 说明 |
|---|---|
| 性能 | Node.js 不擅长 CPU 密集型 |
| 工具链 | 很多新工具用 Rust 写(Turbopack、Rome) |
| WASM | Rust 是 WebAssembly 的首选语言 |
| 成长 | 学习新范式 |
与 JavaScript 的对比
变量绑定
// Rust 默认不可变
let x = 5;
x = 6; // 报错!
// 需要显式声明可变
let mut x = 5;
x = 6; // OK
// JavaScript 默认可变
let x = 5;
x = 6; // OK
内存管理
JavaScript 有垃圾回收,Rust 用所有权系统:
fn main() {
let s1 = String::from("hello");
let s2 = s1; // s1 的所有权转移给 s2
// println!("{}", s1); // 报错!s1 已经失效
println!("{}", s2); // OK
}
这叫”移动语义”,JavaScript 程序员最不习惯的地方。
错误处理
// Result 类型,强制处理错误
fn read_file(path: &str) -> Result<String, io::Error> {
fs::read_to_string(path)
}
fn main() {
match read_file("test.txt") {
Ok(content) => println!("{}", content),
Err(e) => eprintln!("Error: {}", e),
}
}
// JavaScript 用 try-catch
try {
const content = fs.readFileSync('test.txt', 'utf-8');
console.log(content);
} catch (e) {
console.error('Error:', e);
}
Rust 强制你处理错误,虽然麻烦但更安全。
所有权系统
这是 Rust 最核心的概念。
规则
- 每个值有一个所有者
- 值在同一时刻只能有一个所有者
- 所有者离开作用域,值被释放
借用
不转移所有权,只是”借来用用”:
fn main() {
let s = String::from("hello");
let len = calculate_length(&s); // 借用 s
println!("{} has length {}", s, len); // s 还能用
}
fn calculate_length(s: &String) -> usize {
s.len()
} // s 只是借用,不会释放
可变借用
fn main() {
let mut s = String::from("hello");
change(&mut s);
println!("{}", s); // "hello world"
}
fn change(s: &mut String) {
s.push_str(" world");
}
规则:同一时刻,要么有一个可变引用,要么有多个不可变引用,不能同时存在。
生命周期
最让新手头大的概念:
// 编译器需要知道返回的引用来自哪里
fn longest<'a>(x: &'a str, y: &'a str) -> &'a str {
if x.len() > y.len() { x } else { y }
}
fn main() {
let s1 = String::from("long string");
let result;
{
let s2 = String::from("xyz");
result = longest(&s1, &s2); // OK,s1 活得够长
}
// println!("{}", result); // 报错!s2 已经没了
}
编译器在编译时检查引用是否有效,防止悬垂引用。
实战:写一个 CLI 工具
用 Rust 写命令行工具体验很好。
项目结构
cargo new mycli
cd mycli
代码
use clap::Parser;
#[derive(Parser)]
#[command(name = "mycli")]
#[command(about = "A simple CLI tool", long_about = None)]
struct Cli {
#[arg(short, long)]
name: String,
#[arg(short, long, default_value_t = 1)]
count: u8,
}
fn main() {
let cli = Cli::parse();
for _ in 0..cli.count {
println!("Hello {}!", cli.name);
}
}
运行
$ cargo run -- --name World --count 3
Hello World!
Hello World!
Hello World!
$ cargo run -- --help
A simple CLI tool
Usage: mycli [OPTIONS] --name <NAME>
Options:
-n, --name <NAME>
-c, --count <COUNT> [default: 1]
-h, --help Print help
clap 会自动生成帮助信息,非常方便。
与 Node.js 交互
NAPI-RS
用 Rust 写 Node.js 原生模块:
use napi_derive::napi;
#[napi]
fn add(a: i32, b: i32) -> i32 {
a + b
}
编译后在 JavaScript 里调用:
const { add } = require('./index.node');
console.log(add(1, 2)); // 3
WebAssembly
use wasm_bindgen::prelude::*;
#[wasm_bindgen]
pub fn add(a: i32, b: i32) -> i32 {
a + b
}
在浏览器里用:
import { add } from './pkg/my_wasm.js';
console.log(add(1, 2)); // 3
生态对比
| 领域 | JavaScript | Rust |
|---|---|---|
| Web 框架 | Express、Koa | Actix、Axum |
| ORM | Prisma、TypeORM | Diesel、SeaORM |
| CLI | Commander | Clap |
| 测试 | Jest | 内置 |
| 包管理 | npm | cargo |
学习资源
| 资源 | 说明 |
|---|---|
| The Rust Book | 官方教程,必读 |
| Rust by Example | 代码示例学习 |
| Rustlings | 练习题 |
| Crates.io | 包仓库 |
总结
Rust 学习曲线确实陡,尤其是所有权和生命周期。但过了这个坎,就能体会到它的魅力:
- 编译器帮你找 bug
- 没有空指针异常
- 性能接近 C/C++
- 工具链完善
给前端同行的建议:不要想着快速上手,慢慢啃,理解原理。Rust 改变的是思维方式。