• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1# Editions: Life of a FeatureSet
2
3**Author:** [@mkruskal-google](https://github.com/mkruskal-google)
4
5**Approved:** 2023-08-17
6
7## Background
8
9Outside of some minor spelling tweaks, our current implementation of features
10has very closely followed the original design laid out in
11[Protobuf Editions Design: Features](protobuf-editions-design-features.md). This
12approach led to the creation of four different feature sets for each descriptor
13though, and it's left under-specified who is responsible for generating these
14(protoc, plugins, runtimes), who has access to them, and where they need to be
15propagated to.
16
17*Exposing Editions Feature Sets* (not available externally) was a first attempt
18to try to define some of these concepts. It locks down feature visibility to
19protoc, generators, and runtimes. Users will only be exposed to them indirectly,
20via codegen changes or runtime helper functions, in order to avoid Hyrum's law
21cementing every decision we make about them. We (incorrectly) assumed that the
22protoc frontend would be able to calculate all the feature sets and then
23propagate all four sets to the generators, who would then forward the fully
24resolved runtime features to the runtime. This had the added benefit that we
25could treat our C++ feature resolution logic as a source-of-truth and didn't
26have to reimplement it identically in every language we support.
27
28*Editions: Runtime Feature Set Defaults* (not available externally) was a
29follow-up attempt to specifically handle the default feature sets of an edition.
30We had realized that we would need proto2/proto3 default features in each
31language to safely roll out editions, and that languages supporting descriptor
32pools would have cases that bypass protoc entirely. The solution we arrived at
33was that we should continue using the protoc frontend as the source-of-truth,
34and propagate these defaults down to the necessary runtimes. This would fix the
35proto2/proto3 issue, and at least provide some utilities to make the situation
36easier for descriptor pool users.
37
38[Protobuf Editions Design: Features](protobuf-editions-design-features.md)
39defines the feature resolution algorithm, which can be summarized by the
40following diagram:
41
42![Feature resolution diagram](./images/editions-life-of-a-featureset-image-01.png)
43
44Feature resolution for a given descriptor starts by using the proto file's
45edition and the feature schemas to generate the default feature set. It then
46merges all of the parent features from top to bottom, merging the descriptor's
47features last.
48
49## Glossary
50
51We will be discussing features **a lot** in this document, but the meaning
52behind the word can vary in some subtle ways depending on context. Whenever it's
53ambiguous, we will stick to qualifying these according to the following
54definitions:
55
56*   **Global features** - The features contained directly in `FeatureSet` as
57    fields. These apply to the protobuf language itself, rather than any
58    particular runtime or generator.
59
60*   **Generator features** - Extensions of `FeatureSet` owned by a specific
61    runtime or generator.
62
63*   **Feature resolution** - The process of applying the algorithm laid out in
64    [Protobuf Editions Design: Features](protobuf-editions-design-features.md).
65    This means that edition defaults, parent features, and overrides have all
66    been merged together. After resolution, every feature should have an
67    explicit value.
68
69    *   **Unresolved features** - The features a user has explicitly set on
70        their descriptors in the `.proto` file. These have not gone through
71        feature resolution and are a minimal representation that require more
72        knowledge to be useful.
73
74    *   **Resolved features** - Features that have gone through feature
75        resolution, with defaults and inheritance applied. These are the only
76        feature sets that should be used to make decisions.
77
78*   **Option Retention** - We support a retention specification on all options
79    (see
80    [here](https://protobuf.dev/programming-guides/proto3#option-retention)),
81    including features
82
83    *   **Source features** - The features available to protoc and generators,
84        before option retention has been applied. These can be either resolved
85        or unresolved.
86
87    *   **Runtime features** - The features available to runtimes after option
88        retention has been applied. These can be either resolved or unresolved.
89
90## Problem Description
91
92The flaw that all of these design documents suffer from is that protoc **can't**
93be the universal source-of-truth for feature resolution under the original
94design. For global features, there's of course no issue (protoc has a
95bootstrapping setup for `descriptor.proto`` and always knows the global feature
96set). For generator features though, we depend on [imports to make them
97discoverable](protobuf-editions-design-features.md#specification-of-an-edition).
98
99If a user is actually overriding one of these features, there will necessarily
100be an import and therefore protoc will be able to discover generator features
101and handle resolution. However, if the user is ok with the edition defaults
102there's no need for an import. Without the import, protoc has **no way of
103knowing** that those generator features exist in general. We could hardcode the
104ones we own, but that just pushes the problem off to third-party plugins. We
105could also force proto owners to include imports for *every* (transitive)
106language they generate code to, even if they're unused, but that would be very
107disruptive and isn't practical or idiomatic.
108
109Pushing the source-of-truth to the generators makes things a little better,
110since they each know exactly what feature file needs to be included. There's no
111longer any knowledge gap, and we don't need to rely on imports to discover the
112feature extension. Additionally, many of our generators are written in C++ (even
113non-built-in plugins), so we could at least reuse our existing feature
114resolution utility for all of those and limit the amount of duplication
115necessary. However, there's still a code-size issue with this approach. As
116described in the previous documents, we would need to send four feature sets for
117**every** descriptor to the runtime (i.e. in the generator request and embedded
118as a serialized string). We wouldn't be able to use inheritance or references to
119minimize the cost, and every generator that embeds a `FileDescriptorProto` into
120its gencode would see a massive code-size increase.
121
122There's also still the issue of descriptor pools that need to be able to build
123descriptors at runtime. These are typically power users (and our own unit-tests)
124doing very atypical things and bypassing protoc entirely. In previous documents
125we've attempted to push some of the cost onto them by explicitly not giving them
126feature resolution. They would have to specify every feature on every
127descriptor, and would not be able to use edition defaults or inheritance.
128However, this cost is fairly high and it also makes the `edition` field
129meaningless. Any missing feature would be a runtime error, and there would be no
130concept of "edition". This creates an inconsistent experience for developers,
131where they think in terms of editions in one context and then throw it out in
132another. Also, it would mean that we have two distinct ways of specifying a
133`FileDescriptorProto``: with unresolved features meant to only go through
134protoc, and with fully resolved features meant to always bypass protoc.
135Round-tripping descriptors would become difficult or impossible.
136
137The following image attempts illustrates the issue:
138
139![Diagram showing two generators and two runtimes](./images/editions-life-of-a-featureset-image-03.png)
140
141Here, a proto file is used in both A and B runtimes. The schema itself only
142overrides features for A though, and doesn't declare an import on B's features.
143This means that protoc doesn't know about B's features, and Generator B will
144need to resolve them. Additionally, dynamic messages in both A and B runtimes
145have issues because they've bypassed protoc and don't have any way to follow the
146feature resolution spec.
147
148### Requirements
149
150The following minimal feature sets are required by protoc:
151
152*   **Resolved global source features** - to make proto-level decisions
153*   **Unresolved global source features** - for validation
154
155For each generator:
156
157*   **Resolved generator source features** - to make language-specific codegen
158    decisions
159*   **Unresolved generator source features** - for validation
160*   **Resolved global source features** - to make more complex decisions
161
162For each runtime:
163
164*   **All resolved runtime features** - for making runtime decisions
165*   **All unresolved runtime features** - for round-trip behavior and debugging
166
167With some additional requirements on an ideal solution:
168
169*   **Minimal code-size costs** - code size bloat can easily block the rollout
170    of editions, and once those limits are hit we don't have great solutions
171
172*   **Minimal performance costs** - we want a solution that avoids any
173    unnecessary CPU or RAM regressions
174
175*   **Minimal code duplication** - obviously we want to minimize this, but where
176    we can't, we need a suitable test strategy to keep the duplication in sync
177
178*   **Runtime support for dynamic messages** - while dynamic messages are a
179    less-frequently-used feature, they are a critical feature used by a lot of
180    important systems. Our solution should avoid making them harder to use in
181    any runtime that supports them.
182
183## Recommended Solution
184
185Our long-term recommendation here is to support and use feature resolution in
186every stage in the life of a FeatureSet. Every runtime, generator, and protoc
187itself will all handle feature resolution independently, only sharing unresolved
188features between each other. This will necessarily mean duplication across
189nearly every language we support, and the following sections will go into detail
190about strategies for managing this.
191
192The main justification for this duplication is the simple fact that *edition
193defaults* will be needed almost everywhere. The generators need defaults for
194*their* features to get fully resolved generator features to make decisions on,
195and can't get them from protoc in every case. The runtimes need defaults for
196both global and generator features in order to honor editions in dynamic
197messages and to keep RAM costs down (e.g. the absence of feature overrides
198should result in a reference to some shared default object). Since the
199calculation of edition defaults is by far the most complicated piece of feature
200resolution, with the remainder just being proto merges, it makes everything
201simpler to understand if we just duplicate the entire algorithm.
202
203#### Pros
204
205*   Resolved feature sets will never be publicly exposed
206
207    *   Our APIs will be significantly simpler, cutting the number of different
208        types of feature sets by a factor of 2
209
210    *   There will be no ambiguity about what a `FeatureSet` object *means*. It
211        will always either be unresolved (outside of protobuf code) or fully
212        resolved on all accessible features (inside protobuf code).
213
214    *   RAM and code-size costs will be minimal, since we'll only be storing and
215        propagating the minimal amount of information (unresolved features)
216
217    *   Combats Hyrum's law by allowing us to provide wrappers around resolved
218        features everywhere, instead of letting people depend on them directly
219
220*   **Minimal** duplication on top of what's already necessary (edition
221    defaults).
222
223*   Dynamic messages will be treated on equal footing to proto files
224
225*   The necessary feature dependencies will always be available in the
226    appropriate context
227
228*   We can simplify the current implementation since protoc won't need to handle
229    resolution of imported features.
230
231#### Cons
232
233*   Requires duplication of feature resolution in every runtime and every unique
234    generator language
235
236    *   This means building out additional infrastructure to enforce
237        cross-language conformance
238
239### Runtimes Without Reflection
240
241There are various runtimes that do not support reflection or dynamic messages at
242all (e.g. Java lite, ObjC). They typically embed the "feature-like" information
243they need directly into custom objects in the gencode. In these cases, the
244problem becomes a lot simpler because they *don't need* the full FeatureSet
245objects. We **don't** need to duplicate feature resolution in the runtime, and
246the generator can just directly embed the fully resolved features values needed
247by the runtime (of course, the generator might still need duplicate logic to get
248those).
249
250### Staged Rollout for Dynamic Messages
251
252Long-term, we want to be able to handle feature resolution at run-time for any
253runtime that supports reflection (and therefore needs FeatureSet objects) to
254reduce code-size/RAM costs and support dynamic messages. However, in any
255language where these costs are less critical, a staged rollout could be
256appropriate. Here, the generator would embed the serialized resolved source
257features into the gencode along with the rest of the options. We would use the
258`raw_features` field (which should eventually be deleted) to also include the
259unresolved features for reflection.
260
261This would allow us to implement and test editions, and unblock the migration of
262all non-dynamic cases. A follow-up optimization at a later stage could push this
263down the runtime, and only embed unresolved features in the gencode.
264
265Under this scenario, dynamic messages could still allow editions, as long as
266fully-resolved features were provided on every descriptor. When we do implement
267feature resolution, it will just be a matter of deleting redundant/unnecessary
268features, but there should always be a valid transformation from fully-resolved
269features to unresolved ones.
270
271### C++ Generators
272
273Generators written in C++ are in a better position since they don't require any
274code duplication. They could be given visibility to our existing feature
275resolution utility to resolve the features themselves. However, a better
276alternative is to make improvements to this utility so that some helpers like
277the ones we proposed in *Exposing Editions Feature Sets* can be used to access
278the resolved features that *already exist*.
279
280Protoc works by first parsing the input protofiles and building them into a
281descriptor pool. This is the frontend pass, where only the global features are
282needed. For built-in languages, the resulting descriptors are passed directly to
283the generator for codegen. For plugins, they're serialized into descriptor
284protos, rebuilt in a new descriptor pool (in the generator process), and then
285sent to the generator code for codegen. In both of these cases, a
286`DescriptorPool` build of the protos is done from a binary that *necessarily*
287links in the relevant generator features.
288
289Today, we discover features in the pool which are imported by the protos being
290built. This has the hole we mentioned above where non-imported features can't be
291discovered. Instead, we will pivot to a more explicit strategy for discovering
292features. By default, `DescriptorPool` will only resolve the global features and
293the C++ features (since this is the C++ runtime). A new method will be added to
294`DescriptorPool` that allows new feature sets to replace the C++ features for
295feature resolution. Generators will register their features via a virtual method
296in `CodeGenerator` and the generator's pool build will take those into account
297during feature resolution.
298
299There are a few ways to actually define this registration, which we'll leave as
300implementation details. Some examples that we're considering include:
301
302*   Have the generator provide its own `DescriptorPool` containing the relevant
303    feature sets
304*   Have the generator provide a mapping of edition -> default `FeatureSet`
305    objects
306
307Expanding on previous designs, we will provide the following API to C++
308generators via the `CodeGenerator` class:
309
310They will have access to all the fully-resolved feature sets of any descriptor
311for making codegen decisions, and they will have access to their own unresolved
312generator features for validation. The `FileDescriptor::CopyTo` method will
313continue to output unresolved runtime features, which will become unresolved
314source features after option retention stripping (which generators should
315already be doing), for embedding in the gencode for runtime use.
316
317#### Example
318
319As an example, let's look at some hypothetical language `lang` and how it would
320introduce its own features. First, if it needs features at runtime it would
321create a `lang_features.proto` file in its runtime directory and bootstrap the
322gencode the same as it does for `descriptor.proto`. It would then *also*
323bootstrap C++ gencode using a special C++-only build of protoc. This can be
324illustrated in the following diagram:
325
326![Diagram showing how a language introduces its own features](./images/editions-life-of-a-featureset-image-04.png)
327
328This illustrates the bootstrapping setup for a built-in C++ generator. If
329generator features weren't needed in the runtime, that red box would disappear.
330If this were a separate plugin, the "plugin" box would simply be moved out of
331`protoc` and `protoc` could also serve as `protoc_cpp`.
332
333If `lang` didn't need runtime features, we would simply put the features proto
334in the `lang` generator and only generate C++ code (using the same bootstrapping
335technique as above).
336
337After the generator registers `lang_features.proto` with the DescriptorPool, the
338`FeatureSet` objects returned by `GetFeatures` will always have fully resolved
339`lang` features.
340
341### Non-C++ Generators
342
343As we've shown above, non-C++ generators are already in a situation where they'd
344need to duplicate *some* of the feature resolution logic. With this solution,
345they'd need to duplicate much more of it. The `GeneratorRequest` from protoc
346will provide the full set of *unresolved* features, which they will need to
347resolve and apply retention stripping to.
348
349**Note:** If we're able to implement bidirectional plugin communication, the
350[Bidirectional Plugins](#bidirectional-plugins) alternative may be a simpler
351solution for non-C++ generators that *don't* need features at runtime. Ones that
352need it at runtime will need to reimplement feature resolution anyway, so it may
353be less useful.
354
355One of the trickier pieces of the resolution logic is the calculation of edition
356defaults, which requires a lot of reflection. One of the ideas mentioned above
357in [C++ Generators](#c++-generators) could actually be repurposed to avoid
358duplication of this in non-C++ generators as well. The basic idea is that we
359start by defining a proto:
360
361```
362message EditionFeatureDefaults {
363  message FeatureDefaults {
364    string edition = 1;
365    FeatureSet defaults = 2;
366  }
367  repeated FeatureDefaults defaults = 1;
368  string minimum_edition = 2;
369  string maximum_edition = 3;
370}
371```
372
373This can be filled from any feature set extension to provide a much more usable
374specification of defaults. We can package a genrule that converts from feature
375protos to a serialized `EditionFeatureDefaults` string, and embed this anywhere
376we want. Both C++ and non-C++ generators/runtimes could embed this into their
377code. Once this is known, feature resolution becomes a lot simpler. The hardest
378part is creating a comparator for edition strings. After that, it's a simple
379search for the lower bound in the defaults, followed by some proto merges.
380
381### Bootstrapping
382
383One major complication we're likely to hit revolves around our bootstrapping of
384`descriptor.proto`. In languages that have dynamic messages, one codegen
385strategy is to embed the `FileDescriptorProto` of the file and then parse and
386build it at the beginning of runtime. For `descriptor.proto` in particular,
387handling options can be very challenging. For example, in Python, we
388intentionally strip all options from this file and then assume that the options
389descriptors always exist during build (in the presence of serialized options).
390Since features *are* options, this poses a challenge that's likely to vary
391language by language.
392
393We will likely need to special-case `descriptor.proto` in a number of ways.
394Notably, this file will **never** have any generator feature overrides, since it
395can't import those files. In every other case, we can safely assume that
396generator features exist in a fully resolved feature set. But for
397`descriptor.proto`, at least at the time it's first being built by the runtime,
398this extension won't be present. We also can't figure out edition defaults at
399that point since we don't have the generator features proto to reflect over.
400
401One possible solution would be to codegen extra information specifically for
402this bootstrapped proto, similar to what we suggested in *Editions: Runtime
403Feature Set Defaults* for edition defaults. That would allow the generator to
404provide enough information to build `descriptor.proto` during runtime. As long
405as these special cases are limited to `descriptor.proto` though, it can be left
406to a more isolated language-specific discussion.
407
408### Conformance Testing
409
410Code duplication means that we need a test strategy for making sure everyone
411stays conformant. We will need to implement a conformance testing framework for
412validating that all the different implementations of feature resolution agree.
413Our current conformance tests provide a good model for accomplishing this, even
414though they don't quite fit the problem (they're designed for
415parsing/serialization). There's a runner binary that can be hooked up to another
416binary built in any language. It sends a `ConformanceRequest` proto with a
417serialized payload and set of instructions, and then receives a
418`ConformanceResponse` with the result. In the runner, we just loop over a number
419of fixed test suites to validate that the supplied binary is conformant.
420
421We would want a similar setup here for language-agnostic testing. While we could
422write a highly focused framework just for feature resolution, a more general
423approach may set us up better in the future (e.g. option retention isn't
424duplicated now but could have been implemented that way). This will allow us to
425test any kind of transformation to descriptor protos, such as: proto3_optional,
426group/DELIMITED, required/LEGACY_REQUIRED. The following request/response protos
427describe the API:
428
429```
430message DescriptorConformanceRequest {
431  // The file under test, pre-transformation.
432  FileDescriptorProto file = 1;
433
434  // The pool of dependencies and feature files required for build.
435  FileDescriptorSet dependencies = 2;
436}
437
438message DescriptorConformanceResponse {
439  // The transformed file.
440  FileDescriptorProto file = 1;
441
442  // Any additional features added during build.
443  FileDescriptorSet added_features = 2;
444}
445```
446
447Each test point would construct a proto file, its dependencies, and any feature
448files to include in feature resolution. The conformance binary would use this to
449fully decorate the proto file with resolved features, and send the result back
450for comparison against our C++ source-of-truth. Any generator features added by
451the binary will also need to be sent back to get matching results.
452
453### Documentation
454
455Because we're now asking third-party generator owners to handle feature
456resolution on their own, we will need to document this. Specifically, we need to
457open-source documentation for:
458
459*   The algorithm described in
460    [Protobuf Editions Design: Features](protobuf-editions-design-features.md)
461*   The conformance test framework and how to use it (once it's implemented)
462
463On the other hand, we will have significantly less documentation to write about
464which feature sets to use where. Descriptor protos will *always* contain
465unresolved features, and C++ generators will have a simple API for getting the
466fully-resolved features.
467
468## Considered Alternatives
469
470### Use Generated Pool for C++ Generators
471
472*Note: this was part of the original proposal, but has been refactored (see
473cons)*
474
475Generators written in C++ are in a better position since they don't require any
476code duplication. They could be given visibility to our existing feature
477resolution utility to resolve the features themselves. However, a better
478alternative is to make improvements to this utility so that some helpers like
479the ones we proposed in *Exposing Editions Feature Sets* can be used to access
480the resolved features that *already exist*.
481
482Protoc works by first parsing the input protofiles and building them into a
483descriptor pool. This is the frontend pass, where only the global features are
484needed. For built-in languages, the resulting descriptors are passed directly to
485the generator for codegen. For plugins, they're serialized into descriptor
486protos, rebuilt in a new descriptor pool (in the generator process), and then
487sent to the generator code for codegen. In both of these cases, a
488`DescriptorPool` build of the protos is done from a binary that *necessarily*
489links in the relevant generator features.
490
491However, the FeatureSets we supply to generators are transformed to the
492generated pool (i.e. `FeatureSet` objects rather than `Message`) where the
493generator features will always exist. We've decided that there's no longer any
494reason to scrape the imports for features, but we *could* scrape the generated
495pool for them. This essentially means that when you call `MergeFeatures` to get
496a `FeatureSet`, the returned set is fully resolved *with respect to the current
497generated pool*. This is a much clearer contract, and has the benefit that the
498features visible to every C++ generator would automatically be populated with
499the correct generator features for them to use.
500
501Expanding on previous designs, we will provide the following API to C++
502generators via the `CodeGenerator` class:
503
504They will have access to all the fully-resolved feature set of any descriptor
505for making codegen decisions, and they will have access to their own unresolved
506generator features for validation. The `FileDescriptor::CopyTo` method will
507continue to output unresolved runtime features, which will become unresolved
508source features after option retention stripping (which generators should
509already be doing), for embedding in the gencode for runtime use.
510
511#### Pros
512
513*   Automatic inclusion of any features used in a binary
514*   Features will never be partially resolved
515
516#### Cons
517
518*   Implicit action at a distance could cause unexpected behaviors
519*   Uses globals, making testing awkward
520*   Not friendly to `DescriptorPool` cases who wouldn't necessarily want every
521    linked-in feature to go through feature resolution.
522
523### Default Placeholders
524
525Protoc continues to propagate and resolve core features and imported language
526level features. For language level features that protoc does not know about
527(that is, not imported), a core placeholder feature indicating that the default
528for a given edition should be respected can be propagated.
529
530```
531message FeatureSet {
532  optional string unknown_feature_edition_default = N; // e.g. 2023
533}
534```
535
536Instead of duplicating the entire feature resolution algorithm, plugins must
537only provide a utility mapping editions to their default FeatureSet using the
538generator feature files and optionally caching them.
539
540For example:
541
542```
543if features.hasUtf8Validation():
544  return features.getUtf8Validation()
545else:
546  default_features = getDefaultFeatures(features.getUnknownFeatureEditionDefault())
547  return default_features.getUtf8Validation()
548```
549
550#### Pros
551
552*   Less duplicate logic for propagating features
553
554#### Cons
555
556*   Descriptor proto bloat that is technically redundant with
557    `FileDescriptorProto` edition.
558*   Confusing that some but not all features are fully resolved
559*   Duplicated logic to resolve edition default from edition #
560*   Code-size and memory costs associated with the original approach still exist
561*   Still doesn't help with the descriptor pool case, which may require
562    duplicate logic.
563
564### Bidirectional Plugins
565
566Since the generators know the features they care about, we could have some kind
567of bidirectional communication between protoc and the plugins. The plugin would
568start by telling protoc the features it wants added, and then protoc would be
569able to fully resolve all feature sets before sending them off. This has the
570added benefit that it would allow us to do more interesting enhancements in the
571future. For example, the plugin could send its minimum required edition and
572other requirements *before* actually starting the build.
573
574**Note:** Bidirectional plugins could still be implemented for other purposes.
575This "alternative" is specifically for *using* that communication to pass
576missing feature specs.
577
578#### Pros
579
580*   Eliminates code duplication problem
581*   Provides infrastructure to enable future enhancements
582
583#### Cons
584
585*   Doesn't address the confusing API we have now where it's unclear what kind
586    of features are contained in the `features` field
587*   Doesn't address the code-size and memory costs during runtime
588*   Doesn't address the descriptor pool case
589
590### Central Feature Registry
591
592Instead of relying on generators and imports to supply feature specs, we could
593pivot to a central registry of all known features. Instead of simply claiming an
594extension number, generator owners could be required to submit all the feature
595protos to a central repository of feature protos. This would give protoc access
596to **all** features. There would be two ways to implement this:
597
598*   If it were built *into* protoc, we could avoid requiring any import
599    statements. We would probably still want an extension point to avoid adding
600    a dependency to `descriptor.proto`, but instead of `features.(pb.cpp)` they
601    would be something more like `features.(pb).cpp`.
602
603*   We could keep the current extension and import scheme. Proto files would
604    still need to import the features they override, but protoc would depend on
605    all of them and populate defaults for unspecified ones.
606
607#### Pros
608
609*   Makes all features easily discoverable wherever they're needed
610*   Eliminates the code duplication problem
611*   Gives us an option to remove the import statements, which are likely to
612    cause future headaches (in the edition zero LSC, in maintenance afterward,
613    and also for proto files that need to support a lot of third-party
614    runtimes).
615
616#### Cons
617
618*   Doesn't address the code-size and memory costs
619*   Creates version skew problems
620*   Confusing ownership semantics
621
622### Do Nothing
623
624Doing nothing would basically mean abandoning editions. The current design
625doesn't (and can't) work for third party generators. They'd be left to duplicate
626the logic themselves with no guidance or support from us. We would also see
627code-size and RAM bloat (except in C++) that would be very difficult to resolve.
628
629#### Pros
630
631*   Less work
632
633#### Cons
634
635*   Worse in every other way
636