标签 Cpp 下的文章

Rust 的安全神话?数据库 CEO 为何在关键系统中仍选 C++

本文永久链接 – https://tonybai.com/2025/07/22/cedardb-choose-cpp-rather-than-rust

大家好,我是Tony Bai。

近年来,Rust 语言无疑是技术圈最炙手可热的明星。它以“内存安全”的核心承诺,向统治了系统编程领域数十年的 C++ 发起了强有力的挑战。无数文章和布道者都在宣扬一个近乎“神话”的观点:用 Rust,你就能告别段错误和内存泄漏。

然而,就在这股“Rust 替换一切”的热潮中,一个来自行业核心的声音,给我带来了深深的思考。

Moritz Sichert,高性能数据库 CedarDB 的联合创始人兼 CEO,最近发表了一个“反直觉”的观点:

“Rust 是一门很棒的语言,我真的很喜欢用它。然而,当涉及到数据库系统时,我仍然会选择 C++ 而不是 Rust。”

一个数据库 CEO,在一个对数据安全和系统稳定性要求极致的领域,放弃了以“安全”著称的 Rust,转而拥抱充满了“历史遗留问题”的 C++。这究竟是为什么?

这并非一次简单的“厚此薄彼”,而是一场关于工程现实与语言哲学之间深刻权衡的精彩案例。

理论安全 vs 工程现实:unsafe 的“逃生舱口”

Moritz 首先承认了 Rust 的优点:理论上,Rust 通过其所有权系统和借用检查器,提供了远超 C++ 的默认安全性。这正是大家喜爱 Rust 的原因。

但问题在于,像 CedarDB 这样的高性能数据库,其开发工作远不止是处理业务逻辑。它需要深入到硬件的毛细血管中,榨干每一滴性能。这意味着:

  • 使用大量底层的 CPU 特性。
  • 实现复杂的侵入式数据结构。
  • 进行带有验证的、乐观且激进的内存访问。

在这些场景下,Rust 的安全检查反而成了“束缚”。为了完成任务,你必须使用 Rust 提供的“逃生舱口”——unsafe 关键字。

而 Moritz 抛出的重磅炸弹正在于此:

“一旦你在 Rust 中写下 unsafe 代码,所有的赌注都将失效。在 unsafe 代码中,你遇到未定义行为(UB, Undefined Bahavior)的风险,甚至比在 C++ 中还要高。”

unsafe Rust:一个更危险的“黑暗森林”?

这个论断足以颠覆许多人的认知。unsafe Rust 怎么会比 C++ 还危险?

关键在于理解 unsafe 到底意味着什么。它不是简单地“关闭”安全检查,而是程序员向编译器立下了一个契约:“编译器,请相信我,接下来的这段代码,我将手动保证它完全遵守 Rust 的内存模型和别名规则(Aliasing Rules)。

这正是问题的核心所在。
* C++ 的危险是“已知的”:C++ 就像一片广阔的雷区,但经过几十年的探索,老兵们已经绘制出了详细的“排雷图”。虽然处处是坑,但如何避免、如何调试,已经积累了大量的工程实践和模式。
* unsafe Rust 的危险是“微妙的”:unsafe 区域就像一个“黑暗森林”,你不仅要面对 C++ 程序员熟悉的裸指针、内存布局等问题,更要命的是,你还必须手动维护 Rust 那套比 C++ 更为严格的别名规则(例如,在任何给定时间,你只能有一个可变引用 &mut T,或者任意数量的不可变引用 &T,但不能两者都有)。

对于一位经验丰富的 C++ 开发者而言,在 unsafe 块里很容易不自觉地写出 C++ 风格的指针操作,却无意中违反了 Rust 的核心不变量,从而触发 UB。这种“新规则”下的自由,反而成了一个更容易跌落的陷阱。

Moritz 的观点是:在一个必须大量使用 unsafe 的项目中,与其在一个受严格规则约束的环境里“戴着镣铐跳舞”,不如直接选择那个虽然危险、但规则更为人熟知且自由度更高的 C++。

聊聊 Go:一种不同的安全哲学

那么,聊了这么多 Rust 和 C++,我们作为 Gopher 能从中得到什么启发呢?这恰好能让我们更深刻地理解 Go 的设计取舍。

Go 语言同样有一个 unsafe 包。但与 Rust 的设计哲学截然不同。

Rust 的 unsafe 是语言的一等公民,是其系统编程能力的重要组成部分,是为了实现“零成本抽象”而设计的必要工具。

而 Go 的 unsafe,从其文档的第一句话起,就在极力地“劝退”你:

“任何使用了 unsafe 包的程序都可能在未来 Go 语言版本中无法移植……使用 unsafe 的代码,其安全性需要程序员手动保证,我们不建议使用。”

在 Go 的世界里,unsafe 更像一个被严格限制的“后门”,而非一个功能特性。它的存在主要是为了实现标准库(如 runtime、sync),或在极少数与 C 库交互、进行极致性能微调的场景下使用。

我们可以这样总结三者的哲学:
* C++:默认给予你全部的权力与危险,安全是你的责任。
* Rust:默认提供极致的安全,但给你一个 unsafe 的选项,让你在立下严格契约为前提下重获权力。
* Go:默认通过 GC 和简单的内存模型提供高度的工程安全与效率,unsafe 是一个官方不鼓励、非标准的“应急方案”。

Go 的选择是,在绝大多数场景下,宁愿牺牲掉那部分通过复杂手动内存管理换来的极限性能,也要保证语言模型的简单性和大规模团队协作的工程效率与安全。

小结:没有神话,只有权衡

Moritz Sichert 的观点,并非是对 Rust 的全盘否定,更不是在鼓吹 C++ 的回归。

它有力地击碎了“Rust=绝对安全”的技术神话,揭示了一个冰冷的工程现实:在任何复杂的系统中,安全都是一个连续的光谱,而不是一个二元的开关。

当你的业务场景把你推向性能金字塔的顶尖,逼迫你大量使用 unsafe 时,Rust 的安全优势就会被削弱,其复杂的底层规则甚至可能成为新的风险来源。

这个案例告诉我们,技术选型永远没有“银弹”,只有基于特定问题、特定团队、特定目标的深刻权衡。作为开发者,我们最重要的能力,就是理解自己手中工具的设计哲学、优势边界以及它为之付出的代价。

对于绝大多数的后端服务、云原生应用而言,Go 和 Rust 提供的现代安全模型无疑是巨大的进步。但请记住,当有人在讨论“是否选择 C++”时,他可能不是在开历史的倒车,而是在思考一个我们尚未触及的、更深层次的工程问题。


你的Go技能,是否也卡在了“熟练”到“精通”的瓶颈期?

  • 想写出更地道、更健壮的Go代码,却总在细节上踩坑?
  • 渴望提升软件设计能力,驾驭复杂Go项目却缺乏章法?
  • 想打造生产级的Go服务,却在工程化实践中屡屡受挫?

继《Go语言第一课》后,我的《Go语言进阶课》终于在极客时间与大家见面了!

我的全新极客时间专栏 《Tony Bai·Go语言进阶课》就是为这样的你量身打造!30+讲硬核内容,带你夯实语法认知,提升设计思维,锻造工程实践能力,更有实战项目串讲。

目标只有一个:助你完成从“Go熟练工”到“Go专家”的蜕变! 现在就加入,让你的Go技能再上一个新台阶!


商务合作方式:撰稿、出书、培训、在线课程、合伙创业、咨询、广告合作。如有需求,请扫描下方公众号二维码,与我私信联系。

一张图读懂Go的生存之道:当“面条代码”来敲门

本文永久链接 – https://tonybai.com/2025/07/16/when-spaghetti-code-knocks

大家好,我是Tony Bai。

最近,在网上看到一张关于编程语言的 Meme 图,它以一种黑色幽默的方式,精准地描绘了我们软件开发中一个永恒的敌人,以及 Go 语言那与众不同的应对之道。

在这张图中,一个名为“面条代码 (Spaghetti Code)”的恐怖死神,手持镰刀,一路“收割”。C++ 的门敞开着,流出鲜血;Java 的门也未能幸免;甚至以安全著称的 Rust,门上同样血迹斑斑。当死神狞笑着敲开 Go 的大门时,它迎来的不是束手就擒的羔羊,而是一个手持“简洁 (Simplicity)”大棒、严阵以待的 Gopher。

这张图不仅仅是个有趣的段子,它几乎完美地诠释了 Go 语言的设计哲学和生存之道。今天,我们就来深入解构这张图:这个名为“面条代码”的死神究竟是什么?为什么连 C++、Java 和 Rust 都难以抵挡?以及,Go 手中的“简洁之棒”,到底有多大威力?

门后的敌人:什么是“面条代码”?

“面条代码”是一个非常形象的术语,用来描述那些结构混乱、难以理解和维护的代码。就像一碗意大利面,所有的面条都缠绕在一起,你很难理清任何一根面条的来龙去脉。

其技术特征通常包括:
* 高耦合、低内聚: 模块之间盘根错节,互相依赖,而模块内部的功能却分散混乱。
* 复杂的控制流: 代码的执行路径像迷宫一样,充满了深层嵌套、隐式跳转和复杂的条件判断。
* 滥用继承和全局状态: 过深的继承层次和随处可见的全局变量,使得任何一个微小的改动都可能引发雪崩式的连锁反应。

“面条代码”是所有项目的噩梦,它会让 bug 修复变得像拆弹,让功能迭代举步维艰。

走廊里的倒下者:为什么它们如此脆弱?

Meme 中,死神轻松地“收割”了 C++、Java 甚至 Rust。这并非是说这些语言不好,恰恰相反,是因为它们太强大、太灵活了,以至于为“面条代码”的滋生提供了肥沃的土壤。

1. C++ & Java:强大的抽象带来的“继承面条”与“模式面条”

它们强大的面向对象特性,如复杂的继承层次、多态、以及各种“企业级”设计模式,在带来灵活性的同时也打开了潘多拉的魔盒。

一个典型的 Java “模式面条”可能长这样:

// 一个看似“设计良好”的支付服务
@Component
public class PaymentServiceImpl implements PaymentService {
    @Autowired
    private ValidatorFactory validatorFactory;

    @Autowired
    @Qualifier("creditCardProcessor")
    private PaymentProcessor creditCardProcessor;

    @Override
    public Response processPayment(Request request) {
        // ... 一系列复杂的调用和“魔法”注入
        Validator validator = validatorFactory.getValidator(request.getType());
        validator.validate(request);
        // ...
        return creditCardProcessor.process(request);
    }
}

这段代码的背后,是 Spring 框架通过注解实现的庞大依赖注入网络。程序的控制流不再是清晰的线性调用,而是被框架的“魔法”所接管,一旦出现问题,调试起来极其困难。

2. Rust:“为编译器而战”催生的“生命周期面条”

将 Rust 列为受害者,可能会引起争议。Rust 的所有权和借用检查器,确实能从根本上杜绝内存安全问题。但正是这种严格的约束,在某些复杂场景下,可能会迫使开发者写出为了“通过编译”而扭曲的、难以理解的代码。

比如,当处理复杂的数据结构和引用时,你可能会看到这样的“生命周期面条”:

// 一个为了满足借用检查器而变得复杂的函数签名
fn process_data<'a, 'b, 'c>(
    config: &'a Config,
    data: &'b mut Data<'c>,
) -> Result<&'b str, Error>
where
    'a: 'b,
    'c: 'b
{
    // ... 一系列为了摆平生命周期而进行的复杂操作
    // ... 这段代码逻辑上可能很简单,但类型签名却极其复杂
}

这种代码虽然内存安全,但其认知负荷极高,新成员很难快速理解和维护。

Gopher 的武器:挥舞“简洁之棒”的五种招式

当“面条代码”的死神来到 Go 的门前,它发现这里没有复杂的继承、没有隐式的框架魔法、也没有纠结的生命周期。Gopher 手中的“简洁之棒”,是一套组合拳,招招打在“面条代码”的要害上。

第一式:拥抱小接口

Go 的接口是隐式实现的。这鼓励开发者定义小的、职责单一的接口。一个函数不应该依赖一个庞大的具体实现,而应该依赖它所需要的最小行为。

// "面条"代码:依赖具体的文件类型
func processFile(f *os.File) { /* ... */ }

// "简洁"代码:依赖 io.Reader 接口,更通用,更易测试
func processData(r io.Reader) { /* ... */ }

第二式:拒绝深层嵌套

Go 强制的 if err != nil 显式错误处理,杜绝了异常带来的隐式控制流。配合“前置守卫 (Guard Clauses)”的编码风格,可以让代码路径保持线性,避免“右斜”的箭头型代码。

// "面条"代码:深层嵌套
func process(p Params) error {
    if err := validate1(p); err == nil {
        if result, err := callService(p); err == nil {
            // ... 核心逻辑
        } else {
            return err
        }
    } else {
        return err
    }
    return nil
}

// "简洁"代码:使用 Guard Clauses
func process(p Params) error {
    if err := validate1(p); err != nil {
        return err
    }
    result, err := callService(p)
    if err != nil {
        return err
    }
    // ... 核心逻辑
    return nil
}

第三式:构建清晰的并发管道

面对并发,Go 不鼓励使用复杂的锁和共享内存,而是提倡“通过通信来共享内存”。使用 Channel 可以将复杂的并发任务,拆解成流水线式的、易于推理的独立阶段。

// 可能的"面条"代码:使用锁和共享状态,难以推理
var mu sync.Mutex
var data []int
// ... 多个 goroutine 通过 mu 来操作 data

// "简洁"代码:使用 Channel 构建数据管道
func generator(done <-chan struct{}, nums ...int) <-chan int { /*...*/ }
func square(done <-chan struct{}, in <-chan int) <-chan int { /*...*/ }
// main 函数中将它们串联起来,清晰明了

第四式:善用包的边界

Go 通过首字母的大小写来控制成员的可见性。这是一种简单而强大的封装机制,它强制开发者思考包与包之间的边界,防止内部实现细节泄露,从而避免了模块间的强耦合。

第五式:相信 gofmt

Go 将代码格式化提升到了语言工具链的层面。gofmt 结束了所有关于代码风格的“圣战”,让所有 Go 代码看起来都像一个人写的。这极大地降低了团队协作中的沟通成本和代码阅读的认知负荷。

更深层次的战斗:对抗软件的“熵增定律”

Meme 图背后的战斗,其实远超语言层面。软件系统就像一个孤立的物理系统,天然地趋向于无序和混乱,这就是“软件的熵增定律”

“面条代码”的死神,正是这一定律的化身。我们开发者,在日常工作中总在不自觉地为它敞开大门:
* 功能的诱惑: 为了满足不断叠加的业务需求,我们倾向于“添加”代码,而不是“重构”。
* 过早的抽象: 为了所谓的“未来扩展性”,引入了大量当前并不需要的复杂设计模式。
* 简历驱动开发 (RDD): 为了使用某个时髦的技术,而强行扭曲项目的设计。

Go 语言及其社区文化,本质上是在倡导一种“反熵增”的工程纪律。它通过其简洁的设计,迫使我们时刻对复杂性保持警惕。Go 的谚语“A little copying is better than a little dependency”(一点点复制优于一点点依赖),正是对“过早抽象”的直接反击。

小结:简洁,一种主动的防御

Meme 中的 Gopher 并非天生神力,它只是选择了一种更聪明的战斗方式。它没有选择用更复杂、更华丽的武器去和死神肉搏,而是用一把简单、坚固的“简洁之棒”,守住了自己的大门。

Go 的简洁,不是功能的匮乏,而是一种经过深思熟虑的设计选择,是一种主动防御复杂性的强大武器。它从语言层面就大大提高了制造“面条代码”的门槛。

对于我们所有工程师而言,无论使用何种语言,都应该从这张图中汲取智慧:成为那个手持大棒的 Gopher,时刻对不必要的复杂性说“不”。 这或许才是我们在软件开发这场持久战中,最终的生存之道。


你的Go技能,是否也卡在了“熟练”到“精通”的瓶颈期?

  • 想写出更地道、更健壮的Go代码,却总在细节上踩坑?
  • 渴望提升软件设计能力,驾驭复杂Go项目却缺乏章法?
  • 想打造生产级的Go服务,却在工程化实践中屡屡受挫?

继《Go语言第一课》后,我的《Go语言进阶课》终于在极客时间与大家见面了!

我的全新极客时间专栏 《Tony Bai·Go语言进阶课》就是为这样的你量身打造!30+讲硬核内容,带你夯实语法认知,提升设计思维,锻造工程实践能力,更有实战项目串讲。

目标只有一个:助你完成从“Go熟练工”到“Go专家”的蜕变! 现在就加入,让你的Go技能再上一个新台阶!


商务合作方式:撰稿、出书、培训、在线课程、合伙创业、咨询、广告合作。如有需求,请扫描下方公众号二维码,与我私信联系。

如发现本站页面被黑,比如:挂载广告、挖矿等恶意代码,请朋友们及时联系我。十分感谢! Go语言第一课 Go语言进阶课 Go语言精进之路1 Go语言精进之路2 Go语言第一课 Go语言编程指南
商务合作请联系bigwhite.cn AT aliyun.com

欢迎使用邮件订阅我的博客

输入邮箱订阅本站,只要有新文章发布,就会第一时间发送邮件通知你哦!

这里是 Tony Bai的个人Blog,欢迎访问、订阅和留言! 订阅Feed请点击上面图片

如果您觉得这里的文章对您有帮助,请扫描上方二维码进行捐赠 ,加油后的Tony Bai将会为您呈现更多精彩的文章,谢谢!

如果您希望通过微信捐赠,请用微信客户端扫描下方赞赏码:

如果您希望通过比特币或以太币捐赠,可以扫描下方二维码:

比特币:

以太币:

如果您喜欢通过微信浏览本站内容,可以扫描下方二维码,订阅本站官方微信订阅号“iamtonybai”;点击二维码,可直达本人官方微博主页^_^:
本站Powered by Digital Ocean VPS。
选择Digital Ocean VPS主机,即可获得10美元现金充值,可 免费使用两个月哟! 著名主机提供商Linode 10$优惠码:linode10,在 这里注册即可免费获 得。阿里云推荐码: 1WFZ0V立享9折!


View Tony Bai's profile on LinkedIn
DigitalOcean Referral Badge

文章

评论

  • 正在加载...

分类

标签

归档



View My Stats