Carbon 语言 `Main//default` 默认库的文件类型演进:为何 `main.carbon` 取代 `main.impl.carbon`
2026/9/10 4:38:22 网站建设 项目流程

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//defaultMain//default是预期承载程序入口函数fn Run()的库。

在提案 p003403 之前,Main//default被默认规定为一个impl文件。而按照上述命名规则,实现文件必须使用.impl.carbon扩展名,因此无指令的入口文件将被命名为main.impl.carbon,这与绝大多数开发者直觉中的main.carbon相去甚远。这正是提案在 Problem 一节 指出的核心问题。

历史成因

提案的 Background 一节追溯了这一现状的由来:

  1. 一般规则下,单文件库天然可以是 API 文件。由于实现文件会隐式导入 API,一个没有 API 文件的impl库必然导致导入失败;而 API 文件不要求有实现文件,所以单文件库用api文件表达是自然且自洽的。
  2. 提案 #2550: Simplified package declaration for theMainpackage 选择impl作为默认,并为此提供了一个"空的、无法被导入的 API 文件"来兜底,但该提案并未给出选择impl的明确理由——这更像是顺带做出的决定。
  3. 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不能作为packagelibraryimport指令中的显式名称;
  • 该库只能通过"同时省略packagelibrary"的方式隐式定义;
  • 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 apackagedirective 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.carbonpackage A;):触发ImportSelf
  • fail_default.impl.carbonimpl 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"; // ...

实操要点总结:

  1. 程序入口文件:不写package/library指令,直接定义fn Run(),文件名使用main.carbon(或任意*.carbon);
  2. 每程序仅一个入口文件Main//default api每程序限一个,这是 API 文件"一库一 API"规则的直接推论;
  3. 禁止导入与显式命名Main//default不能出现在packagelibraryimport中,也不能被任何文件导入;
  4. 非入口库文件:使用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),仅供参考

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

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

立即咨询