后端开发精选文章10.925 分钟阅读

用 JavaScript 的视角学习 Rust 编程

从 JavaScript 开发者的角度深入学习 Rust 编程语言,通过对比和实例帮助前端开发者快速掌握 Rust 的核心概念。

作者:li-lingfeng发布于 2024年10月10日

用 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 开发者需要重点理解。

所有权规则

  1. Rust 中的每一个值都有一个被称为其所有者(owner)的变量
  2. 值在任一时刻有且只有一个所有者
  3. 当所有者(变量)离开作用域,这个值将被丢弃

与 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 的学习路径:

  1. 类型系统: 从动态类型到静态强类型
  2. 内存管理: 从垃圾回收到所有权系统
  3. 错误处理: 从异常到 Result 类型
  4. 并发: 从单线程事件循环到多线程安全
  5. 工具链: 从 npm/yarn 到 Cargo
  6. 异步编程: 从 Promise 到 Future

学习建议

  1. 循序渐进: 先掌握基本语法和类型系统
  2. 实践为主: 通过小项目练习所有权和借用
  3. 对比学习: 将 Rust 概念与 JavaScript 对应概念关联
  4. 工具熟悉: 熟练使用 Cargo 和 Rust 开发工具
  5. 社区参与: 积极参与 Rust 社区,阅读优秀的开源项目

Rust 虽然学习曲线陡峭,但其提供的内存安全、性能优势和现代化的工具链,使其成为系统编程和高性能应用的理想选择。对于前端开发者来说,掌握 Rust 不仅能够拓展技术栈,还能更好地理解和使用基于 Rust 构建的前端工具。

推荐学习资源

相关文章

前端开发8.775 分钟

TypeScript 高级类型实战指南

深入探索 TypeScript 的高级类型系统,包括条件类型、映射类型、模板字面量类型等,通过实际案例学习如何构建类型安全的应用。

觉得这篇文章有用?

分享给更多朋友,让知识传播得更远 ✨

评论讨论

参与讨论

登录后即可发表评论,与其他读者交流想法

加载评论中...