Sway 实验性特性(Experimental Features)完全指南:特性开关、优先级机制与条件编译
【免费下载链接】sway🌴 Empowering everyone to build reliable and efficient smart contracts.项目地址: https://gitcode.com/GitHub_Trending/sw/sway
Sway 编译器通过一套统一的"实验性特性(Experimental Features)"机制,在语言特性尚未稳定、需要受控引入破坏性变更或需兼容旧行为时,为开发者提供显式的 opt-in/opt-out 开关。本文基于 docs/book/src/reference/experimental_features.md 系统讲解该机制的完整脉络:如何在Forc.toml、forcCLI 与环境变量三个层面配置特性开关、三者之间的覆盖优先级,以及如何用#[cfg(experimental_<flag> = true/false)]编写依赖特性的条件编译代码。读完本文,你将能够准确识别当前仓库已内置的全部特性 flag,并在自己的 Sway 工程中按需、可控地启用或停用这些语言特性。
实验性特性是什么,为什么要用它
Sway 编译器支持实验性特性,其主要用途有三类(对应仓库中标注了tracking-issue标签的 GitHub Issue):
- 开发尚未稳定的大型语言特性:例如 References(引用)特性,在开发过程中可能不稳定,需要逐步打磨后才正式放开;
- 以受控方式引入破坏性变更:例如 Partial equivalence(部分等价性)相关改造,允许使用方显式选择迁移时机,而不是在升级编译器时被动接受破坏;
- 在不兼容变更出现时保留旧编译器行为:例如 New Hashing(新哈希方案),让需要旧行为的工程可以在升级后继续按原语义编译。
每项实验性特性在 Sway 仓库中都有一个对应的tracking-issue,其中包含该特性的详细描述以及它引入的所有破坏性变更,可作为启用前的必读资料。当前已激活与已合入的特性清单均可通过该标签在仓库 Issue 列表中检索。
当前仓库内置的特性 flag 全集
特性 flag 的权威定义位于 sway-features/src/lib.rs,通过features!宏一次性声明。截至当前仓库版本,共定义了 5 个实验性特性,每个特性同时携带其默认开关状态(true表示默认启用,false表示默认关闭)和对应的 tracking issue:
| 特性 flag | 默认状态 | 特性说明 |
|---|---|---|
new_encoding | 默认启用(true) | 新的 ABI/数据编码方案(对应 tracking issue #5727) |
references | 默认启用(true) | 引用类型特性(对应 tracking issue #5063) |
new_hashing | 默认启用(true) | 新的哈希方案(对应 tracking issue #7256),文档中以它作为 flag 示例 |
str_array_no_padding | 默认关闭(false) | 字符串数组不带填充字节的新布局(对应 tracking issue #7528) |
dynamic_storage | 默认关闭(false) | 动态存储特性(对应 tracking issue #7560) |
从宏定义可以看出,Feature枚举与ExperimentalFeatures结构体均由这 5 个 flag 宏展开生成,每个 flag 在ExperimentalFeatures中对应一个bool字段,其默认值直接由宏参数决定。因此上述默认状态是写死在编译器里的;如果你在Forc.toml的experimental字段中没有列出某个已存在特性 flag,编译时就会使用这个默认值。
三级配置方式与覆盖优先级
实验性特性可以通过三种方式启用和禁用:Forc.toml(清单级)、forcCLI 参数(命令级)、环境变量(会话级)。三者之间存在明确的优先级关系,这也是整个机制中最重要的规则:
环境变量覆盖 CLI 参数,CLI 参数覆盖
Forc.toml配置。
具体的解析顺序在 sway-features/src/lib.rs 的ExperimentalFeatures::new中被完整实现,注释明确列出了五步应用顺序:
- manifest(
Forc.toml,各 flag 之间无特定顺序); - CLI 的
--no-experimental; - CLI 的
--experimental; - 环境变量
FORC_NO_EXPERIMENTAL; - 环境变量
FORC_EXPERIMENTAL。
也就是说,即便某个特性在Forc.toml中已开启,你依然可以用 CLI 或环境变量在单次构建中临时关掉它;反之,Forc.toml中未开启的特性也可以由 CLI/环境变量临时打开,无需改动清单文件。ExperimentalFeatures::new先从default()出发,依次叠加清单、CLI 禁用以外的 CLI 启用项,最后再解析两个环境变量完成覆盖,最终得到一个合并后的特性集合传给编译过程。
下面分别介绍三种配置方式的具体写法。
方式一:Forc.toml中的[project] experimental字段
要为某个包启用/禁用实验性特性,在Forc.toml的[project]小节内使用experimental字段即可。它是一个键值映射:键为特性 flag 名,值为true(启用)或false(禁用):
[project] name = "my_contract" version = "0.1.0" license = "Apache-2.0" # ... 其他项目字段 ... experimental = { new_hashing = true, str_array_no_padding = false }- 若某个已存在特性未出现在
experimental字段中,则使用上文表格中的编译期默认值; - 若填写的 flag 名不存在,会得到
Unknown experimental feature: "<flag>".之类的解析错误(详见下文"错误处理"小节)。
从实现看,forc-pkg/src/manifest/mod.rs 的Project结构体中,experimental被反序列化为HashMap<String, bool>,并由parse_from_package_manifest逐项通过set_enabled_by_name写入特性集合;set_enabled_by_name内部会对 flag 名做trim()并匹配已知名称,空串被静默忽略,未知名称则报错。
仓库的 e2e 测试大量使用了这一配置方式,例如 test/src/e2e_vm_tests/test_programs/should_pass/language/associated_const_in_decls_of_other_constants/test.dynamic_storage.toml 中:
experimental = { new_encoding = true, dynamic_storage = true }test.dynamic_storage.toml这类后缀配置由 e2e 测试框架按需叠加到被测包上,用于在特定用例中开启对应特性——这恰好验证了"在Forc.toml层开启特性"这一路径的真实运作方式。
方式二:forcCLI 的--experimental/--no-experimental
在命令行层面,使用两个编译期 flag 完成 opt-in 与 opt-out:--experimental用于开启、--no-experimental用于关闭:
forc build --experimental some_feature --no-experimental some_other_feature同时操作多个特性时用逗号分隔(注意中间不要有空格,逗号即分隔符):
forc build --experimental some_feature_1,some_feature_2 --no-experimental some_other_feature_1,some_other_feature_2这两个 flag 可以同时出现:--no-experimental的优先级高于清单配置、低于--experimental(见前文顺序),所以同一命令行里既开启又关闭不同特性是完全合法的组合。
从实现看,sway-features/src/lib.rs 中的CliFields用 clap 的#[clap(long, value_delimiter = ',')]声明了experimental与no_experimental两个参数,value_delimiter = ','正是命令行逗号分隔语义的来源。该CliFields被挂载到多个 forc 子命令上,例如 forc/src/cli/commands/build.rs、forc/src/cli/commands/check.rs、forc/src/cli/commands/test.rs、forc/src/cli/commands/contract_id.rs、forc/src/cli/commands/predicate_root.rs。也就是说,build、check、test、contract-id、predicate-root等命令都可以直接携带这两个 flag。
随后这些参数被传入编译链路:以forc build为例,forc/src/ops/forc_build.rs 将cmd.experimental.experimental与cmd.experimental.no_experimental取出,最终汇入 forc-pkg/src/pkg.rs 的build函数,在该函数中对依赖图上的每个包调用ExperimentalFeatures::new(&manifest.project.experimental, experimental, no_experimental)完成合并。
方式三:环境变量FORC_EXPERIMENTAL/FORC_NO_EXPERIMENTAL
在环境层面,使用FORC_EXPERIMENTAL与FORC_NO_EXPERIMENTAL两个环境变量分别启用和禁用特性,值为逗号分隔的特性名列表。常见用法是在运行forc前先行设置:
# 只开启 some_feature 与 other_feature FORC_EXPERIMENTAL=some_feature,other_feature forc build # 只关闭 some_feature 与 other_feature FORC_NO_EXPERIMENTAL=some_feature,other_feature forc build # 同时使用两个变量,分别管理开启与关闭集合 FORC_EXPERIMENTAL=some_feature FORC_NO_EXPERIMENTAL=other_feature forc build实现上,parse_from_environment_variables会先读取FORC_NO_EXPERIMENTAL(按逗号切分并逐个禁用),再读取FORC_EXPERIMENTAL(按逗号切分并逐个启用)——这保证了两个变量同时存在时FORC_EXPERIMENTAL对同一 flag 拥有最终决定权。sway-features自带的单元测试 sway-features/src/lib.rs(ok_parse_experimental_features)就验证了这一行为:先设置FORC_EXPERIMENTAL=new_encoding与FORC_NO_EXPERIMENTAL=(空串),解析后new_encoding被启用;再交换两个变量的值,解析后new_encoding被禁用。这也提示了一个实用技巧:若需要"清空"某个方向的设置,把对应环境变量置为空串即可。
由于环境变量优先级最高,它特别适合 CI 流水线:无需改动仓库中的Forc.toml,即可在特定 job 中全局切换特性组合。
特性使用与错误处理
在启用特性后,代码中即可使用该特性对应的语法或语义。若代码用到了某个默认关闭且未在任何层开启的特性,编译器会直接报错。Feature枚举上的error_because_is_disabled方法生成对应的CompileError::FeatureIsDisabled,其错误消息定义在 sway-error/src/error.rs:
This needs "{feature}" to be enabled, but it is currently disabled. For more details go to {url}.
即错误信息会明确指出缺少哪个特性 flag,并附带该特性的 tracking issue 链接,便于你决定是开启特性、还是改用其他实现方式。
另外需要注意的是:实验性特性的合并结果是以"包"为单位计算的。forc-pkg/src/pkg.rs 的build在遍历编译计划中的每个包时,都会用该包自己的清单manifest.project.experimental与全局 CLI/环境变量合并出一份特性集合,因此工作区内不同包可以各自拥有不同的特性组合。
条件编译:#[cfg(experimental_<flag> = true/false)]
实验性特性还深度集成在 Sway 的条件编译机制中。Sway 的#[cfg(...)]属性支持三类条件(详见 docs/book/src/reference/attributes.md 中的 Cfg 一节):
#[cfg(target = "<target>")]:目标平台,取"evm"或"fuel";#[cfg(program_type = "<program_type>")]:程序类型,取"predicate"、"script"、"contract"或"library";#[cfg(experimental_<feature_flag> = true/false)]:实验性特性开关状态,其中<feature_flag>必须是已知的特性 flag。
对每个特性 flag,sway-features都会自动生成一个名为experimental_<feature_flag>的布尔型 cfg 参数(这正是Feature::CFG常量的内容)。被#[cfg]标注的代码元素仅在以下两种情况下参与编译:
- 编译时该特性已启用,且
experimental_<feature_flag>写为true; - 编译时该特性未启用,且
experimental_<feature_flag>写为false。
结合当前仓库的实际 flag,一个可运行的真实示例是:
// 仅当 new_hashing 特性启用时编译 #[cfg(experimental_new_hashing = true)] fn uses_new_hashing() { log("Compiled only when `new_hashing` is enabled."); } // 仅当 new_hashing 特性禁用时编译 #[cfg(experimental_new_hashing = false)] fn uses_new_hashing() { log("Compiled only when `new_hashing` is disabled."); }当条件依赖多个特性时,可以叠加多个#[cfg]属性——所有条件同时满足才会编译该代码元素:
#[cfg(experimental_some_feature = true)] fn conditionally_compiled() { log("This is compiled only if `some_feature` is enabled."); } #[cfg(experimental_some_feature = false)] fn conditionally_compiled() { log("This is compiled only if `some_feature` is disabled."); } #[cfg(experimental_some_feature = true)] #[cfg(experimental_some_other_feature = true)] fn conditionally_compiled() { log("This is compiled only if both `some_feature` and `some_other_feature` are enabled."); }上述三个同名函数利用#[cfg]实现了互斥与联合两种组合:前两个函数一真一假互斥编译,最后一个函数要求两个特性同时开启。#[cfg]判断发生在解析阶段,保证被排除的代码不会进入后续的类型检查与代码生成,因此未启用特性对应的#[cfg(false)]分支即使引用了尚未稳定的语法,也不会报错——这正是用条件编译为特性编写渐进式兼容代码的基础。
从实现看,条件编译判定逻辑位于 sway-core/src/transform/to_parsed_lang/convert_parse_tree.rs:当遇到experimental_*形式的 cfg 参数时,会调用ExperimentalFeatures::is_enabled_for_cfg查询该特性当前的启用状态(该方法会自动在特性名前面补上experimental_前缀,并处理未知名称),再与 cfg 参数中写明的布尔值比对,不一致即判定该代码元素不参与编译。同时,sway-core/src/transform/attribute.rs 通过Feature::CFG.contains(&self.name.as_str())识别哪些 cfg 参数名属于实验性特性,并为编译器补全所有已知的experimental_*参数列表。
优先级全景与典型使用场景
综合以上内容,把三级配置与条件编译串起来看,整个实验性特性机制可归纳如下:
| 配置层 | 语法 | 优先级 |
|---|---|---|
Forc.toml[project] experimental | experimental = { flag = true/false, ... } | 最低 |
forcCLI | --experimental f1,f2/--no-experimental f1,f2 | 中间 |
| 环境变量 | FORC_EXPERIMENTAL=f1,f2/FORC_NO_EXPERIMENTAL=f1,f2 | 最高 |
| 条件编译 | #[cfg(experimental_<flag> = true/false)] | 编译期反映上述合并结果 |
常见的使用场景包括:
- 新特性尝鲜:在
Forc.toml中把默认关闭的 flag(如dynamic_storage、str_array_no_padding)置为true,立即体验新语法; - 回归旧行为:遇到默认开启的 flag(如
new_encoding、new_hashing、references)引入的破坏性变更时,用--no-experimental或FORC_NO_EXPERIMENTAL在单次构建/整个流水线中退回旧行为,为迁移争取时间; - 按包差异化配置:利用"特性集合按包合并"的特性,让工作区内不同包分别启用不同 flag,互不干扰;
- 编写前后兼容库代码:用
#[cfg(experimental_<flag> = true/false)]在同一份源码中维护新旧两套实现,由使用方编译时的特性开关决定最终产物。
需要注意的是,实验性特性之所以"实验性",正因为它可能不稳定、可能引入破坏性变更。在升级 forc/Sway 编译器版本后,务必重新核对所用 flag 的默认值与 tracking issue 中的变更说明,必要时用forc build的报错信息(FeatureIsDisabled)定位缺失的特性开关。
关键文件索引
- 特性 flag 定义、默认值、合并优先级与 CLI/环境变量解析:sway-features/src/lib.rs
Forc.toml的Project结构体(含experimental字段解析):forc-pkg/src/manifest/mod.rs- 编译计划中按包合并特性集合的调用点:
forc-pkg/src/pkg.rs的build函数与ExperimentalFeatures::new调用(forc-pkg/src/pkg.rs) forc各子命令对--experimental/--no-experimental的接入:forc/src/cli/commands/build.rs、forc/src/cli/commands/check.rs、forc/src/cli/commands/test.rs#[cfg]条件编译的判定实现:sway-core/src/transform/to_parsed_lang/convert_parse_tree.rsFeatureIsDisabled编译错误消息定义:sway-error/src/error.rs- e2e 测试中通过
Forc.toml开启特性的真实示例:test/src/e2e_vm_tests/test_programs/should_pass/language/associated_const_in_decls_of_other_constants/test.dynamic_storage.toml
【免费下载链接】sway🌴 Empowering everyone to build reliable and efficient smart contracts.项目地址: https://gitcode.com/GitHub_Trending/sw/sway
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考