为什么 Rust 能不断进化,而 C++ 和 Go 却越来越“保守”?

发布时间:2026/6/6 9:24:02

为什么 Rust 能不断进化,而 C++ 和 Go 却越来越“保守”? 文章目录为什么 Rust 能不断进化而 C 和 Go 却越来越“保守”从 Python2 到 Python3 的历史教训C 背着历史包袱前进Go 过度保守的演进Edition 机制让 Rust 不断进化结语为什么 Rust 能不断进化而 C 和 Go 却越来越“保守”任何一门想长久发展的编程语言都面临着一个问题如何在兼容旧代码的前提下让语言持续进化。从 Python2 到 Python3 的历史教训从 Python2 到 Python3是编程语言如何演进这个话题里一个永远绕不开的经典反面案例。Python 早期为了易用性做了很多妥协随着语言的持续演进问题越来越明显这使得开发者痛苦不堪。而在这一大背景Python3 以不兼容 Python2 的方式在 2008 年发布。这既无奈但又必须这么做。而这一决定使得整个 Python 社区产生了巨大的分裂继续留在 Python2 意味着项目稳定、依赖齐全和生产可用。升级到 Python3 意味着未来方向、语言更加合理。那时在社区找开源包要专门看支持 Python2 还是 Python3招聘要求 JD 上要专门写会 Python2 还是 Python3。因为从本质上来说 Python2 和 Python3 就是两门不同的编程语言。这是一个极其尴尬的局面而这场分裂到最后统一生态持续了十二年从 2008 年 Python3 的发布到 2020 年 Python2 正式停止维护。最终这笔账由整个社区来承担。C 背着历史包袱前进C 的最大特点是永远不会轻易破坏兼容性。从 1985 年诞生至今很多几十年前的代码在今天依然能编译。这听起来很美好但是代价是巨大的。因为 C 只能不断往上加新东西不能删旧东西这导致了语言变得非常的臃肿。同一个功能有很多写法比如初始化变量int a 1; int a(1); int a{1}; auto a 1;同时这也导致错误设计永远无法被删除比如 NULL、C 风格数组、旧式指针用法等这就导致开发者必须同时理解新旧好几套东西。可以说兼容性的包袱实在是太重了持续演进下去这个包袱还会更重这也就导致标准技术委员变得会愈发保守很多设计提案根本通过不了。除此之外编译器的开发也因此变得非常复杂为了兼容几十年的代码编译器必须理解很多历史规则必须保留旧语义的特殊情况处理各种奇怪但合法的写法毕竟我不是 C 专家所以我的吐槽其实是不够犀利的感兴趣的话可以去油管上看下 Lazo Velko 的这个视频《The worst programming language of all time》足足两个小时的吐槽而且有理有据很下饭。Go 过度保守的演进Go 提出了兼容性承诺Go 1 写的代码未来 Go 1.x 都应该能运行而这成为整个生态的重要基础。这样的好处是升级成本几乎为零但是 Go 的演进速度实在是太慢。早期接触 Go 的开发者一定知道那时候 Go 连个包管理工具都没有所有代码都写在$GOPATH/src目录下无版本锁定、无依赖版本管理。官方对此的回应是无版本设计是刻意取舍。这其实很好理解早期 Go 就是为了服务 Google 内部而研发的无版本设计符合 Google 内部的情况。也正因如此在那时社区涌现出了大量包管理工具如godep、glide、govendor 等。随着社区的不断反映Go1.5 推出 Vendor 作为 GOPATH 补丁但是依旧没解决无版本锁定和无依赖版本管理的问题只是允许把依赖放进项目本地 vendor 目录。而我们现在用的 Go Modules 是 Go1.112018才作为实验性功能推出的直到 Go1.162021才永久默认启用。除此之外还有泛型也是在社区的不断反映下Go 团队才打破最初定下的不支持泛型的原则。然而泛型的发展历程依旧是非常坎坷最终在 Go1.18 才正式推出泛型但是依旧很保守比如不支持泛型方法。这也导致了推出泛型之后几乎没人用的窘境。而泛型方法直到 Go1.26 才支持。另外Go 社区一直存在着诟病错误处理的声音希望能像 Rust 的?那样进行错误传递避免一次又一次的写iferr!nil{returnerr}虽然我对这种显式处理错误的方式没啥意见也挺好的。但是社区的声音也是有道理的类似于 Rust 的?处理方式也是显式的而且更加的优雅。然而不出意外Go 官方依旧无视这一诉求。也正因为演进过慢的原因社区还自发分裂出 dingo 这个项目在这里你可以使用?进行错误传递。不过这个项目处于早期阶段做下了解就好了。Edition 机制让 Rust 不断进化Rust 自 1.0 版本发布以来始终承诺向后兼容这意味着你在 2015 年写的 Rust 代码在 2026 年的编译器上依然能正常运行而这就需要依靠 Edition 机制来保障。Edition 大约每三年发布一个版本比如 Edition 2015、Edition 2018 等。这一点和 C 相似比如 C11、C14 等。而两者最大的区别是C 的新标准需要长期背负所有历史规则而 Rust 的 Edition 则允许语言逐步引入新规则并逐步淡化旧写法。因此对于新手来说不需要纠结应该学习哪个 Edition。直接学习最新的 Edition 即可。与此同时Rust 还保证了不同 Edition 之间的互操作性。比如一个 Edition 2024 的项目可以正常依赖 Edition 2015、2018 等的 crate。这里面的差异交由 Rust 工具链来处理开发者并不需要操心。而这也引出了一个重要的前提Edition 之所以能够成功落地一个重要原因在于 Rust 的工具链如 cargo、rustc、rustfmt 等全部都由官方统一维护与管理。这里我将演示一个 Edition 2024 的项目调用一个 Edition 2015 的 crate在这个 crate 里使用async作为变量名。Edition 2018 引入关键字async和await也就是说在 Edition 2015它们是能作为变量名使用的letasync123;// Edition 2015 合法新建了一个 workspace 项目目录结构如下├── Cargo.lock ├── Cargo.toml └── crates ├── new │ ├── Cargo.toml │ └── src │ └── main.rs └── old ├── Cargo.toml └── src └── lib.rs根目录的Cargo.toml如下[workspace] resolver 3 members [crates/new, crates/old]old 的Cargo.toml如下[package] name old version 0.1.0 edition 2015old 的lib.rs如下这里使用async作为变量名然后打印出来pubfndemo(){letasync123;println!(The value of async is: {},async);}new 的Cargo.toml如下这里我们引入 old crate[package] name new version 0.1.0 edition 2024 [dependencies] old { path ../old }new 的main.rs如下这里我们只是调用 old crate 的 demo 函数fnmain(){old::demo();}我们到根目录执行cargo run命令就能打印出结果了cargorun# The value of async is: 123这就是 Rust 的核心设计哲学之一 Stability Without Stagnation稳定而不停滞。既允许语言不断进化又不会无脑向后兼容还尽可能保证导致生态不分裂。结语虽然这篇文章是夸 Rust 的 Edition 机制的但是要记住这一切的前提是Rust Edition 是站在巨人的肩膀上的是吸取的很多前人的教训的。在未来也还会有后来人站在 Rust 的肩膀上吸取 Rust 的教训。

相关新闻