当前位置: 首页 > news >正文

从 C++ 到 Rust:不是更好的模样,而是另一套答案

最近我在学 Rust 的时候,心里一直有一种隐隐的敌意。

这种敌意并不完全来自语法难度。Rust 的语法当然有不少新东西,比如 matchif letlet ... else、所有权、借用、生命周期、OptionResult、trait 等等。但真正让我不适的地方,并不是“我看不懂它在干什么”,而是我经常会产生一种感觉:

这件事我明明知道怎么做,为什么你不让我这么写?

这是一种很典型的 C++ 程序员式挫败感。

C++ 训练出来的思维方式是:我理解机制,所以我能掌控复杂性。Rust 给人的感觉却是:你必须把意图写成编译器能验证的形式,否则即使你心里知道没问题,它也不允许你通过。

这个差异,可能正是我很长时间以来对 Rust 产生抵触的原因。

C++ 更像“机制外露”,Rust 更像“规则外露”

我觉得可以这样概括两门语言的气质:

C++ 更像“机制外露”。
Rust 更像“规则外露”。

C++ 的很多特性虽然复杂,但它通常让人能猜出背后的实现机制。

比如 C++ 里的范围 for

for (auto x : vec) {// ...
}

你大概能想象它会展开成基于迭代器的循环。

再比如 lambda:

auto f = [x](int y) { return x + y; };

你也大概能想象它背后是一个带 operator() 的匿名对象。

再比如模板:

template <typename T>
T add(T a, T b) {return a + b;
}

你知道它大致是一种编译期代码生成机制。

C++ 的抽象通常是这样的:

我给你一个更方便的写法,但你仍然能隐约看到对象、指针、引用、构造、析构、拷贝、移动、栈和堆。

它的很多东西虽然很难,但难得“有迹可循”。你可以沿着底层机制去理解它。

Rust 不一样。

Rust 经常给你一个很新颖的语法,比如:

let Coin::Quarter(state) = coin else {return None;
};

它不像 C++ 那样容易让人直接联想到某个底层对象模型。你必须知道这里发生了几件事:

  1. let PATTERN = expr else { ... }; 是一种可失败的模式绑定;
  2. else 分支必须发散,也就是不能正常继续执行;
  3. 成功匹配后,绑定出来的变量会进入外层作用域;
  4. 如果匹配失败,就必须 returnbreakcontinuepanic!
  5. 编译器依靠这些规则保证后续代码里 state 一定存在。

这不是运行时魔法,而是编译期规则。

但对于一个习惯了 C++ 的人来说,它确实会显得像“语法突然变聪明了”。

C++ 程序员的不适感:不是我不会写,是你不让我写

C++ 程序员学 Rust 时,常见的不适感并不是单纯的“不懂”。很多时候恰恰相反:

我懂这个东西在机器层面怎么实现。
我知道这里不会出错。
我知道引用不会悬空。
我知道这个变量在这条路径上一定初始化过。
我知道这两个可变访问不会重叠。
可是 Rust 编译器不认。

这时就很容易产生烦躁。

C++ 的态度更像:

你是程序员,你理解机制,你自己负责。

Rust 的态度更像:

你必须把你的理解表达成我能证明的规则。

这两者的心理体验非常不同。

在 C++ 中,很多安全性来自程序员经验、团队规范、代码审查、RAII、智能指针、const 约定、对象生命周期设计等。它当然不是没有规则,但很多规则是“工程纪律”。

Rust 则试图把这些工程纪律尽可能变成语言级别的静态约束。

所以 C++ 程序员在 Rust 里经常遇到的不是“我不会做”,而是:

我不能按照我已经理解的机制直接做。

这才是最拧巴的地方。

let ... else 这个例子为什么典型

let ... else 为例:

fn describe_state_quarter(coin: Coin) -> Option<String> {let Coin::Quarter(state) = coin else {return None;};if state.existed_in(1900) {Some(format!("{state:?} is pretty old, for America!"))} else {Some(format!("{state:?} is relatively new."))}
}

它的含义并不复杂:如果 coinCoin::Quarter(state),就取出 state;否则返回 None

问题在于,为什么 else 里必须 return None;,而不能只是写 None

答案是:Rust 不允许一个变量在某个作用域中处于“可能存在,也可能不存在”的状态。

如果写成:

let Coin::Quarter(state) = coin else {None
};state.existed_in(1900);

那么当匹配失败时,else 分支执行完后程序还会继续往下走,但此时 state 并没有被绑定。Rust 不允许这种状态存在。

所以 else 分支必须发散:

return None;

或者:

panic!("not a quarter");

或者在循环里:

continue;
break;

它们的共同点是:不会让程序在 state 未绑定的情况下继续执行。

这个设计体现了 Rust 很典型的思路:

不是让程序员在脑子里记住某个变量是否存在,而是让控制流本身保证变量一定存在。

Rust 的“魔法”多数是编译期规则

Rust 确实有一些看起来像魔法的地方。

比如在一个需要某个具体类型的地方,居然可以写:

return None;

或者:

panic!("error");

原因是它们的类型是 !,也就是 never type,表示“这个表达式不会正常产生值”。

因为它不会产生值,所以它可以出现在任何需要值的位置。它不是返回了一个神奇的值,而是根本不会继续执行到那里。

这就解释了为什么下面的代码是合理的:

let state = if let Coin::Quarter(state) = coin {state
} else {return None;
};

成功分支返回的是 state,失败分支直接从函数返回。失败分支不会真的给 state 赋值,所以不会造成类型冲突。

但如果写成:

let state = if let Coin::Quarter(state) = coin {state
} else {None
};

就不行了。因为此时 if 表达式的两个分支必须产生同一种类型。一个分支是 State,另一个分支是 Option<_>,类型不一致。

这类地方让我意识到:Rust 的“魔法”不是运行时魔法,而是类型系统和控制流规则的组合。

它并不是偷偷帮你维护一个“变量可能存在”的状态,而是从语法上禁止你进入这种状态。

C++ 是自由,Rust 是证明

如果用一句话概括 C++ 和 Rust 在这里的差异,我会说:

C++ 更相信程序员能理解机制。
Rust 更要求程序员提供证明。

C++ 允许你写很多东西。你可以手动管理资源,可以使用裸指针,可以决定对象在哪里构造、在哪里析构,可以通过约定保证引用有效,可以用移动语义表达所有权转移,也可以通过各种模板技巧制造非常复杂的抽象。

它给了你极大的自由。

但自由的另一面是,你必须自己承担大量不变量维护工作。

Rust 的做法则是:把不变量尽可能变成类型系统和借用检查器能验证的东西。

比如:

  • 一个值到底有没有所有权;
  • 一个引用是否活得足够久;
  • 是否同时存在多个可变引用;
  • 一个分支之后变量是否一定初始化;
  • 错误是否被显式处理;
  • 模式匹配是否穷尽;
  • 跨线程共享的数据是否满足安全约束。

这些事情在 C++ 中很多时候靠程序员经验,在 Rust 中则尽量靠编译器检查。

所以 Rust 写起来有时像写楷书:横平竖直,起笔收笔都有要求。写出来大概率端正,但刚开始会觉得手被绑住了。

C++ 则更像行书、草书、工程图纸和机械结构的混合体。高手可以写得非常漂亮,也可以写得非常危险。

为什么新手可能反而更容易接受 Rust

这也解释了一个有趣现象:有些没有 C++ 背景的人,反而更容易接受 Rust。

因为他们没有那么强的心理预设。

他们不会反复想:

这在 C++ 里我可以用指针解决。
这在 C++ 里我可以手动保证生命周期。
这在 C++ 里我知道不会出问题。
这在 C++ 里我可以写得更直接。

他们会直接接受 Rust 的规则:

let x = value;
let y = &x;
match x {// ...
}

他们从一开始就习惯:变量有所有权,引用有生命周期,可变性要显式,错误要用 Result,不存在的值要用 Option,分支要穷尽。

这就像一开始就照着楷书字帖练字。慢,但规整。

而 C++ 程序员更像已经形成了自己的笔法。突然有人要求每一笔都按字帖来,就会觉得别扭。

这不是谁更聪明的问题,而是已有经验和新规则之间的冲突。

“Rust 是更好的 C++”这个说法为什么容易引起反感

我以前听到“Rust 是更好的 C++”这种说法,会本能地产生反感。

现在想想,这句话的问题在于它太像一句胜利宣言:

C++ 过时了。
Rust 是替代品。
你应该承认新东西更好。

这当然会让 C++ 程序员不舒服。

更准确的说法应该是:

Rust 不是更好的 C++,而是对 C++ 长期痛点给出的另一套答案。

它没有沿着 C++ 的路线继续往前走。

C++ 的路线是:在保持兼容性、性能和表达力的前提下,不断增加机制,让程序员能够更精细地控制复杂性。

Rust 的路线是:在系统编程性能目标下,重新设计一套更强约束的类型系统和所有权模型,让很多错误无法通过编译。

两者关注的问题重叠,但解决方式不同。

所以把 Rust 简单说成“更好的 C++”,其实会遮蔽掉它真正有价值的地方。它不是 C++ 的升级皮肤,而是一套不同的设计哲学。

学 Rust 时,问题应该从“它怎么实现”变成“它要证明什么”

对 C++ 程序员来说,学 Rust 最重要的心态转变可能是:

不要总是先问它底层怎么实现,而要先问它在类型系统里保证了什么。

比如看到:

if let Some(x) = opt {// ...
}

它保证:只有匹配成功时,x 才存在。

看到:

let Some(x) = opt else {return;
};

它保证:后续代码中 x 一定存在。

看到:

match value {A => ...,B => ...,
}

它保证:分支必须穷尽,并且每个分支的类型必须统一。

看到:

result?

它保证:成功时解包,失败时提前返回,并且错误类型必须能转换。

看到借用检查器报错,也许不要立刻想:

这明明没问题。

而是问:

编译器现在缺少哪条保证?
它是不知道所有权还在?
不知道引用活得够久?
不知道两个可变引用不会重叠?
不知道某个分支一定初始化了变量?
不知道错误类型该怎么转换?

这样 Rust 的很多限制就会从“莫名其妙的规矩”,变成“我需要向编译器提供的证明”。

我对 Rust 的敌意减少了

这次思考对我很有价值。

我以前对 Rust 的敌意,很大一部分其实不是来自 Rust 本身,而是来自两个东西:

第一,是某些宣传话术让人感觉 Rust 只是“更先进的 C++”,好像 C++ 程序员的经验已经过时了。

第二,是我习惯了 C++ 的机制思维,到了 Rust 里却不断遇到规则约束,于是本能地想反驳这些限制。

但现在我更愿意把 Rust 看成一套独立的设计:

它试图把系统编程中大量依赖经验和纪律维护的不变量,变成编译期可以检查的规则。

这不意味着我必须立刻喜欢 Rust,也不意味着 C++ 就不值得继续使用。

但这至少让我愿意更多地思考 Rust 的设计,而不是急着反驳它。

也许学习 Rust 最有价值的部分,并不是掌握某几个语法点,而是理解它试图回答的问题:

在不牺牲系统编程能力的前提下,语言能不能强迫程序员把安全意图写得足够清楚?

C++ 给出的答案是:相信程序员,提供机制。

Rust 给出的答案是:约束程序员,要求证明。

这两种答案都很有价值。

真正重要的,也许不是站队,而是理解它们为什么会走向不同的方向。

http://www.jsqmd.com/news/779407/

相关文章:

  • 20260508 0
  • ESP32无人机远程识别模块:完整开源架构与安全集成实现指南
  • Snap.Hutao:免费高效的原神工具箱完全使用指南
  • 黑客赚钱的路子有多野?CTF逆向入门指南
  • Rocky linux 10.1 ARM版本系统安装
  • 如何快速入门 Kubernetes 网络配置?
  • 户外徒步戴运动耳机哪款好?盘点十款实用性价比运动耳机测评分享
  • 从单Agent协作到多Agent并行:收藏这份AI编程协作新范式指南,小白也能轻松掌握大模型
  • 从Kryo核心到Symphony系统:探秘移动SoC异构计算与能效协同设计
  • 认知神经科学研究报告【20260035】
  • 2026年北京君正数字IC笔试试卷带答案
  • 从 Claude Code 看 Harness Engineer 的设计
  • 20242210实验三《Python程序设计》实验报告
  • 3分钟配置Spyder深色模式:Python开发者的护眼终极指南
  • 2026教程:将整个项目Wiki交给Gemini 3.1 Pro,问答精度实测
  • LLM应用开发中的令牌管理:token-discipline项目详解与实践指南
  • 使用 Stream 流处理集合时如何避免中间结果占用过高内存?
  • 从“PPT小白”到“大神”,这些网站你必须知道!
  • 用Google ADK从零搭一个能调工具的AI Agent:Python实操全过程
  • 周红伟SEO能力加强和客户转化的能力点
  • 2026年最新安徽实景婚纱摄影TOP6权威评测考核报告 - 安徽工业
  • ARM开发板触摸屏移植全记录:Qt应用依赖的tslib-1.4交叉编译与配置详解
  • 世界杯足球直播APP技术维度实测与适配分析 - 奔跑123
  • VSCode 安装 Claude Code 插件,配置 DeepSeek V4(Windows)
  • Debian安装Mariadb
  • 【C++】set和map的系统性学习:
  • 回合制战斗模拟器:从策略选择到数值平衡的工程实践
  • 云计算 Linux 基础概念
  • STM32看门狗实战:用CubeMX和HAL库快速配置独立看门狗IWDG(附防误触发技巧)
  • Vidura:为本地大语言模型设计的智能体框架部署与实战指南