首页 / 资讯中心 / 文章详情

Rust RFC 3399 深度解析:用 cfg 属性在 where 子句中优雅实现条件编译 Trait Bound

Rust RFC 3399 深度解析:用 cfg 属性在 where 子句中优雅实现条件编译 Trait Bound ★ FEATURED ARTICLE
文档【免费下载链接】rfcsRFCs for changes to Rust项目地址https://gitcode.com/gh_mirrors/rf/rfcs点击查看免费下载在 Rust 中按目标平台或 feature 开关条件化编译 trait 约束长期依赖尖括号内的#[cfg]拆分或 dummy trait 样板代码代码量与维护成本会随条件数量成倍膨胀。本篇文章围绕 RFC 仓库中的提案文档 text/3399-cfg-attribute-in-where.md 展开系统讲解该提案如何允许在where子句的单个 bound 上直接书写#[cfg(...)]从动机、语法设计、编译语义到与既有替代方案的对比帮助读者掌握在impl与函数签名中按条件精准开关 trait bound 的写法并理解这一语言层设计的边界与代价。提案概览为 where 子句引入 cfg 属性该 RFCFeature Name 为cfg_attribute_in_where起始日期 2023-03-11的核心主张非常直接让条件编译 trait bounds 这件事变得更优雅——允许 cfg 属性直接出现在 where 子句中。正如文档 Summary 所言Lets make it more elegant to conditionally compile trait bounds by allowing cfg-attributes directly in where clauses.也就是说与其绕道尖括号泛型参数、dummy trait 或宏不如让#[cfg(...)]以与 struct 字段、函数签名一致的惯用法直接修饰 where 子句中的每一条 bound。这样既能精确控制单条约束又能保持代码意图与书写位置一一对应。现状之痛条件编译 Trait Bound 为何如此笨拙理解这个提案的价值需要先看清它试图解决的问题。RFC 的 Motivation 部分指出Rust 目前对条件编译 trait bound的支持非常有限已有的几种写法都谈不上顺手。尖括号 bound 中的 cfg可行但组合爆炸Rust 已经允许在尖括号angle-bracketed泛型参数列表中使用 cfg 属性因此下面的写法在语法上是可行的impl #[cfg(something)] T: SomeRequirement, #[cfg(not(something))] T SomeTraitT for Thing {}但它既不美观而且在存在多个相互独立的编译条件/约束对时呈组合式膨胀grows combinatorically。每多一个独立条件泛型参数列表就要多拆出一组分支代码规模随条件数量指数增长。更关键的是这种写法无法用于 associated type 或其他更复杂的左侧项——那些只能出现在完整 where bound 中的约束根本没法塞进尖括号列表里。这正是本提案的切入点之一。dummy trait 迂回方案N 越大越糊另一种常见做法是构造一个代理 traitdummy trait让它按条件分叉后实现之再让目标 trait 依赖这个 dummy。单个条件时看起来还算可控#[cfg(something)] trait Dummy: SomeRequirement {} #[cfg(something)] implT: SomeRequirement Dummy for T {} #[cfg(not(something))] trait Dummy {} #[cfg(not(something))] implT Dummy for T {} implT: Dummy SomeTraitT for Thing {}然而 RFC 明确指出这种样板代码在多个条件化需求面前并不生长得更好does not grow well即便只有两个条件N 2代码就已经相当糊rather soupy#[cfg(something_a)] trait DummyA: SomeRequirementA {} #[cfg(something_a)] implT: SomeRequirementA DummyA for T {} #[cfg(not(something_a))] trait DummyA {} #[cfg(not(something_a))] implT DummyA for T {} #[cfg(something_b)] trait DummyB: SomeRequirementB {} #[cfg(something_b)] implT: SomeRequirementB DummyB for T {} #[cfg(not(something_b))] trait DummyB {} #[cfg(not(something_b))] implT DummyB for T {} implT: DummyA DummyB SomeTraitT for Thing {}其他替代路径同样不理想RFC 还提到其他实现这一目标的方式通常宏味过重、难以实现或难以检查typically macro heavy and difficult to implement or check。也就是说该功能在语言中其实已经存在但缺乏一个符合人体工学的承载形式——一旦条件数量稍多就迅速超出合理可控的范围。这一点构成了本提案最核心的动因。提案设计在 where 子句中直接书写 cfg 属性基本用法impl 上的条件 boundGuide-level explanation 部分给出最直观的形态where子句可以在每一条 trait bound 上使用 cfg 属性implT SomeTraitT for Thing where #[cfg(something_a)] T: SomeRequirementA, #[cfg(something_b)] T: SomeRequirementB, {}这里something_a、something_b可以是任意 cfg 条件例如feature client、target_os windows等。编译时条件为真的 bound 保留为假的 bound 被剥离从而实现同一份 impl 代码不同编译配置下施加不同约束。函数签名单条 bound 可叠加多个 cfg 属性同样的语法也适用于函数签名并且单条 bound 上可以叠加多个 cfg 属性fn some_functionT(val: T) where #[cfg(something_a)] T: SomeRequirementA, #[cfg(something_b)] #[cfg(not(something_a))] #[cfg(target_os(some_os))] T: SomeRequirementB, {}注意第二条 bound 上叠了三个属性something_b、not(something_a)与target_os(some_os)构成与的关系——只有三者同时为真时T: SomeRequirementB才会生效。这也是 cfg 属性在 Rust 中一贯的求值语义同一 item 上的多个#[cfg(...)]需全部匹配才保留该 item这一语义在 text/0194-cfg-syntax.md 中有正式定义。除了impl与函数签名其他适用 where 子句的场合如 trait 定义、类型别名等同样可以使用。编译语义所有 cfg 属性逐一求值文档对行为语义给出明确描述编译期间where bound 上的所有cfg 属性都会被求值若求值结果为假则该条 bound 不会被编译约束对该类型也不生效。这里有一个重要的连带后果依赖这些被剥离约束的代码本身也必须做同样的条件编译否则会因为缺少 trait bound 而报编译错误。RFC 特别指出凡是熟悉 cfg 属性的开发者对此应该不陌生——它与 struct 字段、函数签名上的 cfg 用法行为一致。从更广的视角看cfg 机制在 Rust 中本就承担这类职责。仓库中的 text/3013-conditional-compilation-checking.md 归纳了条件编译的三种入口#[cfg(c)]属性、#[cfg_attr(c, attr)]属性以及cfg!(c)内置宏本文讨论的 where bound 上的 cfg 属于第一种形态在语法位置上的扩展。此外rustc还提供可选的--check-cfg编译期检查用来校验条件名与取值是否存在拼写错误这为大规模代码库中大量使用 cfg 提供了纠错保障。参考级语义语法的精确边界Reference-level explanation 给出了实现层面的精确约束值得逐条细读适用范围所有接受 where 子句的位置trait 实现、函数签名等都可以让单条 clause 携带 cfg 属性。属性位置cfg 属性必须写在冒号左侧即#[cfg(...)] T: Foo而不是T: #[cfg(...)] Foo。作用范围属性只作用于它修饰的那一条 bound范围到下一个逗号或 where 段结束为止。空 where 的合法性条件求值后可能使得整段 where 子句在编译时没有任何 bound 条目即出现空 where 子句。文档明确说明这在 Rust 中自1.16 起就是合法的且在使用宏的既有代码中已经时有发生因此无需担心这一边界情况。要深入理解#[cfg(...)]内部的条件表达式可以参考仓库中定义 cfg 语法的原始 RFC text/0194-cfg-syntax.md。该文档将#[cfg(p)]中的p定义为递归的cfg pattern共五种形式cfg pattern 形式匹配语义key编译环境中存在该key时匹配如#[cfg(test)]、#[cfg(linux)]key value编译环境中存在该 key 到 value 的映射时匹配如#[cfg(target_os linux)]not(p)内部 patternp不匹配时匹配all(p, ...)所有列出的 pattern 都匹配时才匹配any(p, ...)任一列出的 pattern 匹配即匹配其中 key-value 形式目前主要用于编译器预定义的键如target_os、endian等。当一个 item 带有#[cfg(p)]且 pattern 不匹配时该 item 会从 AST 中被剥离。这里的item 剥离语义与本 RFC 中bound 被剥离的处理方式是同一套机制在更细粒度上的复用——这也解释了为什么本提案在实现上可以被视为语法层grammar level的扩展而不是类型系统层面的新规则。权衡与代价DrawbacksRFC 并未回避引入该特性的成本列出了两点主要顾虑语言与文法复杂度增加与任何新特性一样允许在 where 子句中使用 cfg 属性会给 Rust 的语言和语法grammar增加一层复杂性。条件化约束的连带影响一般来说条件编译的 trait bound 可能基于编译目标或 feature 组合产生非预期的交互或约束——比如上文提到的依赖被剥离约束的代码也必须同步条件编译。不过文档也强调这些 drawback在既有的各种 workaround 中已经存在本提案并没有引入新的问题类别只是把解决同一需求的手段变得更直接。为什么值得做Rationale 与替代方案对比RFC 的核心论点在于该功能在 Rust 中已经可以做到但不够优雅且写出的代码与代码的意图之间缺乏清晰关联without a clear relationship between the written code and its intent。提案逐一对主要替代方案做了对比对比 dummy trait在 where 子句中直接写 cfg 属性使意图立刻局部化immediately local与它所控制的代码直接关联而不是把约束隔空委托给一个代理 trait。对比尖括号内的 cfgwhere 子句中的 cfg 让每条 bound 可以被单独开关无需为多个条件枚举组合式分支同时也能覆盖尖括号写法无法表达的、带复杂 item path 的 bound如 associated type 约束。对比宏 / proc macro宏也能条件化 where 子句但至少引入一层对核心目标的遮蔽obfuscation。对比整体复制 trait把整个 trait 在多个 cfg 条件下各复制一份会随 trait 的规模、复杂度和交互属性数量可能组合式增长而难以扩展并带来重复代码的维护负担。至于适用场景RFC 列举了两类典型需求拥有不同部署目标的应用以及希望发布不同功能集合的构建如 client、server、editor、demo 等变体。这类项目正是条件编译约束的高频用户也是本特性最大的受益者。先例参考Prior artRFC 作者明确表示没有发现语言层添加该特性的直接先例。在带预处理器的语言如 C/C中可以用宏条件指令实现类似效果implT SomeTraitT for Thing where #ifdef SOMETHING_A T: SomeRequirementA #endif {}但作者随即补充这不是 Rust 提供此类功能的预期方式——Rust 以属性而非文本预处理器来承载条件编译本提案正是沿着这一既有范式延伸。未决问题与未来方向两个未决问题Unresolved questions冒号右侧的 cfg 属性是否值得支持理论上在冒号右侧T: #[cfg(...)] Foo装饰单条 bound 也无害。但作者个人倾向不支持同一个 item 本就可以声明多条独立 bound 来达到同样效果而且这种写法在尖括号 bounds 中本来就不被允许支持右侧会制造不一致inconsistency。rustfmt 如何格式化rustfmt 需要对 where 子句进行格式化。问题在于它应该尝试把属性放在同一行还是始终把属性放在单独的行这需要在 rustfmt 侧进一步明确格式化策略。未来可能性Future possibilities条件化 where bound 未来还可服务于trivial bounds即形如T:的空约束。RFC 认为此处不需要任何额外支持因为条件编译发生在语法层面grammar level而非类型层面type level——属性剥离的是 bound 的语法条目类型检查器根本不会看到被剥掉的内容因此 trivial bound 的条件化自然随之成立。在 RFC 仓库中查阅与延伸提案原文text/3399-cfg-attribute-in-where.mdcfg 语法基础cfg pattern 递归定义、all/any/not、AST 剥离语义text/0194-cfg-syntax.md条件编译的编译期检查--check-cfg与三种 cfg 使用方式text/3013-conditional-compilation-checking.mdrustdoc 对条件化 item 的文档呈现doc(cfg)与doc(auto_cfg)text/3631-rustdoc-cfgs-handling.mdRFC 流程与生命周期总览README.md若想了解 Rust RFC 的标准撰写结构Summary、Motivation、Guide-level explanation、Reference-level explanation、Drawbacks、Rationale and alternatives 等章节的由来可对照仓库中的 text/0000-template.md。总体而言RFC 3399 描绘的是一条低侵入、高回报的语言演进路径把早已存在于 cfg 机制中的求值逻辑下沉到 where 子句这个高频书写位置让按条件开关 trait bound从一段段绕路样板回归为一句就地声明。赞分享文档【免费下载链接】rfcsRFCs for changes to Rust项目地址https://gitcode.com/gh_mirrors/rf/rfcs点击查看免费下载相关推荐Rust By Example深入解析 cfg 条件编译属性与 cfg! 宏Rust By Example深入解析 cfg 条件编译属性与 cfg! 宏 cfg 是 Rust 中实现 条件编译 的核心机制它允许同一份源码根据目标平台文档教程Rust 条件编译语法演进从扁平 cfg 列表到递归 cfg patternRFC 194 深度解析Rust 条件编译语法演进从扁平 cfg 列表到递归 cfg patternRFC 194 深度解析 cfg ... 是 Rust 条件编译的基石允许开文档Rust RFC 1647 详解让 Self 类型合法出现在 trait impl 的 where 子句与头部各位置Rust RFC 1647 详解让 Self 类型合法出现在 trait impl 的 where 子句与头部各位置 导读 RFC 1647 https://文档上一篇React Native Background Job 性能优化终极指南如何减少电池消耗并提高任务执行效率 下一篇【亲测免费】 推荐文章将Excel转换为JSON的强大工具——convert-excel-to-json创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
阅读完成 · 觉得有帮助?
咨询建站