Carbon vs Rust | 你想要了解的

2022-12-08 20:47:53 浏览数 (1)

昨天 Google 宣布了 Carbon[1] 语言,国内外相关技术媒体论坛争相关注,可谓是一出道即网红(两天star数上了7.7k)。甚至在 Rust 群里还有一些朋友争先恐后地学习了起来。

我同样也对 Carbon 好奇,但是我不是好奇它的语言语法设计,我是好奇它为什么会出现。

在我的世界观下,任何一件人造的新生事物的出现,它一定是有原因的。尤其是 Google 这种大公司发布的新的语言,它肯定是为了解决一些问题。

我头脑里不禁涌出以下几个问题:

  • 创造 Carbon 的动机是什么?
  • Carbon 到底想要解决什么问题呢?这些问题是 Rust 无法解决的吗?
  • Carbon 如何解决这些问题?
  • Carbon 的未来将走向何处?

这些问题是值得我们花时间思考和探索的,因为这个过程可以让我们对 Rust 语言的能力边界可以看得更清楚。这篇文章并不是鼓励你去学习 Carbon,除非你对语言的设计和实现非常感兴趣,否则你学习它短期(十年)内在实际项目中根本用不上。

“官方:「Carbon 还需要几年的时间——即使实验成功,它也不太可能在未来几年内准备好用于严肃或生产用途。[2]

现在,让我们从这五个疑问出发来探索一下 Carbon。

创造 Carbon 的动机是什么?

“有人说这是 KPI 项目,也有人说这是 Cpp 标准委员会内斗(阴谋论)的结果,我觉得都不尽然。幸亏 Carbon 官方有非常详尽的文档来阐述创造 Carbon 背后的动机,让我们来了解一下。

Cpp 仍然是性能关键型软件的主要编程语言,拥有大量且不断增长的代码库和各种投入。虽然 Cpp 还在不断改进自身以适应现代 Cpp 开发者的需求,但因为 Cpp 长达数十年的技术债务,让改进 Cpp 变得十分困难。比如 从整型提升到具有最令人烦恼解析的复杂语法[3]。Cpp 也优先考虑了向后兼容,包括语法和ABI,但是随着时间推移,不是改变或替换语言设计来简化和改进语言,而是大量的功能被添加了。

改进 Cpp 的阻力也来自于标准委员会(也许是阴谋论的来源)。标准委员会在 Cpp 演化方向上无法就 Google 团队的需求达成一致,同时在流程上也非常受限。标准委员会是以标准化而非设计导向,并且是瀑布式流程,委员会结构的设计是为了确保国家和公司的代表性,而不是建立一个包容和欢迎的团队和专家社区,以及积极为语言做出贡献的人。

至此,创建 Carbon 的动机就明确了:Carbon 团队想要创造一门语言,不直接继承 C 和 Cpp 的遗产,而是从现代化的基础开始,提供现代泛型系统、模块化代码组织和一致的简单语法,并且在此基础上建立一种简化和改进的语言,既能与C 互通,又能从C 迁移,同时放弃了透明的后向兼容性。这从根本上说是一种后续语言的方法,而不是试图逐步发展C 来实现这些改进。

简而言之就是,即离不开 Cpp,又还不想用 Cpp,改进 Cpp 又非常无望,只能创造一门新的语言来达成所愿

“有人提到可以对 Cpp 分叉来解决这些问题,但Carbon 团队不认为分叉 C 是实现这一目标的正确途径。分叉可能会混淆哪些代码适用于标准 C 。他们相信后继编程语言是一种更好的方法,因为它为 Carbon 的设计提供了更多自由,同时保留了现有的 C 生态系统投资。

Carbon 到底要解决什么问题?这些问题 Rust 解决不了吗?

动机中就包含了这门语言想解决的问题:无障碍利用现有的 Cpp 生态,并且可以不受 Cpp 几十年技术债务的影响而持续发展。

简而言之,Carbon 就是想解决 Cpp 生态的可持续发展问题

那么 Rust 语言能解决 Cpp 生态的可持续发展问题吗?答案显而易见,Rust 语言无法解决 Cpp 生态的问题。

如果非要让 Rust 解决的话,那只有用 Rust 把 Cpp 生态重新实现一遍的暴力方法。但这是完全不可能的。先不说工作量有多大,像 Google 这样的公司也没有动机把工作良好的 Cpp 项目重新实现一遍。

另外,现有的大型 Cpp 代码库做出了与 Safe Rust 不兼容的架构选择。

首先,Rust 语言和 Cpp 的无缝交互存在很多障碍。比如,Rust 无法做到“获取一个带有 foo 方法的 Cpp 类并调用该方法,或者创建一个具有 foo 方法的类并从 C 调用该方法”。Rust 和 Cpp 交互只能通过 C-ABI 来完成。即便是现在知名的可以和 Cpp 安全交互 `cxx`[4] 库,也是通过 C-ABI 来完成的。Google 还开源了另一个让 Rust 与 Cpp 双向交互的实验性工具 crubit[5]。如果是 Safe Rust 直接调用公开的 Cpp API,则要求 Cpp 代码在 API 边界遵循借用检查规则;反过来,Cpp 调用 Safe Rust API 的话,则要求 Cpp 用户遵循 Rust 借用检查规则。

其次,将 Cpp 代码移植为 Safe Rust 代码,如果不对Cpp代码架构、数据结构或 API 进行重大更改,则不能无缝移植。因为 Rust 比 Cpp 编译器更加严格,在 Cpp 中的某些合理设计,在 Rust中也许行不通。比如,C API 和数据结构在设计时并未考虑 Rust 借用检查规则。即便移植为 Unsafe Rust,也需要遵循Unsafe Rust 下的一些安全规则。

所以,Rust 语言从根本上无法解决 Cpp 自身生态的问题。除非你有新的项目,完全没有历史包袱,可以选择 Rust 就选择 Rust,完全用不着 Carbon。而 Carbon 适用于严重依赖 C 的组织和项目,比如,具有大量 C 代码或使用许多第三方 C 库的项目。

Carbon 如何解决该问题?

为了解决 Cpp 生态可持续发展的问题,Carbon 的设计目标如下:

  1. 通过包容、热情和务实的文化促进健康和充满活力的社区。虽然这可能不会直接影响 Carbon 的设计,但它会影响 Carbon 的设计方式。
  2. 面向性能设计,面向性能关键型软件。
  3. 实用的安全和测试机制。尽可能多地为 Carbon 添加语言级别的安全性,使用混合策略来平衡其他目标。
  4. 易于阅读、理解和编写的代码。包括比如,出色的工效学、IDE支持、代码的行为和语义应该尽可能清晰和简单地指定、坚持最小意外的原则等。
  5. 与现有 C 代码的无缝零开销互操作性和可扩展的移植,对惯用的 C 代码进行某种程度的源到源转换。

从 Carbon 设计目标和提供的细节文档中看得出来,Rust 语言给了 Carbon 设计太多灵感。Carbon 的追求也是开放社区、性能和安全。

通过包容、热情和务实的文化促进健康和充满活力的社区

这一点应该是从 Rust 开源社区的成功发展得到了启示。他们认为只有开源社区才能促成 Carbon 的成功。

Carbon 性能目标

但是性能的目标是支持在某些资源限制方面的性能对其成功运行至关重要的软件。包括让开发人员控制性能的各个方面、惯用代码应该很快、代码应该可以预测地执行、不给低级语言(比如 C)留下空间。

Carbon 安全目标

而 Carbon 的安全策略和 Rust 也有些许不同,它是混合策略。Carbon 将在编译时进行尽可能多的安全检查,也将提供动态运行时检查和强大的测试方法,从单元测试到集成和系统测试,一直到覆盖导向的模糊测试。Carbon 也有 unsafe,但是会对 unsafe或有风险的代码的常见模式必须支持静态检查、所有unsafe 或有风险的操作和接口都必须支持一些动态检查。

对于 Carbon 来说,它要解决的安全问题是:

  • 内存安全可防止无效的内存访问。Carbon 使用 两个主要的子类别 来保证内存安全:
    • 空间内存安全可防止访问超出源范围的地址。这包括数组边界,以及取消引用无效指针,例如NULL,C 中的未初始化指针或伪造的指针地址。
    • 临时内存安全可防止访问已释放的地址。这包括堆地址的使用后释放和堆栈地址的返回后使用。
  • 类型安全防止使用不正确的类型访问有效内存,也称为“类型混淆”。
  • 数据竞争安全,可防止内存访问竞争:当线程与不同的写入线程同时访问(读取或写入)内存位置且未同步时。

看上去和 Rust 的安全目标是一致的。并且 Carbon 的安全级别也是向 Rust 看齐的。

在编写代码时,Carbon 开发人员应该期望在不需要添加安全注释的情况下获得安全性。Carbon 将具有可选的安全注释,用于优化安全检查或提供提高安全检查覆盖率的信息。Carbon 将支持编译时安全检查,因为及早发现问题将使应用程序更加可靠。在编译时无法证明安全性的情况下,将启用运行时检查,无论是错误检测还是安全强化。

并且 Carbon 中的安全性必须与 可互操作或移植的 C 代码一起使用,以便 C 开发人员可以轻松利用 Carbon 的改进。想情况下,安全机制将设计为适用于自动移植的 C 代码。

Carbon 优先考虑语言的可用性,特别是尽量减少对 C 开发人员的再培训和简化 C 代码库的迁移,而不是其他一些语言(尤其是 Rust)所追求的可证明的安全性。

Carbon 在实现安全性时,会考虑参考 Rust 的借用检查模型,但不会直接模仿 Rust 的技术。直接模仿 Rust 的技术可能不足以实现 Carbon 的 编译器性能目标。Rust 编译性能表明它的借用检查性能很慢,尽管很难确定这有多重要或是否可以改进。并且Rust 的编译时安全方法需要使用与C 大不相同 的设计模式和惯用法,Carbon 需要在类型系统中完全建模生命周期和引用排他性,必须重新设计数据结构以避免共享可变状态,也会增加基于节点和指针的数据结构实现复杂性,比如链表。

Carbon 正在围绕安全性做出妥协,以便为 C 的发展提供一条道路。比如通过多种方式修改 Rust 模型以减轻 C 开发人员的负担、使用引用计数保证运行时内存安全(性能需要改进)等。C 开发人员必须能够轻松移植他们的代码库,并且能够以高度自动化的方式进行移植。为了实现自动移植,Carbon 不需要对移植的 C 代码进行根本性的重新设计。虽然移植工具理论上可以将所有移植的代码标记为unsafe,但 Carbon 应该使用一种安全策略,可以优雅地降级并为 C 代码提供改进,无论是否移植。

易于阅读、理解和编写的代码

代码语言:javascript复制
// carbon 语言代码示例
package sample api;

fn Square(x: i32) -> i32 {
  return x * x;
}

fn Main() -> i32 {
  return Square(12);
}

从上面示例代码可见一斑,Carbon 的语法风格像是 Cpp 和 Rust 的结合。

但本文不会去探究 Carbon 全部的语法设计,只介绍其中一个点:泛型。

Carbon 的泛型支持 模版(对应Cpp)和 可检查泛型(常用在 Rust、Kotlin、Swift、Go 等语言 )。两者的关键区别在于,模板参数只能在实例化过程中完成类型检查,而可检查泛型则指定了一个接口,参数可以在没有实例化的情况下完成类型检查。后者的好处是:

  • 泛型函数的类型检查错误更早发生,使编译器更容易产生有用的诊断。
  • 泛型函数可以产生较少的编译输出,使有许多用途的编译变得更快。

Carbon 更喜欢泛型而不是模板,模板只是为C 代码的移植而提供的。

关于更多 Carbon 语法设计内容可以参考:设计[6]

与现有 C 代码的无缝零开销互操作性

Carbon 和 C 之间的互操作性设计取决于:

  • 与各种代码互操作的能力,例如类/结构和模板,而不仅仅是自由函数。
  • 愿意将 C 的惯用语暴露在 Carbon 代码中,反之亦然,必要时最大限度地提高互操作性层的性能。
  • 使用包装器和泛型编程(包括模板)来最小化或消除运行时开销。

Carbon 的互操作性语言目标将集中在 C 17 兼容性上。

更详细的互操作性目标设计可以参考:互操作性的设计与目标[7]

Carbon 的未来将走向何处?

2022 年目标:公开 Carbon,完成 0.1 版本。主要目标包括:

  • 将实验转变为公开。
  • 达到核心语言设计基本完成的程度。

到 2022 年底,Carbon 语言核心设计基本完成,包括表达式和语句、类、泛型和模板、整数和指针等核心内置类型和接口的设计,以及与 C 的互操作性。为达到这一点所做的设计选择预计将是实验性的,其中许多可能需要在达到 1.0 之前重新审视,但语言的大致形状在这一点上应该很清楚,并且应该可以编写大致的Carbon计划。

2023 年潜在目标:完成 0.2 版本,结束实验。然而,即使实验成功结束,将 Carbon 变成一种生产质量语言仍然需要做很多工作。

2024-2025 年潜在目标:发布1.0版本。那时也应该完成将 Carbon 的所有治理转移到一个独立的开源组织。

小结

经过上面对 Carbon 的探索,我对 Carbon 的几个疑问也得到了解答。

目前离不开 Cpp 生态,又还不想用 Cpp,改进 Cpp 又非常无望,只好创造一门新的语言来达成所愿,于是 Carbon 诞生了。Carbon 就是想解决还有长期价值的大型项目所依赖的 Cpp 生态的可持续发展问题。而面对这个问题,Rust 束手无策。Rust 只是面向没有特别重的历史包袱的新的领域的挑战。如果你能用 Rust,就不需要考虑使用 Carbon。

Carbon 通过现代化的优秀设计来解决Cpp 生态的可持续发展问题,其中包括通过包容、热情和务实的文化促进健康和充满活力的社区、面向性能设计,面向性能关键型软件、提供实用的安全和测试机制、易于阅读、理解和编写的代码和与现有 C 代码的无缝零开销互操作性和自动化移植。其中安全目标和级别是和 Rust 对齐的,但是具体的实现策略,和 Rust 是不同的,Carbon不会直接模仿Rust借用检查机制,而是为了降低使用成本而考虑其他方式,并且还考虑运行时检查机制。

Carbon 目前还是实验性的,今年的目标就是发布 0.1 版。到 2025年打算发布 1.0版本。但是即便按时发布了,也不可能直接达到生产级,还有很多工作要做。

至此,我们对 Carbon 的诞生动机、它要解决的问题以及未来方向都了解清楚了,也通过这个过程明确了 Rust 的能力边界。Rust 虽然是通用语言,但是在这片被 Cpp 统治几十年的土地上可以发挥的地方也是有限的,只能去开创新的疆域。

参考资料

[1]

Carbon: https://github.com/carbon-language/carbon-lang

[2]

Carbon 还需要几年的时间——即使实验成功,它也不太可能在未来几年内准备好用于严肃或生产用途。: https://github.com/carbon-language/carbon-lang/blob/trunk/docs/project/faq.md#how-soon-can-we-use-carbon

[3]

从整型提升到具有最令人烦恼解析的复杂语法: https://shafik.github.io/c /2021/12/30/usual_arithmetic_confusions.html

[4]

cxx: https://cxx.rs/

[5]

crubit: https://github.com/google/crubit

[6]

设计: https://github.com/carbon-language/carbon-lang/tree/trunk/docs/design

[7]

互操作性的设计与目标: https://github.com/carbon-language/carbon-lang/blob/trunk/docs/design/interoperability/philosophy_and_goals.md

0 人点赞