不再迷茫:Rust, Zig, Go 和 C

杰克-逊の黑豹,恰饭啦 []( ̄▽ ̄)

热门和老牌劲旅的抗争

作为程序开发者,掌握一门编程语言总觉得不够,特别是在应用层编程太久的开发者,总想着学习一门系统级别的程序语言,补充自己的技能仓库。可问题也来了,选哪门语言合适呢?

就目前热门程度看,系统级编程语言的候选者有老牌的C和C++,新贵阵营则是Rust,Zig,Go。如果你恰好比较纠结选择哪个,不妨继续往下看。

永远的C/C++

必须要声明,C 和 C++ 是两门不同的语言,但是在整个计算机发展历程中,它们的贡献太大,加之C++对C的兼容,二者常常并论,称为 C/C++。

现代流行的操作系统几乎是C和汇编编写出来的,这奠定C语言不可撼动的重要性。只要你在操作系统之上编写程序,你就要和系统调用打交道,系统调用的背后就是C语言实现的种种能力,因此,C语言的地位,几乎无法挑战。更何况,还有不少基础软件也是用C语言开发的,像nginx, redis等等。这些软件都是行业里面的基石。C语言有如此硕果,又怎能撼动?更要命的是,C语言不再是一门语言,而变成了不同编程语言之间的通讯协议。两个语言之间想要互通,必须让自己生成的代码合乎C语言标准,另一方再借此调用,这就是FFI。

C++虽然不怎么用来开发操作系统,但是在过去几十年,大量的基础软件差不多都是C++的结果,游戏引擎、浏览器引擎、数据库引擎、解释器、编译器、图形渲染引擎等等。要想把这些软件全部推翻,用别的语言重写一遍,根本不太现实。因此,C++的地位也难以撼动。

但是C/C++的高性能,既是它们的优点,也是它们的缺点。因为要想追求卓越的性能,就必须舍弃安全保障。近些年来,反对C/C++编程的声音,就是认准它们的非安全性。对于和我一样,从非系统编程领域转移过来的新手,该不该放弃C/C++呢?我的答案是,半放弃。

半放弃的意思是,C/C++仍旧要学习,但是你不要使用它们编写项目。学习C/C++的目的,不是掌握生产力,用它们编写项目,而是出于以下几种考虑。

第一,操作系统使用C语言编写,那么操作系统暴露给开发者用的调用接口、系统库,都是C语言风格的。你肯定会说,其他高级编程语言都有自己的标准库,为我们封装得好好的,直接用就可以,何必关心什么操作系统的调用接口。问题就是高级编程语言封装得太友好,屏蔽底层的细节。在应用层面编程,这些细节你可以不关心,但是一旦拐进系统编程的门,这些细节就是你编程的基础,必须要了解。系统编程与应用层编程最大的区别,就是编程者对操作系统能力的细微把控和使用。

第二,享受现有工具库的福利。开发程序需要使用轮子,不是什么新鲜事儿,在系统编程领域也是如此。像Redis里边,内存分配器就是直接用的轮子。坏轮子,你可以不用,你喷它,我也极力赞成,但是好轮子你不用,非要自己来搞,我就觉得有点离谱了。系统编程领域的轮子,几乎都是C/C++的成果,不学C/C++,你也没办法将它们用起来。但时代总是进步,Rust就有不少好心的开源朋友提供C/C++库的binding crate,节省你自己折腾的时间。再次,感谢开源,感谢社区。

第三,维护刚才说的那些工具。你是使用工具的人,使用过程中发现问题,除了报告问题外,你就不想来个PR么?如果你不学习C/C++,怎么PR?

第四,也是最重要的原因,更好理解硬件。C语言是汇编语言的升华,它高级了一些,但仍旧贴近硬件。C语言提供的抽象能力,是对硬件加以逻辑概括,而不是把硬件直接上升到人类感性的范畴。系统编程要的就是这种硬件思维,不论你使用哪种系统编程语言。

第五,现代系统编程语言都是对C/C++缺点的完善,你学习过C/C++之后,才能理解到这些新贵语言的特性。

再说说为什么不用C/C++开发。道理很简单,人不值得信赖,人比编译器更容易犯错。

有人会说,编译器还是人写的呢,编译器怎么就比人强了。很简单啊,编译器只要被保证是靠谱的,那么编译器生成代码的这种行为就是稳定的。可是,人无法保证自身某种行为的稳定。上一秒,你聚精会神,编写的代码无误,等下一秒,你累了,分心走神,极有可能写下BUG。可编译器作为机器,人家没有累不累的说法,人家的行为非常稳定。

话再说回来,C/C++编写的每一行代码,都需要你保证安全。一行代码可以,那一百行呢,一千行呢,一万行呢?即便像Linux那样,让许多牛人分别打理不同模块,但这些牛人也不轻松啊。

Linux诞生的年代,只有C/C++这种工具,他们没得选啊,现在我们已经有了更新、更安全的工具,何必拧着头皮用老工具呢?

使用C/C++编写的项目,肯定要经过严格的测试,并经历长时间的BUG修改,反复迭代,才可以摘下不安全的帽子。那问题来了,你有很多时间去测试?其实,你所关注的,只是如何在第一次近乎安全地写好代码。大海捞针不易,在庞大的C/C++代码里发现错误,修复错误,更不易。时间宝贵啊,且行且珍惜。

还有一点是,C/C++的历史包袱太重,开发体验不佳,比方说包管理器,C语言里边include来include去,不是一个好方法,C++在最新特性中加入了module,用起来也不顺手,很麻烦。程序员的优点就是懒惰,能自动化就自动化搞定,能半自动化就半自动化搞定。有人会说,全手动处理,全局尽在我的掌握。好吧,我认可你NB,但你又何必费那么多力气呢?纵观编程的发展趋势,就是往人类偷懒的方向变化的,能省下一些力气,就省下一些力气,否则 make CMake Ninja 不会出现了。

新贵里的选择

在系统级编程语言的新贵里,有几个可选对象:Rust, Zig, Go。但是,严格地讲,Go不能算是彻彻底底的系统编程语言,因为它带有GC,系统编程一般都是冲着高性能去的,GC很拉分。接下来,我不会说该用哪一个,而是把它们的优势说说。我不喜欢所有的编程语言,但是我尊重所有的编程语言

Rust

Rust。我想它是C++的完美形态。尽管C++也在发展,引入很多安全特性,但这更像是为了安全而打补丁,它提供的安全特性,需要编程者小心使用,才能确保安全。

打个比方看,使用Rust编程,就像是走在平坦的大道上,道上有几处坑,你只要留意即可使用C++编程,就像是走在坑坑洼洼的大道上,你要找出一条平坦的路径走下去

Rust的策略是把不安全局限在少部分代码中,只要你能人工确保这些代码的安全,Rust就能保证其余代码的安全,如果代码不安全了,那根源肯定在这些少部分的代码。

C++的策略是到处使用它的安全特性编程,造就出整体的安全,不像Rust,C++编译器不参与安全的推导,推导全靠人类。我没有贬低C++的意思。Rust的推导融入了所有权规则推理,会带来更长的编译时间,也增加编程苦难,反而不如C++轻松、自在。

但是Rust的开发体验更出色。它足够抽象,抽象出来的数据表示,对人类十分友好,代码可读性非常高,语法表达能力强,能节省开发者的时间。特别是文档,结合vscode,非常nice。遇到不懂的API,鼠标悬浮,弹窗中就会给出方法签名,方法解释,方法如何使用的指南,方法需要注意的点,最重要的是给出极简的demo代码片段。C++差点意思,你还要到它的官网看,官网里确实给出demo代码片段了,但不好理解,它也给出解释了,同样也不好理解,晦涩难懂。Go语言的文档稍微好一些,虽然解释的清楚,但是每个方法或者函数缺少实例代码。

Rust提供了包管理、构建系统、测试工具,你不用单独安装什么工具。这使得Rust项目结构整洁,井井有条,开发全链路一应俱全,开发体验非常爽。包管理和前端开发的路子非常贴切,对前端开发者十分友好,难怪它可以在前端圈吃得开。

Zig

Zig目前来看初出茅庐,不成熟,但已经有拿得出手的生产环境级别的项目------bun。

一定程度上看,Zig完全可以用起来了。如果说谁能平替C语言,我会投Zig一票。

Zig保留了C的味道,弥补了C的缺点。其实C最大的缺点就几个。第一点,没有包管理机制。第二点,写C程序的时候,申请内存,释放内存,容易遗漏,C没有在语言层面提供解法。第三点,跨端编译不够简单。

Zig提供了包管理机制,提供了defer关键字确保释放内存的代码一定可以执行到,提供了显式内存分配器的设计,内存管理更加灵活、因地制宜。Zig还提供了非常强的跨端编译能力,像Uber就使用Zig作为交叉编译的工具,保证跨端的兼容和丝滑。

Zig为了确保执行流清晰、确切,没有Rust和C++那样的RAII机制,内存全部靠手动释放,编译器不会帮忙,因此Zig算不得安全的系统编程语言,但是它比C要安全,比C现代化,感觉上就是,它就是解决了自身缺陷的C语言。

Zig的语法可读性很高,表达能力很强,有 Rust,Swift 的影子,就是语法里有很多@符号的东西,看上去很别扭,在错误处理方面的语法也很别扭,需要花费不少时间适应,其余的语法就很直观、舒服了。我也是反复四次入门,才习惯这门语言,以前我是真不觉得它有多优雅!

Zig同样有开箱即用的构建系统、文档系统、测试工具,开发效率也杠杠的。不过,由于内存分配器显式化了,当你想在堆内存里申请内存时,就要自己斟酌选择哪⏰内存分配器了,并负责好内存的申请和释放。

在我看来,Zig是C语言的完美形态

Go

Go拥有GC,拥有runtime,还有包管理机制,它的语法风格简洁明了,贴近C的风格,如果用一句话来说,Go是应用层面的C语言。Go可以参与系统编程,但是超高性能要求的系统编程,就不适合它了。Go语言对于前端开发者很友好,迁移难度不大,适合替代nodejs。前端开发者编写代码,潜移默化都是偏向函数式编程多一些,Go语言同样也是偏向函数式编程的语言。只不过,它的标准库API继承了C的风格,没有做出像nodejs那么高的抽象,在使用起来多少会有一点别扭,但花费一些时间熟悉后,还是会喜欢上它的。

编译产物的大小

很多人会很在意Rust/Go/Zig编译产物的大小,拿它和C/C++对比。这里我想做一些解释。C/C++编译产物比较小,是有原因的,它们默认采用动态链接的方式在可执行文件里引入标准库,当然会很小。它之所以可以这样做,就是因为操作系统大都是用C编写的,C库在操作系统里是默认安装,天然支持动态链接。如果一个操作系统使用Rust编写,那么Rust也可以这么搞。

Rust/Go/Zig的编译产物默认采用静态链接引入标准库,因此会很大。Rust和Zig的标准库还算轻巧,导致产物最终是几百KB的样子。但是Go里边有支持Go协程的runtime,它的标准库就大得多了,因此它的产物是几MB起步的。

如果让Rust采用动态链接引入标准库,编译的产物大小和C其实差不多,稍微比C大那么几KB吧。

相关推荐
用户967151139167215 小时前
Rust 如何轻松实现 RTMP 流媒体推送?深入解析直播推流场景与解决方案
rust·ffmpeg
懒羊羊大王&15 小时前
模版进阶(沉淀中)
c++
无名之逆15 小时前
Rust 开发提效神器:lombok-macros 宏库
服务器·开发语言·前端·数据库·后端·python·rust
s91236010115 小时前
rust 同时处理多个异步任务
java·数据库·rust
owde15 小时前
顺序容器 -list双向链表
数据结构·c++·链表·list
GalaxyPokemon16 小时前
Muduo网络库实现 [九] - EventLoopThread模块
linux·服务器·c++
W_chuanqi16 小时前
安装 Microsoft Visual C++ Build Tools
开发语言·c++·microsoft
tadus_zeng16 小时前
Windows C++ 排查死锁
c++·windows
EverestVIP16 小时前
VS中动态库(外部库)导出与使用
开发语言·c++·windows
胡斌附体17 小时前
qt socket编程正确重启tcpServer的姿势
开发语言·c++·qt·socket编程