Go 一行注释就能把整个文件绑到平台,仓颉为什么不行?
内容提要
仓颉不支持像 Go 那样用一行注释将整个文件绑定到平台,而是提供三种粒度:@When 条件编译(声明级,最实用)、cjpm source-set(目录级,对标 Go,但需配置且为实验特性)、common/specific(声明级契约)。mmap-cj 项目因 source-set 会迫使消费者开启实验开关且测试链接失败,最终改回 @When。
延伸解读
三种粒度:仓颉跨平台机制的选择逻辑
仓颉没有 Go 那样的一行文件级注释,而是提供了三种不同粒度的机制:@When 条件编译(声明级)、cjpm source-set(目录级)和 common/specific(声明级契约)。@When 最实用且非实验性,但需在每个顶层声明上重复标注;source-set 最接近 Go 的 //go:build,但需配置 cjpm.toml 且为实验特性;common/specific 用于定义跨平台 API 契约,强制签名匹配。选择哪种取决于你是想简单排除平台代码,还是建立严格的接口契约。
source-set 的消费者成本:实验特性如何传染
source-set 是 cjpm 的实验特性,使用它的库会迫使所有消费者在 cjpm.toml 中声明 [profile] experimental = true,否则 cjpm 会忽略 source-set 配置并因找不到默认 src 目录而报错。更严重的是,在 cjpm 1.2.0 上,即使消费者开启实验开关,cjpm test 仍会因链接阶段漏掉依赖库而失败(issue #3515)。这意味着依赖 source-set 布局的库,其消费者的单元测试完全无法运行,直到该缺陷被修复。
mmap-cj 的教训:为什么最终切回 @When
mmap-cj 是一个被 badger-cj 依赖的跨平台内存映射库。它最初采用 common/specific + source-set 方案,但发现消费者必须开启实验开关,且测试无法运行。在缺陷修复前,它选择切回 @When 条件编译,因为 @When 不依赖实验特性,消费者无需任何额外配置即可构建和测试。代价是每个顶层声明都要单独标注 @When,导致大量重复。这个案例表明,对于第三方库,可测试性和低消费成本比技术先进性更重要。
常见陷阱:大小写、@NonProduct 与 main 位置
使用仓颉跨平台机制时需注意几个易错点:@When 中 os 值首字母大写(如 "Linux"),而 cjpm feature 为小写点分(如 os.linux),两者不通用;source-set 中公共源码集必须加 @NonProduct 注解,且文件头的 features 声明必须与 cjpm.toml 完全一致;main 函数必须位于产品源码集,否则报错;一个 feature 组合只能对应一个目录。此外,source-set 不是交叉编译,目标平台仍由 --target 决定。
Q&A
仓颉为什么不能像 Go 那样用一行注释把整个文件绑定到平台?
因为仓颉的设计目标与 Go 不同:Go 的 //go:build 解决的是文件组织问题,而仓颉的 common/specific 机制想解决的是跨平台 API 的契约问题,要求签名一致。因此仓颉没有文件级的一行注释,而是提供了三种不同粒度的机制:@When 条件编译(声明级)、cjpm source-set(目录级)、common/specific(声明级契约)。
仓颉的 @When 条件编译怎么用?有哪些限制?
@When 可以标注在函数、类等声明或 import 上,例如 @When[os == "Linux"] func foo() { ... }。支持 os、backend、arch、cjc_version、debug、test 等条件,可用 ==、!=、>、< 等运算符组合,也支持自定义 --cfg。限制:不支持嵌套(连续两个 @When 会报错),宏展开产生的代码中含 @When 会编译报错。
cjpm 的 source-set 机制如何配置?它和 Go 的 //go:build 有什么异同?
source-set 需要在 cjpm.toml 中声明 [[source-set]],指定 src-dir 和 features,并开启 [profile] experimental = true。源码文件头部也要声明 features {} 或 features {os.linux},公共源码集需加 @NonProduct。它和 Go 的 //go:build 类似,都是目录/文件级平台绑定,但需要显式配置且是实验特性,不如一行注释方便。
mmap-cj 项目为什么从 source-set 切回 @When?
因为 source-set 是实验特性,消费者必须同样开启 experimental = true 才能编译,否则报错;更严重的是,即使消费者开启开关,cjpm test 仍然失败(链接阶段缺少依赖库,issue #3515)。而 mmap-cj 被 badger-cj 依赖,需要保证测试可运行,因此切回 @When,使消费者无需实验开关即可 build 和 test。
仓颉的 common/specific 机制有什么规则和限制?
common 和 specific 用于声明级契约:common 有完整实现时可不写 specific;common 只有签名时必须有对应的 specific;specific 签名必须匹配 common(参数类型一致、返回值相同或子类型、修饰符一致);每个 specific 必须匹配唯一的 common。不支持静态成员变量、主构造函数、静态初始化器;@Frozen 修饰 common/specific 行为异常。
source-set 和 @When 在消费者使用成本上有什么差别?
source-set 会迫使消费者开启 experimental 开关,否则 cjpm 忽略其配置并按默认 src 查找导致报错;且开启后测试仍可能失败(旧版 cjpm 缺陷)。@When 则不需要消费者做任何额外配置,普通 cjpm 即可构建和测试。因此对于第三方库,@When 更稳妥,尽管它需要在每个声明上重复标注。