
Carbon 语言的单一静态开放扩展机制原则为什么接口是唯一开放扩展点【免费下载链接】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 官方原则文档 docs/project/principles/static_open_extension.md 展开系统讲解 Carbon 为何将interface接口确立为唯一的静态开放扩展机制以及该决策如何解决 C 开放重载open overloading与 ADL 带来的泛型无法独立类型检查、代码上下文敏感等核心问题。读完本文你将理解 Carbon 泛型、运算符重载与 C 互操作设计背后的统一原理并能结合仓库源码prelude 接口定义与 generics 设计文档掌握接口作为扩展点的实际写法。背景C 开放重载带来的问题在 C 中同一个函数可以被重载且多个定义可以分散在不同的文件中。ADLArgument-dependent name lookup实参依赖查找 规则甚至允许一个非限定调用解析到定义在不同命名空间中的函数。这些规则被广泛用来定义静态分派的扩展点例如运算符重载以及类似swap这样的函数。但 C 对函数重载的签名没有任何约束这带来两个层面的问题泛型无法独立类型检查如果重载被用作扩展点来为多种类型定义某个操作由于签名不受约束泛型代码在尝试调用该操作时无法仅凭重载集合进行类型检查——编译器看不到所有可能的实例化类型也就无法在定义时验证泛型体的正确性。开放重载集合无法退出在 C 中只要某个非成员函数与调用实参列表相关联的类型处于同一命名空间它就可能被纳入查找。函数声明中没有直接的退出opt-out机制调用点虽然有一些退出手段但很少被使用。结果是大量同名但功能无关的非成员函数会共同构成重载集合而代码中没有任何线索表明不同命名空间里的哪些函数旨在暴露同一种能力。这正是 docs/design/generics/goals.md 中所说的open overloading问题开放重载是指重载集合不局限于单个文件或库的重载机制它通过 ADL 在不需要重新打开原命名空间的情况下实现开放二者共同构成了 C 的定制点customization points机制。这种机制的缺陷在于它不对不同重载之间的一致性做任何强制使得代码的含义依赖于导入了哪些重载与在实例化之前就能类型检查函数定义的目标背道而驰。原则接口是唯一的静态开放扩展机制Carbon 给出的回答非常直接在 Carbon 中interface是唯一的静态开放扩展机制。这意味着每个类型可以为每个接口定义自己的实现泛型代码可以针对任何实现了该接口的类型编写由于接口明确规定了调用的签名泛型代码可以独立于其被实例化的具体类型进行类型检查。为了让语言保持简单这也是 Carbon 中唯一的静态开放扩展机制。其直接推论包括函数重载被限制Carbon 中的函数重载仅限于在同一库中一同定义的签名即封闭重载C 互操作需要接口映射为了与 C 互操作C 中的运算符和swap需要在 Carbon 侧有对应的接口。为什么接口优于开放重载原则文档总结了接口作为开放扩展机制的几大优势泛型可被独立类型检查最主要优势泛型定义与泛型调用可以分别检查编译错误更早、更清晰构建也更快。更低的上文敏感度参见 低上下文敏感度原则。开放函数重载会因导入内容不同而以不同方式解析名字而接口实现是**连贯coherent**的——一个类型对同一接口至多只有一个实现见 generics 术语中的 coherence解析结果不随上下文漂移。简化 C 导出Carbon 的封闭重载简化了从 Carbon 导出到 C 的内容——不会出现跨文件重载集合被整体带出的情况。表达意图更显式接口实现是显式声明意图impl而往跨文件重载集合里加一个函数可能是无意的。接口如何分组并约束函数接口提供了一种把函数分组并表达组内所有函数都必须被实现这一约束的方式。原则文档以随机访问迭代器为例一个 C 模板函数如果只访问了类型恰好支持的某个方法子集代码暂时能工作但一旦代码改动为使用另一个子集就会在更晚的时候失败。接口把整组能力声明为契约避免了这种隐性巧合。这一点服务于 Carbon 的代码应易读、易理解、易编写这一项目目标。泛型视角接口如何成为扩展点要理解接口是唯一静态开放扩展机制需要看它在泛型中的实际形态。Generics 概览文档 给出了最经典的例子——与其为每种元素类型各写一个排序函数fn SortInt32Vector(a: Vector(i32)*) { ... } fn SortStringVector(a: Vector(String)*) { ... } ...不如写一个对所有可比较元素的向量都成立的泛型函数fn SortVector(generic T: Comparable, a: Vector(T)*) { ... }其中Comparable就是接口interface Comparable { // Less 是一个关联方法。 fn Less(self, rhs: Self) - bool; }接口只描述功能方法签名不描述数据不允许声明变量。类型需要显式实现接口impl来表示它支持该功能一个类型对同一接口至多实现一次。实现可以内联在类定义中用extend impl表示接口成员并入类的 API也可以在类外定义class Song { // ... extend impl as Printable { fn Print(self: Song) { ... } } } // 不改变 Song 的 API在 Song 或 Comparable 所属库中定义。 impl Song as Comparable { fn Less(self, rhs: Self) - bool { ... } }这套机制的价值在于编译器可以做两件事Rust 社区称之为 Golden Rule定义检查definition checking在不知道调用方信息的情况下完整类型检查泛型定义封装encapsulation只依据函数签名而非函数体即可类型检查泛型调用。这正是开放重载做不到、而接口签名可以保证的。运算符重载接口即运算符原则文档特别指出为了与 C 互操作运算符和swap需要在 Carbon 侧有对应的接口。在 Carbon 中重载运算符的唯一方式就是实现标准库中的对应接口例如一元-对应Negate接口generics 概览文档Operator overloading一节。这在仓库源码中可以直接得到印证。core/prelude/operators/arithmetic.carbon是 Carbon 标准库 prelude 中对算术运算符接口的实际定义arithmetic.carbon// Addition: a b. interface AddWith(Other: type) { let Result: type; fn Op(self, other: Other) - Result; } // Addition with assignment: a b. interface AddAssignWith(Other: type) { fn Op(ref self, other: Other); } // Increment: a. interface Inc { fn Op(ref self); } // Negation: -a. interface Negate { let Result: type; fn Op(self) - Result; }表达式设计文档中的算术运算符章节 进一步给出了面向用户类型扩展的完整形态不仅定义了AddWith(U: type)这样的参数化接口还通过命名约束named constraint给出便捷形态// Unary -. interface Negate { default let Result: type Self; fn Op(self) - Result; } // Binary . interface AddWith(U: type) { default let Result: type Self; fn Op(self, other: U) - Result; } constraint Add { extend AddWith(Self) where .Result Self; }注意这里AddWith是参数化接口Other: type作为输入参数这使得Vector(MyType)的可以随MyType的实现而扩展——这正是接口比特殊方法名更灵活的关键详见下文备选方案。与 C 的互操作影响原则文档明确承认C 互操作是做出此决策时必须面对的问题。由于 C 依赖开放重载和 ADL 建立定制点Carbon 与其互操作时Carbon 侧的运算符、swap等能力必须映射为对应的接口Carbon 的封闭重载使得从 Carbon 导出到 C 的内容更可控——不会像 C 那样把整个跨文件开放重载集合一并暴露。docs/design/generics/goals.md 也记录了这一点与使用开放重载的 C 代码互操作确实会带来问题需要专门解决该文档称之为 bridge for C customization points 的待办事项。备选方案对比为什么不用特殊方法名原则文档还专门对比了另一种流行的扩展机制——使用特定名称的方法来做运算符重载C 使用以operator关键字开头的方法名Python 使用以双下划线开头和结尾的方法名dunder methods。这两种方案的本质局限是实现的位置受限。例如用特殊方法名时Vector(T)类上的只能定义在Vector(T)的定义内部而用接口Vector(MyType)的可以随MyType的实现一起定义。C 通过允许非成员运算符重载换来了同样的灵活性但代价是必须从一个可能非常庞大的开放重载集合中挑选最佳匹配的运算符。这个对比再次呼应了原则的核心接口在实现位置上更灵活可以在定义类型、定义接口或定义类型参数的库中声明impl参见 generics 概览文档中的 parameterized impl declarations同时保持了签名约束与连贯性。相关原则与历史与低上下文敏感度的关联接口扩展比开放重载更符合 低上下文敏感度原则。开放重载的解析结果依赖导入了什么这属于典型的上下文敏感行为而接口实现的连贯性保证了解析结果不随上下文漂移代码更易复制、移动与重构。在原则体系中的位置该原则收录于 docs/project/principles/README.md与Errors are valuesPrefer providing only one way to do a given thing偏好单一做法等原则并列体现了 Carbon 以原则驱动设计决策、明确要做什么与不做什么的风格。提案来源该原则最初由 proposals/p000998-principle-one-static-open-extension-mechanism.md 提出提案明确将单一机制单一开放扩展机制作为简化语言的目标并指出在性能优先的前提下需要的是支持静态分派避免动态分派运行时开销的扩展方式最终在三种主流方案——开放函数重载C、接口Rust 风格 trait、特殊方法名C/Python——中选择了接口。小结Carbon 的单一静态开放扩展机制原则是一系列设计决策的支点设计决策结论扩展点的形态只有interfaceimpl实现泛型如何获得约束通过接口签名实现定义检查与封装函数重载封闭的仅限同一库内一同定义的签名运算符重载实现AddWith、Negate等 prelude 接口C 互操作运算符与swap需要对应接口映射备选方案特殊方法名Coperator、Python dunder被否决对于语言设计者与泛型实现者而言这一原则的直接收益是泛型可以被单独类型检查、错误更早更清晰、构建更快见 generics goals 中的定义检查讨论对于 Carbon 开发者而言这意味着扩展一个类型的能力时路径是唯一的——声明并实现接口而不是往某个开放的重载集合里碰运气地添加函数。【免费下载链接】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),仅供参考