
Protobuf Editions 中的 Feature 扩展布局设计按生成器划分还是按运行时实现划分【免费下载链接】protobufProtocol Buffers - Googles data interchange format项目地址: https://gitcode.com/GitHub_Trending/pr/protobuf本文以 docs/design/editions/editions-feature-extension-layout.md 设计文档作者 mkruskal-google、zhangskz2023-08-23 批准为主体讲清 Protobuf Editions 项目中的一个关键设计决策全局featuresoption 的语言级扩展feature extensions到底应该归谁拥有——是归运行时实现C、upb、Java……还是归代码生成器protoc 插件。读完后你将理解四个备选方案各自的利弊、最终取舍背后的权衡逻辑并能在仓库源码descriptor.proto 的FeatureSet定义中看到这个决策的实际落地形态。背景谁该拥有这些 Feature 扩展What are Protobuf Editions 提出了用edition ...取代syntax ...、用可继承的features控制代码生成与运行时行为的总体计划并计划用全局 features proto 的扩展来让 protobuf 团队之外的各方定义自己关心的特性。但当时遗留了一个模糊点这些扩展的归属边界——语言language、代码生成器code generator、运行时实现runtime implementation三者相似却不等同一种语言如 Python可能有多种运行时实现纯 Python、Python/C、Python/upb一种运行时实现如 upb、C可能被多种语言共享Python、Rust、Ruby、PHP 都以它们为后端。触发这次讨论的具体案例是 Editions Zero Feature: utf8_validation该文档未对外发布但后来去掉争议选项的版本即仓库中的 edition-zero-features.md 所讨论的 UTF-8 校验特性。原本唯一的扩展特性legacy_closed_enumJava/C没有归属歧义而 utf8_validation 有在 Python 中proto2/proto3 下的当前行为对三种实现pure Python、Python/C、Python/upb各自不同。这正是扩展按谁划分问题的典型压力测试。两个让按运行时实现划分复杂化的现实原文档在 Overview 中列出会议里讨论出的两大复杂性Polyglot多语言混用upb 或 C 运行时在多语言场景下应遵循哪套 features这一歧义其实今天就已存在——所有 proto2 字符串以及大量 proto3 字符串在跨语言传输时本身就是不安全的。Shared Implementations共享实现upb 和 C 被用作多种语言的后端。如果只有一套upb或cpp级别的 features那么语言切换到这些共享实现时将更难迁移因为没有按语言独立的开关。同样这也是现状——今天切换运行时实现就可能带来微妙且危险的行为变化。文档给出的结论是既然当前只有两种行为且其中一种无歧义不如在 edition zero 推广过程中先搁置这个决定等收集到更多边界案例再说同时后续 edition 对 features 的重新建模自由度很大所以初始实现应保持最简单即下文备选方案 2。四种备选方案逐一对比方案一按运行时实现划分Runtime Implementation Features这是 Editions Zero Feature: utf8_validation 中的原始设想features 按运行时实现组织。例如 Protobuf Python 用户需要根据底层实现设置不同扩展features.(pb.cpp).feature或features.(pb.upb).feature。优点与 Editions 之前可表达的行为范围最一致。缺点底层实现往往对用户不透明用户甚至可能不知道自己在用哪个后端而且缺乏针对语言/实现组合的独立开关——例如无法独立设置Python-on-C的行为而不动 C 本身这会加大从其他 Python 实现迁移的难度。方案二按代码生成器划分Generator Features最终采纳方向features 只按生成器划分——每个 protoc 插件拥有一套自己的 features。这是团队在后续讨论中做出的第二个决定。它与方案一非常相似但更贴合features 主要服务于 codegen的目标。例如所有 Python 实现共享同一套 featuresfeatures.(pb.python).feature若某特性确实需要针对特定实现可以把特性名本身定向到该实现如features.(pb.python).upb_utf8_validation只会被 Python/upb 使用。优点允许对不同目标语言共享同一实现的情况做独立控制例如 Python 的 upb 特性不会影响 PHP。缺点upb 需要理解自己应该遵循哪门语言的特征而 upb 目前并不知道自己是给哪门语言服务的在行为冲突时共享实现的跨语言进程内共享如 Python-upb 与 PHP-upb 同进程会受到限制可能还需要额外的检查。方案三迁移到 bytesMigrate to bytes既然争议围绕 utf8 校验干脆不在 edition zero 引入这个开关把目前不强制 UTF-8 校验的字段全部迁移为bytes。这大概率需要一个新的代码生成特性来把 bytes 的 getter/setter 生成为字符串 API但不存在当前看到的归属歧义。文档认为此路不可行utf8 校验并不是简单的开/关二值决策它在语言之间差异很大——很多情况下 UTF-8 在部分语言中校验、在另一些语言中不校验还有 C 那种只记日志但放行非法 UTF-8的 hint 行为。文档同时留了个口子可以在后续 LSClarge-scale change中通过定向禁用所有相关语言校验的特性组合部分实现该思路。优点回避问题不需要任何 upb 特性C 特性全部退化为纯代码生成特性避免在 edition zero 引入一个非常复杂的特性。缺点以当前复杂度看基本做不到会有O(10M) 量级的 proto2 string 字段被盲目改为 bytes。方案四嵌套特性Nested Features允许共享的 feature set 消息upb 定义自己的 feature 消息但不把它作为全局FeatureSet的扩展使用 upb 实现的语言在自己的 feature 里内嵌一个该类型的字段实现更细粒度的控制。C 则既扩展全局FeatureSet也允许作为其他语言的字段出现。此外可在特性校验阶段加入检查强制不可能的组合不被指定——例如在当前实现下features.(pb.python).cpp必须与features.(pb.cpp)恒等因为没有机制区分二者。优点比方案一、二更显式。缺点可能过度显式——proto 属主被迫大量复制duplicate特性声明。决策逻辑信息不足时选最简单的模型文档 Overview 的结论值得单独强调只有两种行为、且其中一种无歧义此时强行选定按实现划分还是按生成器划分的归属模型风险大于收益。与其在 edition zero 就定死复杂的所有权结构不如让初始实现保持简单备选方案 2按生成器划分把按实现定向留作特性命名层面如upb_utf8_validation这样的特征名的柔性能力待推广期积累更多边界案例后再决定是否升级建模方式。这一决策也呼应了 what-are-protobuf-editions.md 中codegen backends own the definitions of their features的总体原则——特性定义权归各语言后端而归属单位是生成器。仓库源码印证FeatureSet扩展声明就是按生成器划分设计文档讨论的抽象问题在当前仓库的 src/google/protobuf/descriptor.proto 中有明确的落地形态可以作为事实核对。1. 每个生成器独占一个FeatureSet扩展号FeatureSet消息descriptor.proto 处定义末尾用extension_range显式登记了已分配的语言级扩展其编号即按生成器划分的直接证据extensions 1000 to 9994 [ declaration { number: 1000, full_name: .pb.cpp, type: .pb.CppFeatures }, declaration { number: 1001, full_name: .pb.java, type: .pb.JavaFeatures }, declaration { number: 1002, full_name: .pb.go, type: .pb.GoFeatures }, declaration { number: 1003, full_name: .pb.python, type: .pb.PythonFeatures }, declaration { number: 1004, full_name: .pb.csharp, type: .pb.CSharpFeatures }, declaration { number: 1100, full_name: .imp.impress_feature_set, type: .imp.ImpressFeatureSet }, declaration { number: 9989, full_name: .pb.java_mutable, type: .pb.JavaMutableFeatures }, declaration { number: 9990, full_name: .pb.proto1, type: .pb.Proto1Features } ]; extensions 9995 to 9999; // For internal testing extensions 10000; // for https://github.com/bufbuild/protobuf-es这段声明印证了文档结论的几个要点没有pb.upb、pb.cpp_impl这类按运行时实现的顶层扩展只有按语言/生成器命名的.pb.cpp、.pb.java、.pb.python……——这正是备选方案 2 的形态.pb.java_mutableJavaMutableFeatures扩展号 9989作为独立的生成器扩展存在而非嵌套在JavaFeatures里的字段——从源码结构看面向特定实现的变体被建模为另一个生成器级别的扩展而不是方案 4 的嵌套特性消息保留 9995–9999 给内部测试、10000 给第三方生成器bufbuild/protobuf-es说明这套编号空间是留给各 codegen 自持特性的开放注册表。各生成器的特性消息定义在独立文件中例如 src/google/protobuf/cpp_features.proto配套生成的cpp_features.pb.h/cc同目录与extensions声明中.pb.CppFeatures一一对应。2. 触发讨论的utf8_validation特性长什么样FeatureSet中的utf8_validation字段紧随repeated_field_encoding之后定义展示了该特性在去掉争议选项后的最终形态enum Utf8Validation { UTF8_VALIDATION_UNKNOWN 0; VERIFY 2; NONE 3; reserved 1; } optional Utf8Validation utf8_validation 4 [ retention RETENTION_RUNTIME, targets TARGET_TYPE_FIELD, targets TARGET_TYPE_FILE, feature_support { edition_introduced: EDITION_2023, }, edition_defaults { edition: EDITION_LEGACY, value: NONE }, edition_defaults { edition: EDITION_PROTO3, value: VERIFY } ];几点与设计文档的呼应retention RETENTION_RUNTIME特性会被序列化进 descriptor 供运行时使用即它确实横跨 codegen 与 runtime这正是归属需要慎重设计的原因edition_defaults显式区分了EDITION_LEGACYproto2 时代默认NONE与EDITION_PROTO3默认VERIFY把现状不一致固化成按 edition 的默认值而不是在扩展布局上分叉reserved 1说明早期曾存在第三个枚举值后被移除——与文档提及的问题选项problematic options修订过程一致。3. 特性解析在哪里发生从源码结构看仓库中src/google/protobuf/下存在feature_resolver.h/.cc及对应测试feature_resolver_test.cc承担把 edition 默认值与显式声明合并、完成特性继承解析的工作FeatureSetDefaultsdescriptor.proto 处定义则携带每个 edition 的默认特性集合让解析退化为找最近的匹配 edition 再做 proto 合并。特性如何被各生成器/运行时消费可继续参见 docs/design/editions/editions-life-of-a-featureset.md 的完整流程描述。对实践者的启示写 editions proto 时扩展命名空间按你使用哪个生成器选择用 C codegen 就写features.(pb.cpp).x用 Python codegen 就写features.(pb.python).x无论底层是不是 upb——这与文档所有 Python 实现共享同一套 features的设计一致。实现级差异用特性名表达而不是新扩展需要区分后端时参照features.(pb.python).upb_utf8_validation的命名思路在自己拥有的生成器扩展内加定向特性。理解共享实现的边界以 upb/C 为后端的多种语言之间行为可能受同一实现、不同语言 features的冲突约束文档方案 2 的 Cons同进程多语言混用时值得额外验证序列化/校验行为。迁移语义依赖 edition 默认值而非扩展布局utf8_validation等特性的 edition 默认值proto2→NONE、proto3→VERIFY保证了 proto2/proto3 向 editions 的机械迁移在语义上是 no-op这也是整个 feature 布局设计要服务的最终目标。延伸阅读均在当前仓库内docs/design/editions/what-are-protobuf-editions.mdEditions 总纲features/editions 的基本概念与生命周期。docs/design/editions/edition-zero-features.mdedition zero 首批特性的定义其中string_field_validationMANDATORY/HINT/NONE正是本文主题讨论的前身后被utf8_validation取代。docs/design/editions/editions-life-of-a-featureset.md一个 FeatureSet 从文件到生成代码的完整旅程。docs/design/editions/README.mdeditions 设计文档索引。src/google/protobuf/descriptor.protoFeatureSet、FeatureSetDefaults的权威定义。【免费下载链接】protobufProtocol Buffers - Googles data interchange format项目地址: https://gitcode.com/GitHub_Trending/pr/protobuf创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考