Carbon 语言Main//default默认库的文件类型演进:为何main.carbon取代main.impl.carbon
【免费下载链接】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 仓库中的设计提案 proposals/p003403-change-main-default-to-an-api-file.md 展开,系统讲解"省略package指令时,源文件所属的Main//default库由impl文件改为api文件"这一核心变更。你将掌握 Carbon 中 API 文件与实现文件的命名规则、Main//default库的特殊地位、该变更对可执行程序入口文件(fn Run)的实际影响,以及编译器(toolchain/check)中对应检查逻辑与测试用例的实现细节,从而正确编写与组织 Carbon 程序入口文件。
背景:Carbon 的 API 文件与实现文件
在 Carbon 语言中,每个源文件都属于某个包(package)下的某个库(library),而每个库由一个 API 文件(interface)和零个或多个实现文件(implementation)构成。根据 docs/design/code_and_name_organization/README.md 的定义:
- API 文件:
package指令不带impl修饰符,例如package Geometry library "Shapes";。其文件名必须以.carbon结尾,不得以.impl.carbon结尾(见 README.md 第 425-427 行)。 - 实现文件:
package指令带impl修饰符,例如impl package Geometry library "Shapes";。其文件名必须以.impl.carbon结尾(见 README.md 第 432-434 行)。
实现文件会隐式导入同库的 API 文件,这意味着"只有实现文件而没有 API 文件"的库在语义上是不成立的。反之,一个 API 文件却可以没有对应的实现文件——API 文件本身可以承载全部实现代码(这是单文件库得以存在的前提)。
问题:Main//default曾经默认是一个impl文件
在 Carbon 中,如果源文件既没有package指令,也没有library指令,它就隐式地属于Main包下的默认库Main//default。Main//default是预期承载程序入口函数fn Run()的库。
在提案 p003403 之前,Main//default被默认规定为一个impl文件。而按照上述命名规则,实现文件必须使用.impl.carbon扩展名,因此无指令的入口文件将被命名为main.impl.carbon,这与绝大多数开发者直觉中的main.carbon相去甚远。这正是提案在 Problem 一节 指出的核心问题。
历史成因
提案的 Background 一节追溯了这一现状的由来:
- 一般规则下,单文件库天然可以是 API 文件。由于实现文件会隐式导入 API,一个没有 API 文件的
impl库必然导致导入失败;而 API 文件不要求有实现文件,所以单文件库用api文件表达是自然且自洽的。 - 提案 #2550: Simplified package declaration for the
Mainpackage 选择impl作为默认,并为此提供了一个"空的、无法被导入的 API 文件"来兜底,但该提案并未给出选择impl的明确理由——这更像是顺带做出的决定。 - C++ 的
main.cpp惯例可能影响了最初的选择:C++ 中入口文件通常写作main.cpp,这种"更对等的感觉"可能是impl默认被选中的心理来源。
核心提案:无指令文件默认为Main//default api
提案 p003403 的核心变更只有一句话:省略package指令时,文件默认属于Main//default api,而不是Main//default impl。
由此带来两个用户可见的直接影响:
| 变更项 | 变更前 | 变更后 |
|---|---|---|
| 文件扩展名 | .impl.carbon(如main.impl.carbon) | .carbon(如main.carbon) |
| 文件数量限制 | 允许存在多个Main//default impl文件 | 每个可执行程序仅允许一个Main//default api文件 |
第二个影响的根源在于 Carbon 的库规则:一个库只能有一个 API 文件(api只能定义一次)。因此每个可执行程序有且仅有一个无指令的入口文件,这与"程序只有一个入口"的直觉一致。
移除的特殊规则
变更还消除了一个此前的特殊规则:文档中不再出现"Main//default api是一个空文件"的表述。在旧方案中,为了给impl文件提供兜底的 API,必须虚构一个无法被导入的空 API 文件;改成api默认后,这个特殊的空文件定义不再需要。
保留的限制
提案同时明确,以下既有限制保持不变:
Main//default不能作为package、library或import指令中的显式名称;- 该库只能通过"同时省略
package与library"的方式隐式定义; Main//default不能被导入。
设计理由
提案在 Rationale 一节引用 Code that is easy to read, understand, and write 这一项目目标:把Run逻辑写在main.carbon中,显然比写在main.impl.carbon中更直观。入口文件是每个程序作者最先接触、最常打开的文件,其命名应当最贴近直觉。
被否决的替代方案:继续默认Main//default impl
提案明确将Main//default impl(即变更前的现状)列为被否决的替代方案,理由如下:
- 一致性:
Main//default api与其它场景下"单文件库即 API 文件"的惯例保持一致,无需为Main//default单独开例外; - 命名自由:开发者更偏好
main.carbon而非main.impl.carbon;采用api默认后,main.carbon的扩展名是通用规则的直接结果,不需要为文件扩展名做任何特殊化处理; - 规则简化:不再需要"
Main//default api是空文件"的特殊定义; - 多实现文件价值有限:虽然旧方案允许多个
Main//default impl文件,但Run只能定义在其中某一个文件里,且Main//default api被定义为空文件、不允许共享任何内容,因此"多实现文件"的实际价值非常有限。
源码级佐证:编译器如何落实这一规则
当前仓库(提案已合并)的 toolchain 实现与上述规则完全吻合。核心逻辑位于 toolchain/check/check.cpp:
1. 隐式Main包的判定
在 check.cpp 第 63 行,编译器用常量定义了Main包名:
static constexpr llvm::StringLiteral MainPackageName = "Main";而在校验导入时(第 133-136 行),编译器判断"文件是否通过省略显式包名而隐式属于Main包":
// True if the file's package is implicitly `Main` (by omitting an explicit // package name). bool is_file_implicit_main = !packaging || !packaging->names.package_id.has_value();也就是说:既没有package指令、也没有library指令的文件(packaging为空),其包被认定为隐式的Main。这正是设计文档中Main//default规则(第 363-367 行)的实现:
If neither a
packagedirective nor alibrarydirective is provided, the file is an API file forMain//default. Animplcannot be provided forMain//default.
2. 禁止导入Main//default
check.cpp 第 165-174 行对"显式导入Main//default"发出诊断错误:
// Diagnose explicit imports of `Main//default`. There is no `api` for it. // This lets other diagnostics handle explicit `Main` package naming. if (is_file_implicit_main && is_import_implicit_current_package && is_import_default_library) { CARBON_DIAGNOSTIC(ImportMainDefaultLibrary, Error, "cannot import `Main//default`"); ... }注释中的 "There is noapifor it" 直接呼应了提案中"Main//default不能被导入"的规则——它只能通过省略指令隐式定义,不存在可供导入的实体。
3. 同库导入的冗余检查
第 154-163 行还处理了同库自我导入的情况:API 文件导入自身报ImportSelf("file cannot import itself"),而实现文件显式导入本库 API 则报ExplicitImportApi("explicit import ofapifromimplfile is redundant with implicit import"),因为实现文件本就隐式导入了 API。
4. 测试用例验证
仓库中的文件测试 toolchain/check/testdata/packages/fail_import_default.carbon 对这一系列诊断做了端到端验证:
fail_main_import_default.carbon(无任何指令的文件):import library default;触发ExplicitImportApi错误(文件不能导入自身);fail_main_lib_import_default.carbon(使用library "..."指令的文件):import library default;触发ImportMainDefaultLibrary错误(不能导入Main//default);fail_default_api.carbon(package A;):触发ImportSelf;fail_default.impl.carbon(impl package A;):触发ExplicitImportApi。
这组测试同时覆盖了 API 文件、实现文件、无指令文件三种形态,是理解Main//default边界规则的完整样例。
实战:编写你的main.carbon
基于当前规则,一个标准 Carbon 程序入口文件的写法非常直接——什么都不用声明,文件天然成为Main//default api。仓库中的真实示例可以佐证这一点:
examples/hello_world.carbon:
// Part of the Carbon Language project, under the Apache License v2.0 with LLVM // Exceptions. See /LICENSE for license information. // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception import Core library "io"; fn Run() { Core.PrintStr("Hello world!\n"); }examples/sieve.carbon 同样以无指令文件 +fn Run() -> i32的形式承载入口逻辑。甚至 examples/advent2024/ 系列(如 day1_part1.carbon)的每个day*_part*.carbon入口文件,也都是通过import library "dayX_common"、import library "sort"导入同包其它库,再定义各自的fn Run()——多个入口文件存在于同一包中,各自构成独立的可执行程序。
作为对比,非入口的库文件则需要显式声明。例如预置库的聚合文件 core/prelude.carbon 使用了完整的包与库声明,并通过export import对外公开各子库:
package Core library "prelude"; export import library "prelude/copy"; export import library "prelude/default"; export import library "prelude/destroy"; // ...实操要点总结:
- 程序入口文件:不写
package/library指令,直接定义fn Run(),文件名使用main.carbon(或任意*.carbon); - 每程序仅一个入口文件:
Main//default api每程序限一个,这是 API 文件"一库一 API"规则的直接推论; - 禁止导入与显式命名:
Main//default不能出现在package、library、import中,也不能被任何文件导入; - 非入口库文件:使用
package Name library "Lib";(API)或impl package Name library "Lib";(实现)显式归属,分别对应.carbon与.impl.carbon扩展名。
结语
提案 p003403 是一次"删繁就简"的设计修正:它把Main//default从需要特殊兜底规则的impl库,收敛为与通用"单文件库即 API 文件"规则完全一致的api库。最终效果是:Carbon 程序的入口文件回归直觉化的main.carbon,同时消除了一项不必要的特殊规则,也让"每可执行程序仅一个入口文件"成为类型系统层面的保证。对语言实现者而言,check.cpp 中的MainPackageName常量、隐式Main判定与ImportMainDefaultLibrary诊断,加上 fail_import_default.carbon 测试组,构成了这套规则从设计到落地的完整闭环。
【免费下载链接】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),仅供参考