用 JavaScript 的视角学习 Rust 编程
前言
Rust 是近年来备受关注的系统编程语言,其核心特点包括:
Rust 的三大特性
- 高性能 - Rust 速度惊人且内存利用率极高。由于没有运行时和垃圾回收,它能够胜任对性能要求特别高的服务,可以在嵌入式设备上运行,还能轻松和其他语言集成。
- 可靠性 - Rust 丰富的类型系统和所有权模型保证了内存安全和线程安全,让您在编译期就能够消除各种各样的错误。
- 生产力 - Rust 拥有出色的文档、友好的编译器和清晰的错误提示信息,还集成了一流的工具——包管理器和构建工具,智能地自动补全和类型检验的多编辑器支持,以及自动格式化代码等等。
为什么前端开发者要学习 Rust?
随着前端基建在不断 Rust 化(如 SWC、Turbopack 等工具),以及 Rust 在编译成 WebAssembly 后在浏览器端的广泛应用,现阶段前端开发人员掌握 Rust 知识变得越来越有价值。
本文将基于 JavaScript 知识进行 Rust 对比学习,帮助前端开发者快速上手 Rust。
类型系统对比
基本类型对比
JavaScript 是一种弱类型的解释型语言,而 Rust 是强类型的编译型语言,在类型系统上更接近于 TypeScript。
JavaScript vs Rust 基本类型
JavaScript | Rust | 说明 |
---|---|---|
number | i32 , f64 , u32 等 | Rust 有多种数字类型 |
string | String , &str | Rust 区分拥有所有权的字符串和字符串切片 |
boolean | bool | 基本相同 |
undefined /null | Option<T> | Rust 用 Option 处理可能为空的值 |
Rust 数字类型详解
Rust 的数字类型根据位数、符号位、浮点数分为:
- 整数类型:
i8
,u8
,i16
,u16
,i32
,u32
,i64
,u64
,i128
,u128
,isize
,usize
- 浮点类型:
f32
,f64
- 其他:
char
(单个字符),bool
(布尔值)
复合类型
Rust 还包含元组、数组等原始复合类型:
- 元组: 类似 TypeScript 中的元组概念
- 数组: 与 JavaScript 的 Array 不同,Rust 中的数组长度固定且类型统一
结构体定义对比
TypeScript 方式
type Person = { firstName: string; lastName: string; }; const person: Person = { firstName: "John", lastName: "Doe", };
Rust 方式
struct Person { first_name: String, last_name: String, } let mut person = Person { first_name: String::from("John"), last_name: String::from("Doe"), };
泛型系统
函数泛型对比
TypeScript 泛型函数
function largest<T>(list: T[]): T { let largest = list[0]; for (let item of list) { if (item > largest) { largest = item; } } return largest; } console.log(largest([1, 2, 3, 4, 5])); // 5 console.log(largest(["a", "b", "c"])); // "c"
Rust 泛型函数
fn largest<T: PartialOrd>(list: &[T]) -> &T { let mut largest = &list[0]; for item in list { if item > largest { largest = item; } } largest } fn main() { let numbers = vec![34, 50, 25, 100, 65]; let result = largest(&numbers); println!("The largest number is {}", result); }
结构体泛型
TypeScript
type Point<T> = { x: T; y: T; }; const intPoint: Point<number> = { x: 5, y: 10 }; const floatPoint: Point<number> = { x: 1.0, y: 4.0 };
Rust
struct Point<T> { x: T, y: T, } impl<T> Point<T> { fn x(&self) -> &T { &self.x } } fn main() { let integer = Point { x: 5, y: 10 }; let float = Point { x: 1.0, y: 4.0 }; }
Traits(特质)系统
Traits 类似于其他语言中的接口(interface),定义了某些类型支持的行为的共同功能。
定义和实现 Trait
// 定义一个 trait pub trait Summary { fn summarize(&self) -> String; } // 为结构体实现 trait struct NewsArticle { headline: String, location: String, author: String, content: String, } impl Summary for NewsArticle { fn summarize(&self) -> String { format!("{}, by {} ({})", self.headline, self.author, self.location) } }
与 TypeScript Interface 对比
TypeScript Interface
interface Drawable { draw(): void; } class Circle implements Drawable { draw() { console.log("Drawing a circle"); } }
Rust Trait
trait Drawable { fn draw(&self); } struct Circle; impl Drawable for Circle { fn draw(&self) { println!("Drawing a circle"); } }
所有权系统
这是 Rust 最独特的特性,JavaScript 开发者需要重点理解。
所有权规则
- Rust 中的每一个值都有一个被称为其所有者(owner)的变量
- 值在任一时刻有且只有一个所有者
- 当所有者(变量)离开作用域,这个值将被丢弃
与 JavaScript 的对比
JavaScript(引用传递)
function takeOwnership(obj) { obj.name = "Modified"; return obj; } let myObj = { name: "Original" }; let newObj = takeOwnership(myObj); console.log(myObj.name); // "Modified" - 原对象被修改
Rust(所有权转移)
fn take_ownership(s: String) -> String { println!("{}", s); s // 返回所有权 } fn main() { let s1 = String::from("hello"); let s2 = take_ownership(s1); // s1 的所有权转移给函数 // println!("{}", s1); // 错误!s1 不再有效 println!("{}", s2); // 正确,s2 拥有所有权 }
借用(Borrowing)
fn calculate_length(s: &String) -> usize { // s 是对 String 的引用 s.len() } // 这里,s 离开了作用域。但因为它并不拥有引用值的所有权,所以什么也不会发生 fn main() { let s1 = String::from("hello"); let len = calculate_length(&s1); // 传递引用,不转移所有权 println!("The length of '{}' is {}.", s1, len); // s1 仍然有效 }
生命周期
生命周期是 Rust 独有的概念,确保引用在需要的时间内有效。
生命周期注解
fn longest<'a>(x: &'a str, y: &'a str) -> &'a str { if x.len() > y.len() { x } else { y } } fn main() { let string1 = String::from("abcd"); let string2 = "xyz"; let result = longest(string1.as_str(), string2); println!("The longest string is {}", result); }
结构体中的生命周期
struct ImportantExcerpt<'a> { part: &'a str, } fn main() { let novel = String::from("Call me Ishmael. Some years ago..."); let first_sentence = novel.split('.').next().expect("Could not find a '.'"); let i = ImportantExcerpt { part: first_sentence, }; }
错误处理
JavaScript vs Rust 错误处理
JavaScript(try-catch)
async function fetchData() { try { const response = await fetch('/api/data'); const data = await response.json(); return data; } catch (error) { console.error('Error:', error); throw error; } }
Rust(Result 类型)
use std::fs::File; use std::io::ErrorKind; fn open_file() -> Result<File, std::io::Error> { match File::open("hello.txt") { Ok(file) => Ok(file), Err(error) => match error.kind() { ErrorKind::NotFound => { println!("File not found!"); Err(error) } other_error => Err(error), }, } }
模块系统
JavaScript vs Rust 模块
JavaScript ES6 模块
// math.js export function add(a, b) { return a + b; } export default function multiply(a, b) { return a * b; } // main.js import multiply, { add } from './math.js';
Rust 模块
// lib.rs 或 main.rs mod math { pub fn add(a: i32, b: i32) -> i32 { a + b } pub fn multiply(a: i32, b: i32) -> i32 { a * b } } use math::{add, multiply}; fn main() { println!("2 + 3 = {}", add(2, 3)); println!("2 * 3 = {}", multiply(2, 3)); }
包管理和工具链
JavaScript vs Rust 工具链对比
JavaScript 生态
# 包管理 npm install lodash yarn add lodash pnpm add lodash # 运行和构建 npm run dev npm run build npm test # 项目初始化 npm init npx create-react-app my-app
Rust 生态
# 包管理 cargo add serde cargo remove serde # 运行和构建 cargo run cargo build --release cargo test # 项目初始化 cargo new my-project cargo init
依赖管理对比
package.json (JavaScript)
{ "name": "my-app", "version": "1.0.0", "dependencies": { "lodash": "^4.17.21", "axios": "^1.0.0" }, "devDependencies": { "typescript": "^4.9.0", "@types/node": "^18.0.0" } }
Cargo.toml (Rust)
[package] name = "my-app" version = "0.1.0" edition = "2021" [dependencies] serde = { version = "1.0", features = ["derive"] } tokio = { version = "1.0", features = ["full"] } [dev-dependencies] criterion = "0.4"
异步编程对比
JavaScript Promise/async-await
async function fetchUserData(userId) { try { const response = await fetch(`/api/users/${userId}`); const userData = await response.json(); return userData; } catch (error) { console.error('Failed to fetch user:', error); throw error; } } // 并发执行 const [user1, user2] = await Promise.all([ fetchUserData(1), fetchUserData(2) ]);
Rust async/await
use tokio; use reqwest; async fn fetch_user_data(user_id: u32) -> Result<User, reqwest::Error> { let url = format!("https://api.example.com/users/{}", user_id); let response = reqwest::get(&url).await?; let user: User = response.json().await?; Ok(user) } #[tokio::main] async fn main() -> Result<(), Box<dyn std::error::Error>> { // 并发执行 let (user1, user2) = tokio::try_join!( fetch_user_data(1), fetch_user_data(2) )?; println!("User 1: {:?}", user1); println!("User 2: {:?}", user2); Ok(()) }
总结
从 JavaScript 到 Rust 的学习路径:
- 类型系统: 从动态类型到静态强类型
- 内存管理: 从垃圾回收到所有权系统
- 错误处理: 从异常到 Result 类型
- 并发: 从单线程事件循环到多线程安全
- 工具链: 从 npm/yarn 到 Cargo
- 异步编程: 从 Promise 到 Future
学习建议
- 循序渐进: 先掌握基本语法和类型系统
- 实践为主: 通过小项目练习所有权和借用
- 对比学习: 将 Rust 概念与 JavaScript 对应概念关联
- 工具熟悉: 熟练使用 Cargo 和 Rust 开发工具
- 社区参与: 积极参与 Rust 社区,阅读优秀的开源项目
Rust 虽然学习曲线陡峭,但其提供的内存安全、性能优势和现代化的工具链,使其成为系统编程和高性能应用的理想选择。对于前端开发者来说,掌握 Rust 不仅能够拓展技术栈,还能更好地理解和使用基于 Rust 构建的前端工具。