protobuf/docs/design/editions/edition-evolution.md

4.1 KiB

Edition Evolution

Author: @mcy

Approved: 2022-07-06

Overview

Protobuf Editions give us a mechanism for pulling protobuf files into the future by upgrading their edition. Features are flags associated with syntax items of a .proto file; they can be either set explicitly, or they can be implied by the edition. Features can either correspond to behavior in protoc's frontend (e.g. proto:closed_enums), or to a specific backend (cpp:string_view).

This document seeks to answer:

  • When do we create an edition?
  • How do backends inform protoc of their defaults?

Proposal

Total Ordering of Editions

NOTE: This topic is largely superseded by Edition Naming.

The FileDescriptorProto.edition field is a string, so that we can avoid nasty surprises around needing to mint multiple editions per year: even if we mint edition = "2022";, we can mint edition = "2022b"; in a pinch.

However, we might not own some third-party backends, and they might be unaware of when we decide to mint editions, and might want to mint editions on their own. Suppose that I maintain the Haskell protobuf backend, and I decide to add the haskell:more_monads feature. How do I get this into an edition?

We propose defining a total order on editions. This means that a backend can pick the default not by looking at the edition, but by asking "is this proto older than this edition, where I introduced this default?"

The total order is thus: the edition string is split on '.'. Each component is then ordered by a.len < b.len && a < b. This ensures that 9 < 10, for example.

By convention, we will make the edition be either the year, like 2022, or the year followed by a revision, like 2022.1. Thus, we have the following total ordering on editions:

2022 < 2022.0 < 2022.1 < ... < 2022.9 < 2022.10 < ... < 2023 < ... < 2024 < ...

This means that backends (even though we don't particularly recommend it) can change defaults as often as they like. Thus, if I decide that haskell:more_monads becomes true in 2023, I simply ask file.EditionIsLaterThan("2023"). If it becomes false in 2023.1, a future backend can ask file.EditionIsBetween("2023", "2023.1").

Creating an Edition

In a sense, every edition already exists; it's just a matter of defining features on it.

If the feature is a proto: feature, protoc intrinsically knows it, and it is implemented in the frontend.

If the feature is a backend feature, the backend must be able to produce some kind of proto like message Edition { repeated Feature defaults = 1; } that describes what a specific edition must look like, based on less-than/is-between predicates like those above. That information can be used by protoc to display the full set of features it knows about given its backends. (The backend must, of course, use this information to make relevant codegen decisions.)

What about Editions "From the Future"?

Suppose that in version v5.0 of my Haskell backend I introduced haskell:more_monads, and this has a runtime component to it; that is, the feature must be present in the descriptor to be able to handle parsing the message correctly.

However, suppose I have an older service running v4.2. It is compiled with a proto that was already edition 2023 at build time (alternatively, it dynamically loads a proto). My v5.0 client sends it an incompatible message from "the future". Because a parse failure would be an unacceptable service degradation, we have a couple of options:

  • Editions cannot introduce a feature that requires readers to accept new encodings.
    • Similarly, editions cannot add restrictions that constrain past parsers.
  • Editions may introduce such features, but they must somehow fit into some kind of build horizon.

The former is a reasonable-sounding but ultimately unacceptable position, since it means we cannot use editions if we wanted to, say, make it so that message fields are encoded as groups rather than length-prefixed chunks. The alternative is to define some kind of build horizon.