Carbon Language 设计原则 p001280 解读:所有 API 都是库 API——把内置类型做成库类型的设计与实现
2026/9/10 12:12:27 网站建设 项目流程

Carbon Language 设计原则 p001280 解读:所有 API 都是库 API——把内置类型做成库类型的设计与实现

【免费下载链接】carbon-langCarbon Language's main repository: documents, design, implementation, and related tools. (NOTE: Carbon Language is experimental; see README)项目地址: https://gitcode.com/GitHub_Trending/ca/carbon-lang

本篇围绕 Carbon Language 的设计提案 p001280:Principle: All APIs are library APIs 展开,完整梳理"语言与标准库的分工"这一核心问题的背景、原则表述、适用边界与替代方案权衡,并结合仓库中core/预置库的真实源码,说明"内置整数类型也是库 API"在参考实现里究竟长什么样。读完本文,你可以掌握这一原则的完整论证链,并能定位到支撑它的具体源码文件。

一、要解决的问题:核心语言与标准库的"分工"

提案的开篇把问题定义得非常直白:

We need a clear and consistent "division of labor" between the core language and the standard library.

(我们需要在核心语言与标准库之间有一个清晰且一致的"分工"。)

这个"分工"指的是:哪些类型、函数、语义属于语言本身(编译器直接内建),哪些属于标准库(以语言代码声明、按普通 API 使用)。边界划在哪里,会直接决定语言的可表达性、可演化性和用户体验。

背景:不同语言把边界划在不同位置

提案的 Background 指向原则文档 docs/project/principles/library_apis_only.md,其中给出了跨语言对比:

  • 每一个主流现代语言都带有标准库——由语言本身写成(实现可能不是)的 API 集合;
  • 但各语言在"语言"与"库"之间划线的地方不同。例如 Go 的map类型内建于核心语言,而 C++ 的对应物std::unordered_map属于标准库;
  • 在 Swift 中,甚至整数和指针这类基本类型也是标准库的一部分,不存在真正"内建"的类型。

原则文档进一步指出,这种决策对语言设计有重要后果:C++ 中许多重要特性(移动语义、可变参数、协程)主要受其在一小批标准库类型上的预期用途驱动;如果这些类型被内建进核心语言,语言本身可以大幅简化、类型可用得更快,但代价是牺牲了公共场景之外用户的灵活性。这正是 p001280 试图回答的问题。

二、原则的核心内容:所有公开 API 都声明在 API 文件中

提案的 Proposal 部分是全文的骨架,完整表述如下:

在 Carbon 中,每一个公开函数都声明在某个 Carbonapi文件中,每一个公开的interfaceimpl和一等类型(first-class type)都定义在某个 Carbonapi文件中。在某些情况下,公开函数的函数体不会用 Carbon 代码定义,或者会以"混合 Carbon 代码"的形式定义——使用普通 Carbon 代码无法获得的 intrinsic(内建指令)。但项目会尽力减少这类情况。

因此,即便是"内建"的 API,也可以像用户自定义 API 一样使用:导入相应的库、使用该库的限定名,并依赖 Carbon API 的普通语义规则。

这段话包含三层关键信息:

  1. 声明层面:公开函数、接口、实现和一等类型的"声明/定义"一律位于 Carbon API 文件中,不藏在编译器里;
  2. 实现层面留了口子:函数体可以不是 Carbon 代码,或依赖普通代码接触不到的 intrinsic——但这是要"最小化"的例外,不是常规;
  3. 使用层面零特权:内置 API 没有特殊调用方式,导入库、限定名查找、普通语义规则完全适用。

原则文档中的应用细节

提案的 Details 同样指向原则文档,其 Applications of this principle 一节给出了四个具体落点,是理解该原则如何约束语法设计的核心:

1. 隐式导入的 prelude 库

Carbon 会有一个特殊的 "prelude" 库,被所有 Carbon 源文件隐式导入;可能还存在一条特殊的名字查找规则,允许 prelude 中的名字不加限定地使用。但按本原则,它们同样可通过普通的限定名查找使用。

2. 类型关键字只是别名

依据项目决议(原文引用了 #543 与 #750 两个 issue),Carbon 会有相当数量的类型关键字,如i32f64bool。但这些关键字全部是普通类型名的别名,例如Carbon.Int(32)Carbon.Float(64)Carbon.Bool。同时,所有算术与逻辑运算符都是可重载的,因此这些类型可以定义为 class 类型。这些类型的成员函数体大概率不会用 Carbon 实现——而原则只约束函数声明,不约束函数定义,因此并不冲突。

3. 指针类型也是库类

形如Foo*的指针类型将是某个库类类型的别名,例如Carbon.Ptr(Foo)。由此 Carbon 可以支持对->和一元*这类指针操作的重载。

4. 函数式语法操作也是库函数

所有使用函数式语法的操作(类比 C++ 的sizeof()decltype())都将作为标准库函数实现;必要时可以用关键字做别名,函数体也不必用 Carbon 定义。

三、参考实现中的证据:core/目录下的 prelude 正是这套原则的落地

以上设计在当前仓库的core/目录中已能看到初步实现。最直接的证据是 prelude 入口文件 core/prelude.carbon:

package Core library "prelude"; export import library "prelude/copy"; export import library "prelude/default"; export import library "prelude/destroy"; export import library "prelude/iterate"; export import library "prelude/operators"; export import library "prelude/range"; export import library "prelude/types";

从源码结构看,prelude 本身就是一个普通的package Core库,通过export import聚合了copydefaultdestroyiterateoperatorsrangetypes七个子库——这与原则中"特殊 prelude 库被隐式导入,但仍可被限定名查找"的表述一致。

Int是库 API 文件里定义的 class 类型

原则文档说"i32Carbon.Int(32)的别名、Int可以定义为 class 类型",在 core/prelude/types/int.carbon 中得到了直接印证:

package Core library "prelude/types/int"; private fn MakeInt(size: IntLiteral) -> type = "int.make_type_signed"; class Int(N: IntLiteral) { adapt MakeInt(N); }

Int(N)就是一个用普通class声明定义的参数化类型,其底层表示由 intrinsic"int.make_type_signed"提供——这正是提案所说"函数体可能不是 Carbon 代码、或依赖普通代码不可用的 intrinsic"的"混合 Carbon 代码"形态。

而它的全部运算符行为,都是库文件中写明的impl块,绑定到各种 intrinsic:

// 同宽度加法 final impl forall [N: IntLiteral] Int(N) as AddWith(Self) where .Result = Self { fn Op(self, other: Self) -> Self = "int.sadd"; } // 与任意可隐式转换为 Int(N) 的类型相加 impl forall [N: IntLiteral, U: ImplicitAs(Int(N))] Int(N) as AddWith(U) where .Result = Int(N) { fn Op(self: Int(N), other: Int(N)) -> Int(N) = "int.sadd"; }

同一个文件还展示了完整的"库 API"结构:拷贝(Copy)、未成形初始化(UnformedInit)、字面量隐式转换(IntLiteral as ImplicitAs(Int(To)),绑定"int.convert_checked")、显式转换(As)、浮点字面量的不安全转换(FloatLiteral as UnsafeAs(Int(To)))、比较(EqWith/OrderedWith)、算术(AddWith/SubWith/MulWith/DivWith/ModWith/Negate)、位运算与移位、复合赋值(AddAssignWith等)、以及Inc/Dec。注意Dec的实现是一个真正的 Carbon 函数体:

impl forall [N: IntLiteral] Int(N) as Dec { fn Op(ref self) { fn AsInt(n: IntLiteral) -> Int(N) = "int.convert_checked"; self -= AsInt(1); } }

也就是说:同一类型的不同操作,有的绑定 intrinsic,有的就是普通 Carbon 代码——两者共存于同一个 API 文件中,没有哪个操作享受"内建"特权。

其他"内建"类型同样以库形式声明

  • core/prelude/types/bool.carbon:alias Bool = MakeBool();,其中MakeBool绑定 intrinsic"bool.make_type"——bool就是原则文档中"关键字是普通类型名别名"的直接实现;
  • core/prelude/types/int_literal.carbon:IntLiteral(整数字面量的类型)同样以 intrinsic 生成的 alias 声明,说明连"字面量是什么类型"这件事也是库 API;
  • core/prelude/operators/deref.carbon:解引用被定义为一个库接口interface CppUnsafeDeref { fn Op(self) -> Result; }——"对指针取*调用什么"这一语义由库中的impl决定,印证了"指针操作可重载"的设计;
  • core/prelude/types/下还有charfloatoptionalstringmaybe_unformedform等类型文件,以及core/prelude/types/cpp/下的int.carbonnullptr.carbonvoid.carbon——后者为 C/C++ 互操作提供了一组同样以库 API 形式暴露的类型。

prelude 的构建方式

从 core/BUILD 与core/prelude/的文件组织可以看出,这些.carbon文件是按库单元组织的 Carbon 源文件,经由参考实现的检查器/编译器目标(见 toolchain/ 中的 driver 与 check 工具)参与构建与测试;core/prelude.carbon头部带有AUTOUPDATE标记,说明其内容部分由自动化工具维护。这里不展开构建细节,重点是:预置库就是一组普通的 Carbon 库文件,没有独立的"内置符号表"概念。

四、原则的例外与边界

原则文档的 Exceptions 一节划定了两条明确的边界,理解它们才能避免把原则读成教条:

1. 只约束"一等类型"(first-class types)

原则对类型生效的前提是该类型是"一等"的——即它可以作为运行时变量、函数参数和返回值的类型。Carbon 类型系统中还会有些使用受限的类型,原则不适用于它们。原文特别指出:函数类型可能不是一等类型,这种情况下它们不必是库类型。

2. 内置字面量语法不属于类定义

某些类型(如元组、结构体、某些整数类型)会有内建的字面量语法来构造其值;部分情况下(元组、结构体)字面量语法还可同时用作模式语法。执行这些操作的逻辑从语义上讲属于这些类型的公开 API,但不会出现在这些类型的类定义里。换言之,原则约束的是"声明在 API 文件中",而非"每个行为都必须写在类体里"。

这两条例外与实现相互呼应:Int的字面量写法、IntLiteral的引入机制都不在类定义内;而函数类型在当前仓库的 prelude 中确实没有以class形式出现,从源码结构看与"函数类型可能不是一等类型"的保留态度相符。

五、为什么选这个原则:与三大语言目标的对应关系

提案的 Rationale 把原则挂接到了 docs/project/goals.md 中明确列出的目标上,形成三点论证:

1. 促进软件与语言的演化(对应 Software and language evolution)

因为用户代码基于 Carbon 提供的类型编写时,这些类型本质上与用户自定义类型没有地位差异,所以可以迁移到合适的用户自定义类型上去;同时语言自身的更多演化可以发生在不需要编译器专业知识的库代码中。

2. 让代码更易读、易懂、易写(对应 Code that is easy to read, understand, and write)

用户自定义 API 可以达到与语言定义 API 相同的人机工效(ergonomics);两类 API 在语法、语言规则和核心概念上保持一致——这正是"所有 API 都是库 API"带来的直接可读性收益。

3. 间接支撑性能关键软件(对应 Performance-critical software)

连最基础类型都使用 Carbon 的 API 抽象机制(impl、运算符接口、隐式转换等),就意味着这些机制必然不引入任何性能开销——否则连Int自己都无法承受。这相当于把 API 抽象机制放进了最严苛的基准场景。

在 goals.md 中,这几个目标同属"语言目标与优先级"清单,且文中明确"这些原则(principles)用于澄清这些目标"——p001280 正是原则目录所定义的"原则"的典型用法:一条影响多个设计决策、而非针对单一特性的规范。

六、被否决的替代方案:内建原始类型

提案最后的 Alternatives considered 只列了一个替代方案,但论证很有代表性:

内建原始类型(Built-in primitive types):可以走 C++ 的路线,让算术类型和指针类型成为内建类型。但提案认为这会大幅侵蚀上述所有优势:

  • Carbon 预期会有多种指针(例如表示不同所有权语义的指针)和多种算术类型(例如以不同方式处理溢出);
  • 它们不可能全部内建——内建符号的数量必然受限;
  • 因此,把公共场景下的类型也放进库里,反而能保证语言有足够的表达力,容纳这些少见场景的库类型。

换言之,"让最常见的类型也走库 API"不是理想主义的代价,而是为了保证"所有权指针""溢出安全整数"这类非常规类型与i32使用同一套机制的必要前提

七、小结:一条原则如何贯穿语法、类型系统与标准库

把提案 p001280 及其原则文档放回仓库全貌,可以得到一条清晰的线索:

  1. 声明层i32f64bool等关键字是Core.Int(32)Core.Float(64)Core.Bool的别名;所有公开函数、interfaceimpl和一等类型都声明在 API 文件中(见 core/prelude/types/int.carbon、core/prelude/types/bool.carbon);
  2. 实现层:函数体可以是 intrinsic 绑定的"混合代码"(如"int.sadd"),也可以是纯 Carbon 代码(如Int(N) as DecOp),原则只要求前者被最小化;
  3. 使用层:运算符(+*->等)全部经由库接口(AddWithCppUnsafeDeref等)重载,指针类型是Carbon.Ptr(Foo)一类的库类别名;
  4. 例外:非一等类型(如函数类型)和内置字面量/模式语法不受此原则约束;
  5. 动机:让语言演化尽量发生在库代码中、让内置 API 与用户 API 体验一致、并借此证明 API 抽象机制零开销。

这套设计使 Carbon 与"内建符号是编译器私产"的传统范式明确划界:在 Carbon 中,编译器负责语义规则,库负责类型与行为的声明——理解这一分工,是读懂后续 Carbon 标准库与互操作(C/C++ 类型同样以库 API 形式进入core/prelude/types/cpp/)的钥匙。

【免费下载链接】carbon-langCarbon Language's main repository: documents, design, implementation, and related tools. (NOTE: Carbon Language is experimental; see README)项目地址: https://gitcode.com/GitHub_Trending/ca/carbon-lang

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询