以Typescript程序员视角对比Go与Rust

就在昨天,微软发布了一则重磅消息,让编程界为之震惊:

  • 在代号为“Corsa”的项目中,TypeScript 的编译器和工具链正在从 JavaScript 移植到 Go。

目标是什么?惊人的 10 倍性能提升。早期的基准测试已经显示,Visual Studio Code 的 150 万行 TypeScript 编译时间仅为 7.5 秒,低于缓慢的 77.8 秒。

特别有趣的是,微软明确选择 Go 而不是 Rust 来完成这个关键项目。他们的工程团队将 Go 与 TypeScript 的结构兼容性和更快的开发时间表作为决定的关键因素,目标是在 2025 年底前完成移植。

Typescript 与 Go 代码的比较显示出相似性
这一高调的认可凸显了业界日益增长的认可:虽然 Rust 在某些领域表现出色,但 Go 务实的设计选择往往会带来更高效、更易维护、性能更佳的实际系统。

Go 和 Rust 大约在同一时间出现(Go 于 2009 年出现,Rust 于 2010 年出现),作为 C 和 C++ 的现代替代品。虽然这两种语言都在编程生态系统中找到了各自的位置,但它们在语言设计和开发人员体验方面代表了根本不同的理念。

随着我们进入 2025 年,这两种语言都将继续发展,但某些核心设计决策仍然存在,这些决策凸显了 Go 在 Rust 更学术的方法产生摩擦的领域中的务实优势。让我们探索一下 Go 做对了什么而 Rust 做错了什么。

1. 简单且学习曲线
编程语言存在于简单性和表现力之间。

Go 刻意选择了简单。

假设有一位新开发人员加入团队。在 Go 中,他们可以在几天或几周内开始工作。整个语言规范非常简洁,一个下午就可以读完。

Dropbox 的一位高级开发人员曾经告诉我:“我们从 Python 迁移到 Go,因为新员工可以在第一周内为我们的代码库做出贡献。而使用 Rust,上手时间接近一个月。”

微软的 TypeScript 团队对 Corsa 项目也做了类似的考量。他们选择 Go,认为与选择 Rust 相比,他们可以更快地完成移植,并更快地让开发人员上手。

这是一个用 Go 编写的简单 Web 服务器:

package main 

import
"fmt" "net/http" ) 

func  main () { 
http.HandleFunc( "/" , func (w http.ResponseWriter, r *http.Request) { 
fmt.Fprintf(w, "hello world." ) // 将其发送到浏览器 }) 
http.ListenAndServe( ":8080" , nil ) // 在端口 8080 上运行服务器
}

Rust 中的等效方法需要理解生命周期、特征和更复杂的错误处理:

use actix_web::{web, App, HttpServer, Responder}; 

async  fn  hello () ->  impl  Responder { 
"hello world." // 仅返回此文本

#[actix_web::main] 
async  fn  main () -> std::io:: Result <()> { 
HttpServer:: new (|| { 
App:: new (). route ( "/" , web:: get (). to (hello)) 
}) 
. bind ( "127.0.0.1:8080" )? 
. run () 
. await // 在此处启动服务器
}

Go 的方法不只是减少代码行数,还降低了认知负荷。几乎所有程序员都能理解 Go 代码,而 Rust 版本则需要理解 Rust 的特定范式。

2.实用并发模型
并发对于现代应用程序至关重要,但使用线程和锁的传统方法容易出错。

假设有一个服务需要同时从多个 API 获取数据。Go 的 goroutine 和通道提供了一种简单、实用的并发方法。

在 Uber,工程师们报告说,Go 的并发模型允许他们使用相对简单的代码每秒处理数百万个地理位置更新。

对于微软的 TypeScript 编译器端口来说,这非常重要。编译器本质上是可并行化的工作负载,而 Go 的并发模型使并行化解析、类型检查和代码生成变得非常简单。

func  fetchData (urls [] string ) [] string {
results := make ([] string , len (urls))
var wg sync.WaitGroup
for i, url := range urls {
wg.Add( 1 )
go  func (i int , url string ) {
defer wg.Done()
resp, err := http.Get(url) // 获取 url 内容 if err != nil {
results[i] = "error: " + err.Error()
return
}
defer resp.Body.Close()
body, _ := io.ReadAll(resp.Body)
results[i] = string (body) // 将结果存储在这里 }(i, url)
}
wg.Wait()
return results
}

Rust 中的等效功能需要理解 async/await、futures 以及处理生命周期问题:

async  fn  fetch_data (urls: &[ String ]) ->  Vec < String > { 
let  fetches = urls. iter (). map (|url| { 
async  move { 
match reqwest:: get (url). await { 
Ok (resp) => match resp. text (). await { 
Ok (txt) => txt, // 在此处获取文本 Err (e) => "error: " . to_string () + &e. to_string (), 
}, 
Err (e) => "error: " . to_string () + &e. to_string (), 


}); 
futures::future:: join_all (fetches). await // 等待全部完成
}

对于使用同步编程模型的开发人员来说,Go 的方法更直观。goroutine 模型感觉像是顺序编程的自然延伸,而 Rust 的异步模型则带来了相当大的复杂性。

3. 编译速度快⚡
编译速度直接影响开发人员的生产力和迭代周期。

在大型代码库中,编译速度慢会严重影响开发速度。Go 从一开始就设计为快速编译。

Cloudflare 的一个团队报告说,从 C++ 切换到 Go 将他们的构建时间从 45 分钟缩短到 1 分钟以内,大大改善了他们的开发周期。

微软的 TypeScript 决策完美地强调了这一点。他们通过 Project Corsa 实现的 10 倍性能提升主要与编译速度有关。当你的编译器需要一分钟以上的时间来处理大型代码库时,开发人员的生产力会受到极大影响。

虽然我无法在代码示例中显示编译速度,但这是一个真实世界的指标:

对于中型项目(约100,000行代码):

  • Go:通常编译时间为 1-3 秒

  • Rust:通常需要 30 多秒才能完成干净的构建

在需要频繁构建的 CI/CD 管道中,这种差异变得更加明显。

Go 团队在设计决策中优先考虑了编译速度:

  • 简单类型系统

  • 直到最近才有泛型(即使到现在,也谨慎实施)

  • 包级依赖分析

  • 无需复杂的模板元编程

Rust 优先考虑运行时性能和安全保障,这导致了更复杂的编译器,并且需要更长的时间来分析代码。

4. 垃圾收集与所有权模型
内存管理是系统编程的一个关键方面。Go 选择了自动垃圾收集,而 Rust 选择了其所有权系统。

对于许多服务器应用程序来说,现代垃圾收集器的可预测延迟是完全可以接受的,而手动管理内存的认知开销则不能接受。

Discord 工程师曾撰文描述 Go 的垃圾收集器如何让他们专注于业务逻辑而不是内存管理,从而加快功能开发速度。

对于微软的 TypeScript 编译器来说,垃圾收集器是完美的选择。编译器在解析和类型检查期间会创建许多临时数据结构,而自动内存管理可以大大简化这一过程。

考虑一个处理项目列表的函数:

func  processItems (items []Item) []Result {
results := make ([]Result, 0 , len (items))
for _, item := range items {
res := process(item) # 处理每个项目
results = append (results, res)
}
return results // gc 将清理内存
}

在 Rust 中,您需要考虑所有权和借用:

fn  process_items (items: &[Item]) ->  Vec < Result > { 
let  mut results = Vec :: with_capacity (items. len ()); 
for  item  in items { 
let  res = process (item); // 在此借用 item results. push (res); 

results // 当这个消失时,内存会被释放
}

Rust 的方法虽然消除了垃圾收集暂停,但却带来了巨大的心理负担。对于许多应用程序,尤其是 Web 服务,Go 所做的权衡更为实际。

5. 标准库的完整性和稳定性
完善的标准库减少对第三方包的依赖,保证长期稳定性。

企业应用程序需要保证今天编写的代码在几年后仍能继续使用。Go 对向后兼容性的承诺及其丰富的标准库支持这一需求。

美国运通等公司已将 Go 的稳定标准库视为将其用于需要可靠运行数十年的金融系统的关键因素。

微软的 TypeScript 团队可能也考虑到了这一点。在构建数百万开发人员依赖的编译器时,稳定性和可预测性至关重要。

需要解析 JSON、发出 HTTP 请求并处理文件?Go 的标准库可以满足您的需求:

func  fetchAndProcessData (url string , outputPath string )  error {
resp, err := http.Get(url) // 从 url 获取数据 if err != nil {
return err
}
defer resp.Body.Close()
var data []Item
if err := json.NewDecoder(resp.Body).Decode(&data); err != nil {
return err #如果json 失败
}
results := processItems(data)
file, err := os.Create(outputPath)
if err != nil {
return err
}
defer file.Close()
return json.NewEncoder(file).Encode(results) // 将结果写入文件
}

在 Rust 中,你可能需要多个外部包来实现相同的功能:

use reqwest; #对于 http请求
use serde::{Deserialize, Serialize};
use std::fs::File;
use std::io::Write; 

#[derive(Serialize, Deserialize)] 
struct  Item { /* ... */
#[derive(Serialize, Deserialize)] 
struct  Result { /* ... */
async  fn  fetch_and_process_data (url: & str , output_path: & str ) -> anyhow:: Result <()> { 
let  resp = reqwest:: get (url). await ?; 
let  data : Vec = resp. json (). await ?; // 在这里解析 json let  results = process_items (&data); 
let  mut file = File:: create (output_path)?; 
let  json = serde_json:: to_string (&results)?; 
file. write_all (json.as_bytes ( ))?; // 写入文件 Ok (()) 
}

Go 的方法意味着更少的依赖、更稳定的代码和随着时间的推移更少的“依赖地狱”。

哲学差异
将这些差异联系在一起的是一种根本的哲学分歧:

  • Go秉承实用主义和简单性。它专为处理大型代码库的程序员团队而设计,优先考虑可读性和可维护性学术纯粹性或最高表现。

  • Rust不惜一切代价追求正确性和性能。它旨在通过其类型系统和所有权模型来防止所有类型的错误,即使这意味着更陡峭的学习曲线。

这两种方法本身都没有错,但 Go 的务实选择使其在某些领域取得了非凡的成功,尤其是后端 Web 服务、云基础设施和 DevOps 工具。微软选择 TypeScript 编译器表明,这种务实主义在开发人员工具中也取得了成功。

当 Go 的方法不够完善时⚠️
公平地说,在某些情况下 Rust 的方法更胜一筹:

  • 内存受限的环境:Go 的垃圾收集器需要内存开销,这在嵌入式系统中可能会出现问题。
  • 硬实时系统:虽然 Go 的 GC 暂停通常很短,但对于硬实时要求来说,它们不够可预测。
  • 最大性能:对于每个 CPU 周期都很重要的应用程序,Rust 的零成本抽象可以提供更好的性能。
  • 在编译时防止数据竞争:Rust 的所有权模型可以捕获某些类别的并发错误,而 Go 只能在运行时捕获这些错误。

结论
Go 的成功源于这样的认识:大多数软件开发并不是为了突破理论可能的界限,而是要由普通程序员团队按时交付可靠的代码。

通过优先考虑简单性、快速编译、实用并发性和稳定的标准库,Go 创建了一种适用于广泛应用程序的高效语言。

微软决定将 TypeScript 的编译器从 JavaScript 移植到 Go 而不是 Rust,这是对这种方法的有力验证。当面对需要兼顾性能和开发人员生产力的现实工程挑战时,他们选择了 Go 的务实模型。

Rust 做出了不同的权衡,专注于通过其类型系统和所有权模型来防止整个类别的错误。这种方法有其优点,特别是对于内存安全至关重要的系统编程而言。

关键的教训是什么?语言设计并不是要创造理论上完美的语言,而是要做出符合用户需求的务实权衡。

你怎么看?Go 的务实方法是否很好地服务于你的项目,还是你更喜欢 Rust 对正确性的关注?你对微软的 TypeScript 决定有何看法?

https://www.jdon.com/78200.html

你可能感兴趣的:(javascript,reactjs)