浅谈 Rust 类型设计:对比 TS

前言

这几年随着越来越多前端基建项目用 Rust 重写,也预示着 Rust 非常有可能成为前端基建的未来,从社区的趋势看,或者已经是了。参加 2023 年杭州 FEDay, 已知字节除了开源的构建工具 Rspack,连跨端的一些技术栈也开始使用 Rust,当然也这依赖字节内部本身有不错的 Rust 生态。开源社区方面,Rollup 团队也开始着手开始布局 rolldown-rs,即 Rollup 的 Rust 版本。Vercel 团队也是有重量级工具使用 Rust 编写,例如 Turbopack。在结束了框架之争、构建工具之争、JS 语言层面核心特性的稳定化后,Web 前端开发也终于从刀耕火种的时代进入趋于稳定的时代。

随着前端应用日益复杂,开始面临一些新的问题,例如巨石应用下的微前端架构引入的几十上百个子应用的构建问题、代码 Lint 和 Prettier 美化等,导致跑一个完整的 CI 工作流动不动就需要花费十几或者几十分钟。无论使用缓存还是并行去执行一些任务,Webpack 本身和社区也是提供了很多方案,字节 Infra 团队也基于 Webpack 做了各种尝试,详情可以看这篇文章:Bundler 的设计取舍:为什么要开发 Rspack,最终发现都没法很好解决巨石应用的构建性能问题。回归到语言特性,JS 本身设计出来只是一个用于运行在浏览器端的脚本语言,作者可能也没想过有一天,JS 需要背负这么多的使命,从服务端到编译再到跨端,最后我们还是得承认,JS 不是万能的。

于是,社区把目光投向了 Rust ,无论是当初语言设计的定位,还是目前的生态去看,它是一个全新的选择。

语言特性

为了接下来更好地理解 Rust 的类型设计,我们先从语言特性出发。

Rust 有以下关键的语言特性:

  • 内存安全,无 GC 且无需手动管理内存,这就依赖编译时的检查,必须提前规避掉不安全的变量引用问题
  • 高性能 ,编译器基于 LLVM ,这使得 Rust 代码可以通过高度优化的机器代码来实现优异性能
  • 语言级安全性,强大的 cargo 编译器,在编译时提前避免潜在的 runtime 安全问题
  • 高并发性,Rust 在语言层面上支持并发编程,它的并发模型是基于"Actors"模式的,它允许在不同的线程之间安全地共享数据,而无需使用锁或其他同步机制
  • 社区生态,目前因为 Rust 可以应用在多个场景,例如用于开发前端基建工具、数据库、云原生、系统工具、操作系统、区块链等,使得 Rust 社区非常活跃

我个人已经学习 Rust 一段时间,有如下比较喜欢的一些点:

  • cargo 编译器的强大,无论是语法问题、未使用变量、编写文档、单测等方方面面,cargo 编译一条龙服务全部包揽;
  • 内置 Option 枚举 ,没有 JS null 或者其他语言中的空指针等问题,避免 10 亿美元故事的困扰
  • 模式匹配,语言内置的策略模式
  • 特征(Trait)和 Struct,没有 Class,不需要理解 OOP 中复杂的各种概念,反而推荐使用更 FP 的方式编程

介绍完语言特性,下面进入本文的正题。

基础类型(Primitive Type)

数字类型

在 TS 中,数字只有一种类型:number

typescript 复制代码
let num1: number = 255;
let num2: number = 3.142592;

简单粗暴!

而 Rust 从内存使用 考虑,根据可存储的数字范围将数字类型划分为整型、无符号整型、浮点型

rust 复制代码
let num: u8 = 255;
let num1: u64 = 1024;

let f1: f64 = 3.141592;

而且根据不同的使用场景,将整型划分为以下几种:

  • 8 位i8, u8
  • 16 位i16, u16
  • 32 位i32, u32
  • 64 位i64, u64
  • 128 位i128, u128
  • 视计算器架构而定的isize, usize,若电脑 CPU 是 32 位的,则这两个类型是 32 位的

而浮点类型,根据精确度的要求分为:f32f64

有多个数字类型的区分,在做一些数字运算的时候相对麻烦,为了方便,有时候不得不依赖 as做类型转换,但是一定要确保类型兼容,转换是符合预期的。例如实现一个将浮点数小数部分也转换为整数的方法,比较简单的做法:

typescript 复制代码
fn f64_to_int (value: f64, digits: u32) -> i64 {
  let base: i32 = 10;
  (value * base.pow(digits) as f64).round() as i64
}

assert_eq!(f64_to_int(12.12, 2), 1212);

// 预期外的转换
(300_i32 as i8) // get 44

布尔类型

布尔类型在任何编程语言应该都不太意外的都一样,只有两个值,在 TS 中:

typescript 复制代码
let isUsed: boolean = true;
let isNotUsed: boolean = false;

Rust 中:

rust 复制代码
let is_used: bool = true;
let is_not_used: bool = false;

布尔类型大多时候用于 if控制语句,因为语言实现问题,在 TS 中,有比较多的隐式类型转换(准确说是编译后的 JS),因此下面这种使用方式是可以的:

typescript 复制代码
// TS 编译通过
if (2) {
  console.log('hello');
}

在 Rust 中,在编译时就报错了:

rust 复制代码
if 2 {
  print!("hello");
}

// error[E0308]: mismatched types
// expected `bool`, found integer

一切为了内存安全,不应该有任何隐式转换。

字符类型

请注意这里是字符类型 ,并不是字符串类型,在 TS 中,只有一个字符串类型,没有字符类型的说法。

typescript 复制代码
let str: string = 'abc';

str = '中';
str = '😻';

在 Rust 中,字符类型字符串类型 是两种不同的类型,而且字符串类型是一种复合类型(Compound Type) ,不属于基础类型。下一章节,我们再详细介绍。

Rust 的字符不仅仅是 ASCII 编码,所有的 Unicode 值都可以作为 Rust 字符:

rust 复制代码
let mut c = 'a';
c = '国';

c = '😻';

注意一个细节,Rust 中的字符类型是用单引号 ' 包裹;在 TS 中,这是没有任何区别的,从习惯上,我个人使用单引号更多。

单元类型

考虑以下场景,如果我想定义一个函数的类型,但是这个函数不要求返回任何数据,这在实际场景中非常常见。例如打印日志或者事件回调函数,在 TS 中,可以通过以下方式定义函数的类型:

typescript 复制代码
type ClickHandler = (e: MouseEvent<HTMLButtonElement>) => void;

我们称 void为 TS 中的单元类型

在 Rust 中,使用 ()表示单元类型 ,Rust 中的 main函数就是典型的不返回任何数据的函数:

rust 复制代码
fn main () {
  print!("hello");        
}

但是在 TS 中,虽然类型设计上不要求返回任何类型,就算你返回一个数据也是可以的:

typescript 复制代码
type ClickHandler = (e: MouseEvent<HTMLButtonElement>) => void;

// TS 编译通过
const onClick: ClickHandler = (e) => {
  console.log(e.target);
  return e.target;
}

在 Rust 中,则不能这样做:

rust 复制代码
struct A {
  value: String,
}

trait PrintSome {
 fn print(value: String) -> ();        
}

impl PrintSome for A {
  fn print(value: String) {
    print!("{:?}", value);
    value
  }    
}

// Error: mismatched types 
// expected `()`, found `String`

合理,一切为了内存安全,你返回数据意味着 Rust 在编译时就要做更多的编译时检查,例如借用检查,生命周期标注,来避免悬垂引用。

总结

在 Rust 中,就只有以上 4 种基础类型,限于篇幅,下一篇文章我会继续聊 Rust 中的复合类型。

在 TS 中,基础类型还包括 undefinednullsymbolbigInt等。

语言设计上,Rust 的语言设计,就规避了 undefinednull的引入,而是通过枚举 Option 解决空值问题。而 symbol是 TS 中因为语言问题,引入的一种特殊类型,而 bigInt在 Rust 中众多的数字类型早已覆盖掉。

内存安全一词在文中反复出现,对于无 GC ,无须手动管理内存的 Rust,编译时就要去保证内存安全,因此这也会体现在语言的类型设计和一些 Rust 语言语法机制上,例如借用检查、生命周期标注等。

Reference

相关推荐
数据智能老司机35 分钟前
精通 Python 设计模式——并发与异步模式
python·设计模式·编程语言
Moonbit18 小时前
用MoonBit开发一个C编译器
后端·编程语言·编译器
烛阴1 天前
【TS 设计模式完全指南】懒加载、缓存与权限控制:代理模式在 TypeScript 中的三大妙用
javascript·设计模式·typescript
该用户已不存在2 天前
Mojo vs Python vs Rust: 2025年搞AI,该学哪个?
后端·python·rust
Moonbit2 天前
MoonBit 正式加入 WebAssembly Component Model 官方文档 !
前端·后端·编程语言
奔跑的蜗牛ing2 天前
Vue3 + Element Plus 输入框省略号插件:零侵入式全局解决方案
vue.js·typescript·前端工程化
大卫小东(Sheldon)2 天前
写了一个BBP算法的实现库,欢迎讨论
数学·rust
CoovallyAIHub2 天前
微软发布 Visual Studio 2026 Insider:AI深度集成,性能大提升,让开发效率倍增(附下载地址)
后端·编程语言·visual studio
echoarts2 天前
Rayon Rust中的数据并行库入门教程
开发语言·其他·算法·rust