为了账号安全,请及时绑定邮箱和手机立即绑定

我试了每一门热门编程语言,最后还是觉得 Rust 最香

标签:
杂七杂八

在这篇文章里,我将试着比较GoLang、Zig和Rust。为什么我认为Rust在这次比较中更胜一筹,对我来说。

讲故事的时间!

我分别用 GoLang、Zig 和 Rust 完成了三个项目。这些项目的规模足够庞大,足以让我更好地理解语言的基本原理、不足之处,以及是否是语言本身、框架支持或者生态系统完善程度的问题。

小贴士:可以直接跳到 TLDR 部分,快速得到答案。

Go (发音为"Gòu")

几个月以来,我开始制作开发工具,一开始使用GoLang。

第一个项目是一个简单的数据库管理与迁移工具(dbdaddy),在用 GoLang 编写时,我真的感到很开心。

尽管这是我第一次用GoLang构建比Advent Of Code及10亿行挑战问题更正式的项目,没想到不到一周我就上手了

Zig

一直以来,我在业余时间尝试了一下 Zig(没太认真)。听说 Redis 把许可证改了,变成了一个“技术上非开源”的类型。

我就想:用 Zig 语言重做 Redis 开源版本有多难?

一个新的 Redis 建阳表情包

两个月后:见鬼。

最基础的想法是基于 LRU 算法的内存缓存,称为‘GbCache’。但实际的数据则保存在磁盘上,并能够为特定键/值的变化提供通知等功能,还包括分配给键的 TTL。

Zig 的明确性真是令人印象深刻,这使得它更简单,也更容易上手工作。

我假设它明天就要支持HyperScale™的负载,看看它能否胜任,从而开始GbCache。

这种想法让我排除了使用GoLang,因为我无法对内存分配进行绝对确定性的判断,我就无法完全掌控程序的运行速度。

从这种角度来看事情,真是一个很好的学习经历,因为你会突然注意到程序中所有可能出问题、内存溢出或形成瓶颈的地方——也就是程序中那些可能卡住的地方,热点代码。

由于 Zig 很清楚,我确切地知道在何时何地分配内存空间。

但归根结底,Zig 最终并不是一个内存安全的语言,而我在这方面的问题就像是登珠峰一样难。

进入 Rust 社区

在过去两年里,我尝试并气得退出了三次 Rust。我来自一个 JavaScript 背景,我们有海量 GB 内存的高端 MacBook,始终无法理解 Rust 的那些规则,以炒作的心态接触 Rust 可能不是好主意。

即使我因愤怒多次退出,Rust 仍时常在我脑中浮现,它那奇怪的语法、让人头疼的内存管理规则以及让人摸不着头脑的生命周期。

但在写Zig的时候,我突然想到,我已经在心里记下了所有的内存规则和生命周期,但这其实给我带来了额外的心理负担。

我发现我在用 Zig 写代码时,老想模仿 Rust 的做法,结果发现了好多固定的套路。

Rust

在构建SFS(简称“Simple File Storage”)的CLI客户端时,我决定再试一次Rust。

这次在阅读《Rust编程书》的过程中,我开始思考这一切背后的为什么。

我最喜欢 Rust 的一点是它的基于所有权和借用的思维方式,用于内存管理。

虽然这种所拥有的和所引用的内存模型是虚构的,它在思考时非常方便,对我来说也不太费劲。

相反,与C和Zig这类语言要求你得在脑子里记每个内存分配和生命周期的传统内存管理方式不同。还得在脑子里记这些内存分配和生命周期?还是算了,谢谢!

要点(TLDR)

Go语言 (Go语言是Google开发的一种编程语言)

  • 快速 (就像你根本用不到这么快的 5 MAU 哈哈)
  • 可靠
  • 极其简单,语言从不成为你实现目标的绊脚石
  • 丰富的包生态系统

Zig

  • 超快
  • 傻瓜简单

Rust

  • 极速
  • 挺靠谱的
  • 丰富的软件包生态系统

所有人

  • 出色的错误处理功能

如果你是一名日常的普通后端工程师,并且喜欢关注自己在冲刺中的表现,你最好的选择大概就是 GoLang 了。

"但我的公司不使用GoLa——"
"滚蛋,离开这破公司,用合适的工具做合适的工作。"

对我来说,Golang就像一段包办婚姻,你从不会感受到那种激动,但它从不会让你失望,它是你风雨同舟的伙伴,你可以把全部赌注押在它身上。

不过朋友呐,如果你想寻找那种感觉,那就接着往下看吧!

Rust: 净化

不——但我想要的那种感觉……我想要在仅仅瞥见他们的眼睛时感觉我心脏要炸了……我想要和他们在一起感觉像在天堂,不在他们身边时感觉像在地狱一样……

蓝发锈男人

我们来看看 Rust 语言中的多线程安全例子。

    use std::thread;

    fn main() {
        let mut counter = Box::new(0); // 分配在堆上的32位整型
        let mut handles = vec![];

        for _ in 0..10 {
            let handle = thread::spawn(|| {
                *counter += 1; // 尝试增加计数值
            });
            handles.push(handle);
        }

        for handle in handles {
            handle.join().unwrap(); // 等待所有线程结束
        }

        println!("计数结果: {}", *counter);
    }

全屏 退出全屏

我们在堆上分配了一个值,有10个线程各自尝试修改它。

这段代码不安全地访问了 counter 变量,这会导致所有线程同时修改 counter

在其他语言里,类似的代码段也可以顺利编译并通过运行。

如果你之前有过多线程编程经验,你可能会首先想到使用一个“互斥量”来避免在更新counter变量时出现竞争条件。

但是很容易在庞大的代码库中因为人为错误而忽略这些小细节。

这个程序 Rust 连编译都不让通过。

由于所有权和借用规则要求,编译器会检测到你在第一个线程中的for循环第一次迭代时可变地借用了counter... 目前为止都没问题... 第二次迭代也想同时可变地借用counter不安全!这会导致编译器在编译时报错!!

引用或“借用”某个值,意图对其进行修改,这被称为‘可变借用’拥有者是指意图对其进行修改的值的拥有者。

    src/main.rs|8 列号 36-38 错误: 不能借用 `*counter` 为可变的,不能在同一时间多次借用 `*counter` 在循环的上一次迭代中已经被借用了

切换到全屏 退出全屏

在这些情况下,其他语言根本不会报错。你需要自己检查正确性,而 Rust 则避免了这样的麻烦。

这样的构造遍布语言,有助于你避免犯低级错误(通常)。

结论

得看情况啦!

我真的希望有一种语言是完美的答案,即使GoLang很接近这个目标,还是要看你的实际需求,最重要的是你的团队。

对我来说,Rust 很好用(暂时),谁知道以后会怎样呢... 哈哈

TigerBeetle 的团队选择了 Zig 并且对 Zig 很满意。

Primeagen 和 Zig 更开心。

对于Jarred Sumner而言,这个Zig这个名字就挺好,他用这个Zig写了这个Bun

Mitchell Hashimoto(背后是 HashiCorp 的家伙)正在用 Zig 语言编写 ghostty,这是一个闪电般快速的终端工具——

<translation></translation>

等一下

点击查看更多内容
TA 点赞

若觉得本文不错,就分享一下吧!

评论

作者其他优质文章

正在加载中
  • 推荐
  • 评论
  • 收藏
  • 共同学习,写下你的评论
感谢您的支持,我会继续努力的~
扫码打赏,你说多少就多少
赞赏金额会直接到老师账户
支付方式
打开微信扫一扫,即可进行扫码打赏哦
今天注册有机会得

100积分直接送

付费专栏免费学

大额优惠券免费领

立即参与 放弃机会
意见反馈 帮助中心 APP下载
官方微信

举报

0/150
提交
取消