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

Carbon Language 设计原则 p001280 解读:所有 API 都是库 API——把内置类型做成库类型的设计与实现 Carbon Language 设计原则 p001280 解读所有 API 都是库 API——把内置类型做成库类型的设计与实现【免费下载链接】carbon-langCarbon Languages 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 的设计提案 p001280Principle: 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文件中每一个公开的interface、impl和一等类型first-class type都定义在某个 Carbonapi文件中。在某些情况下公开函数的函数体不会用 Carbon 代码定义或者会以混合 Carbon 代码的形式定义——使用普通 Carbon 代码无法获得的 intrinsic内建指令。但项目会尽力减少这类情况。因此即便是内建的 API也可以像用户自定义 API 一样使用导入相应的库、使用该库的限定名并依赖 Carbon API 的普通语义规则。这段话包含三层关键信息声明层面公开函数、接口、实现和一等类型的声明/定义一律位于 Carbon API 文件中不藏在编译器里实现层面留了口子函数体可以不是 Carbon 代码或依赖普通代码接触不到的 intrinsic——但这是要最小化的例外不是常规使用层面零特权内置 API 没有特殊调用方式导入库、限定名查找、普通语义规则完全适用。原则文档中的应用细节提案的 Details 同样指向原则文档其 Applications of this principle 一节给出了四个具体落点是理解该原则如何约束语法设计的核心1. 隐式导入的 prelude 库Carbon 会有一个特殊的 prelude 库被所有 Carbon 源文件隐式导入可能还存在一条特殊的名字查找规则允许 prelude 中的名字不加限定地使用。但按本原则它们同样可通过普通的限定名查找使用。2. 类型关键字只是别名依据项目决议原文引用了 #543 与 #750 两个 issueCarbon 会有相当数量的类型关键字如i32、f64、bool。但这些关键字全部是普通类型名的别名例如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.carbonpackage 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聚合了copy、default、destroy、iterate、operators、range、types七个子库——这与原则中特殊 prelude 库被隐式导入但仍可被限定名查找的表述一致。Int是库 API 文件里定义的 class 类型原则文档说i32是Carbon.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声明定义的参数化类型其底层表示由 intrinsicint.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.carbonalias Bool MakeBool();其中MakeBool绑定 intrinsicbool.make_type——bool就是原则文档中关键字是普通类型名别名的直接实现core/prelude/types/int_literal.carbonIntLiteral整数字面量的类型同样以 intrinsic 生成的 alias 声明说明连字面量是什么类型这件事也是库 APIcore/prelude/operators/deref.carbon解引用被定义为一个库接口interface CppUnsafeDeref { fn Op(self) - Result; }——对指针取*调用什么这一语义由库中的impl决定印证了指针操作可重载的设计core/prelude/types/下还有char、float、optional、string、maybe_unformed、form等类型文件以及core/prelude/types/cpp/下的int.carbon、nullptr.carbon、void.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 及其原则文档放回仓库全貌可以得到一条清晰的线索声明层i32、f64、bool等关键字是Core.Int(32)、Core.Float(64)、Core.Bool的别名所有公开函数、interface、impl和一等类型都声明在 API 文件中见 core/prelude/types/int.carbon、core/prelude/types/bool.carbon实现层函数体可以是 intrinsic 绑定的混合代码如int.sadd也可以是纯 Carbon 代码如Int(N) as Dec的Op原则只要求前者被最小化使用层运算符、*、-等全部经由库接口AddWith、CppUnsafeDeref等重载指针类型是Carbon.Ptr(Foo)一类的库类别名例外非一等类型如函数类型和内置字面量/模式语法不受此原则约束动机让语言演化尽量发生在库代码中、让内置 API 与用户 API 体验一致、并借此证明 API 抽象机制零开销。这套设计使 Carbon 与内建符号是编译器私产的传统范式明确划界在 Carbon 中编译器负责语义规则库负责类型与行为的声明——理解这一分工是读懂后续 Carbon 标准库与互操作C/C 类型同样以库 API 形式进入core/prelude/types/cpp/的钥匙。【免费下载链接】carbon-langCarbon Languages 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),仅供参考