protobuf Documentation

Protocol Buffers - Google's data interchange format

RAW Doc

Technical Documentation: protocolbuffers/protobuf

ℹ️ Provenance: Multi-Tier Ingestion · CodeWiki Reference

Overview (`README.md`)

Protocol Buffers - Google's data interchange format

Copyright 2008 Google LLC

Overview

Protocol Buffers (a.k.a., protobuf) are Google's language-neutral,
platform-neutral, extensible mechanism for serializing structured data. You
can learn more about it in protobuf's documentation.

This README file contains protobuf installation instructions. To install
protobuf, you need to install the protocol compiler (used to compile .proto
files) and the protobuf runtime for your chosen programming language.

Working With Protobuf Source Code

Most users will find working from
supported releases to be
the easiest path.

If you choose to work from the head revision of the main branch your build will
occasionally be broken by source-incompatible changes and insufficiently-tested
(and therefore broken) behavior.

If you are using C++ or otherwise need to build protobuf from source as a part
of your project, you should pin to a release commit on a release branch.

This is because even release branches can experience some instability in between
release commits.

Bazel with Bzlmod

Protobuf supports
Bzlmod with Bazel 8 +.
Users should specify a dependency on protobuf in their MODULE.bazel file as
follows.

text
bazel_dep(name = "protobuf", version = <VERSION>)

Users can optionally override the repo name, such as for compatibility with
WORKSPACE.

text
bazel_dep(name = "protobuf", version = <VERSION>, repo_name = "com_google_protobuf")

Bazel with WORKSPACE

Users can also add the following to their legacy
WORKSPACE file.

Note that with the release of 30.x there are a few more load statements to
properly set up rules_java and rules_python.

text
http_archive(
    name = "com_google_protobuf",
    strip_prefix = "protobuf-VERSION",
    sha256 = ...,
    url = ...,
)

load("@com_google_protobuf//:protobuf_deps.bzl", "protobuf_deps")

protobuf_deps()

load("@rules_java//java:rules_java_deps.bzl", "rules_java_dependencies")

rules_java_dependencies()

load("@rules_java//java:repositories.bzl", "rules_java_toolchains")

rules_java_toolchains()

load("@rules_python//python:repositories.bzl", "py_repositories")

py_repositories()

Protobuf Compiler Installation

The protobuf compiler is written in C++. If you are using C++, please follow
the C++ Installation Instructions to install protoc along
with the C++ runtime.

For non-C++ users, the simplest way to install the protocol compiler is to
download a pre-built binary from our GitHub release page.

In the downloads section of each release, you can find pre-built binaries in
zip packages: protoc-$VERSION-$PLATFORM.zip. It contains the protoc binary
as well as a set of standard .proto files distributed along with protobuf.

If you are looking for an old version that is not available in the release
page, check out the Maven repository.

These pre-built binaries are only provided for released versions. If you want
to use the github main version at HEAD, or you need to modify protobuf code,
or you are using C++, it's recommended to build your own protoc binary from
source.

If you would like to build protoc binary from source, see the C++ Installation Instructions.

Protobuf Runtime Installation

Protobuf supports several different programming languages. For each programming
language, you can find instructions in the corresponding source directory about
how to install protobuf runtime for that specific language:

Language Source
C++ (include C++ runtime and protoc) src
Java java
Python python
Objective-C objectivec
C# csharp
Ruby ruby
Go protocolbuffers/protobuf-go
PHP php
Dart dart-lang/protobuf
JavaScript protocolbuffers/protobuf-javascript

Quick Start

The best way to learn how to use protobuf is to follow the tutorials in our
developer guide
.

If you want to learn from code examples, take a look at the examples in the
examples directory.

Documentation

The complete documentation is available at the Protocol Buffers doc site.

Support Policy

Read about our version support policy
to stay current on support timeframes for the language libraries.

Developer Community

To be alerted to upcoming changes in Protocol Buffers and connect with protobuf developers and users,
join the Google Group.


Overview (`.bazelci/README.md`)

Bazel CI test

The .bazelci/presubmit.yml file is used by https://buildkite.com/bazel/protobuf
to test building protobuf with Bazel on Bazel CI. It should contain the same
set of tests as .bcr/presubmit.yml which is the tests that will run before
publishing protobuf to BCR.


Overview (`.bcr/README.md`)

Bazel Central Registry

When protobuf is released, we want it to be published to the Bazel Central
Registry automatically: https://registry.bazel.build

This folder contains configuration files to automate the publish step. See
https://github.com/bazel-contrib/publish-to-bcr/blob/main/templates/README.md
for authoritative documentation about these files.


Overview (`ci/README.md`)

This directory contains CI-specific tooling.

Clang wrappers

CMake allows for compiler wrappers to be injected such as ccache, which
intercepts compiler calls and short-circuits on cache-hits. This can be done
by specifying CMAKE_C_COMPILER_LAUNCHER and CMAKE_CXX_COMPILER_LAUNCHER
during CMake's configure step. Unfortunately, X-Code doesn't provide anything
like this, so we use basic wrapper scripts to invoke ccache + clang.

Bazelrc files

In order to allow platform-specific .bazelrc flags during testing, we keep
3 different versions here along with a shared common.bazelrc that they all
include. Our GHA infrastructure will select the appropriate file for any test
and overwrite the default .bazelrc in our workspace, which is intended for
development only.


Overview (`cmake/README.md`)

This directory contains CMake files that can be used to build protobuf.

You need to have CMake and Git
installed on your computer before proceeding. If you don't have
Abseil installed, it will be fetched
from GitHub. We currently support CMake 3.22 and newer.

Most of the instructions will be given using CMake's command-line interface, but
the same actions can be performed using appropriate GUI tools.

Getting Sources

You can get the latest stable source packages from the release page:

text
https://github.com/protocolbuffers/protobuf/releases/latest

Or you can use git to clone the protobuf git repository.

text
git clone -b [release_tag] https://github.com/protocolbuffers/protobuf.git

Where [release_tag] is a git tag like v32.0-rc1 or a branch name like main
if you want to get the latest code.

Basic Build

It is considered good practice not to build CMake projects in the source tree
but in a separate folder. The following commands show a basic, cross-platform
way to configure, build, and install protobuf.

bash
# From the protobuf source directory
cmake -S . -B build \
  -DCMAKE_INSTALL_PREFIX=../install \
  -DCMAKE_BUILD_TYPE=Release

# Compile the code
cmake --build build --parallel 10

# Run tests
ctest --test-dir build --verbose

# Install the libraries and headers
cmake --install build

This will install the compiled libraries, headers, and protoc binary into the
../install directory, relative to the source root.

CMake Configuration Flags

The following flags can be passed to cmake during the configuration step
(e.g., cmake -S . -B build -D<FLAG>=<VALUE>) to customize the build.

C++ Version

By default, CMake will use whatever C++ version is the system default. Since
protobuf requires C++17 or newer, sometimes you will need to explicitly override
this.

bash
# Configure protobuf to be built with C++17
cmake . -DCMAKE_CXX_STANDARD=17

See the CMake documentation for
CXX_STANDARD{.external}
for all available options.

Dependency Management

Abseil and Google Test

During configuration, you can optionally specify where CMake should expect to
find your Abseil, Google Test, and jsoncpp installations, if they are installed.
If they aren't installed, they will be fetched from GitHub. To specify the
locations, set -DCMAKE_PREFIX_PATH to the path where you installed them.

bash
# Path to where Abseil and GTest are installed
cmake . -DCMAKE_PREFIX_PATH=/path/to/my/dependencies

If the installation of a dependency can't be found, CMake will default to
downloading and building a copy from GitHub. To prevent this and make it an
error condition, you can optionally set:
-Dprotobuf_LOCAL_DEPENDENCIES_ONLY=ON.

Enabling Tests

To enable building the unit tests, set the following flag:

  • -Dprotobuf_BUILD_TESTS=ON

ZLib Support

If you want to include GzipInputStream and GzipOutputStream in libprotobuf,
you need to have ZLib installed. Ensure ZLib headers and libraries are in a
standard system location or a custom install prefix.

If ZLib is installed in a non-standard location, you can help CMake find it by
setting:

  • -DZLIB_INCLUDE_DIR=/path/to/zlib/headers
  • -DZLIB_LIBRARY=/path/to/zlib/library.lib

Build Options

DLLs vs. Static Linking

Static linking is the default. To build shared libraries (DLLs on Windows), add
the following flag during configuration:

  • -Dprotobuf_BUILD_SHARED_LIBS=ON

When you build your own project against protobuf, you must also define #define PROTOBUF_USE_DLLS. For more platform-specific details, see the
Windows Builds section.

Compiling

The standard, cross-platform way to compile a CMake project is:

text
cmake --build <build_directory>

For example:

text
cmake --build build --parallel 10

If your generator supports multiple configurations (like Visual Studio), you
must also specify which one to build:

text
cmake --build build --config Release

Testing

To run unit tests, first compile protobuf as described above. Then run ctest:

text
ctest --test-dir build --progress --output-on-failure

You can also build the check target, which will compile and run the tests:

text
cmake --build build --target check

To run specific tests, you need to pass arguments to the test program itself,
which requires finding the test executable in your build directory.

Installing

To install protobuf to the directory specified by CMAKE_INSTALL_PREFIX during
configuration, build the install target:

text
cmake --build <build_directory> --target install

This will create the following folders under the install location:

  • bin - contains protobuf protoc compiler
  • include - contains C++ headers and .proto files
  • lib - contains linking libraries and CMake package configuration files.

Platform-Specific Details

Windows Builds {#windows-builds}

On Windows, you can build using Visual Studio's command-line tools or IDE.

Generators

Of most interest to Windows programmers are the following
generators:

  • Visual Studio: Generates a multi-configuration .sln file. Example: -G "Visual Studio 16 2019".
  • Ninja: Uses the external tool Ninja to
    build. This is often the fastest solution.

Environment Setup

Open the appropriate Command Prompt from the Start menu (e.g., x86 Native
Tools Command Prompt for VS 2019
) to ensure cl.exe and other build tools are
in your PATH.

DLLs vs. Static Linking on Windows

While shared libraries can be built, static linking is strongly recommended on
Windows. This is due to issues with Win32's use of a separate heap for each DLL
and binary compatibility issues between different versions of MSVC's STL
library.

If you are distributing your software, do NOT install libprotobuf.dll or
libprotoc.dll to a shared location. Keep them in your application's own
install directory.

Notes on Compiler Warnings

The following MSVC warnings have been disabled while building the protobuf
libraries. You may need to disable them in your own project as well.

  • C4065 - # switch statement contains 'default' but no 'case' labels
  • C4146 - # unary minus operator applied to unsigned type
  • C4244 - # 'conversion' conversion from 'type1' to 'type2', possible loss
    of data
  • C4251 - # 'identifier' : class 'type' needs to have dll-interface to be
    used by clients of class 'type2'
  • C4267 - # 'var' : conversion from 'size_t' to 'type', possible loss of
    data
  • C4305 - # 'identifier' : truncation from 'type1' to 'type2'
  • C4307 - # 'operator' : integral constant overflow
  • C4309 - # 'conversion' : truncation of constant value
  • C4334 - # 'operator' : result of 32-bit shift implicitly converted to 64
    bits (was 64-bit shift intended?)
  • C4355 - # 'this' : used in base member initializer list
  • C4506 - # no definition for inline function 'function'
  • C4800 - # 'type' : forcing value to bool 'true' or 'false' (performance
    warning)
  • C4996 - # The compiler encountered a deprecated declaration.

C4251 is particularly notable if you build protobuf as a DLL. See the old
version of this document for a longer explanation if needed.

Linux Builds

Building with CMake works very similarly on Linux. You will need to have gcc
or clang installed. CMake will generate Makefiles by default, but can also be
configured to use Ninja.

After building, you can install the files system-wide using sudo:

text
sudo cmake --install build

Or directly with Makefiles:

text
cd build
 sudo make install

Overview (`conformance/README.md`)

Protocol Buffers - Google's data interchange format

Copyright 2008 Google LLC.

This directory contains conformance tests for testing completeness and
correctness of Protocol Buffers implementations. These tests are designed
to be easy to run against any Protocol Buffers implementation.

This directory contains the tester process conformance-test, which
contains all of the tests themselves. Then separate programs written
in whatever language you want to test communicate with the tester
program over a pipe.

If you're not using Bazel to run these tests, make sure you build the C++
tester code beforehand, e.g. from the base directory:

text
$ cmake . -Dprotobuf_BUILD_CONFORMANCE=ON && cmake --build .

This will produce a conformance_test_runner binary that can be used to run
conformance tests on any executable. Pass it --help for more information.

Running the tests for C++

To run the tests against the C++ implementation, run:

text
$ bazel test //src:conformance_test

Or alternatively with CMake:

text
$ ctest -R conformance_cpp_test

Running the tests for other languages

All of the languages in the Protobuf source tree are set up to run conformance
tests using similar patterns. You can either use Bazel to run the
conformance_test target defined in the language's root BUILD.bazel file,
or create an executable for a custom test and pass it to
conformance_test_runner.

Note: CMake can be used to build the conformance test runner, but not any of
the conformance test executables outside C++. So if you aren't using Bazel
you'll need to create the executable you pass to conformance_test_runner via
some alternate build system.

While we plan to model all our supported languages more completely in Bazel,
today some of them are a bit tricky to run. Below is a list of the commands
(and prerequisites) to run each language's conformance tests.

C#:

text
$ which dotnet || echo "You must have dotnet installed!"
$ bazel test //csharp:conformance_test \
    --action_env=DOTNET_CLI_TELEMETRY_OPTOUT=1 --test_env=DOTNET_CLI_HOME=~ \
    --action_env=DOTNET_SYSTEM_GLOBALIZATION_INVARIANT=1

Java:

text
$ bazel test //java/core:conformance_test //java/lite:conformance_test

Objective-C (Mac only):

text
$ bazel test //objectivec:conformance_test --macos_minimum_os=12.0

PHP:

text
$ bazel test //php:conformance_test

PHP (C):

text
$ which gcc     || echo "gcc is required!"
$ which libtool || echo "libtool is required!"
$ which make    || echo "make is required!"
$ which pear    || echo "pear is required! It might require a development version of PHP such as a php-dev package"
$ which pecl    || echo "pecl is required! It might require a development version of PHP such as a php-dev package"
$ which phpize  || echo "phpize is required! It might require a development version of PHP such as a php-dev package"
$ bazel test //php:conformance_test_c

Python:

text
$ bazel test //python:conformance_test

Python (C++):

text
$ bazel test //python:conformance_test_cpp --define=use_fast_cpp_protos=true

Ruby:

text
$ [[ $(ruby --version) == "ruby"* ]] || echo "Select a C Ruby!"
$ bazel test //ruby:conformance_test --define=ruby_platform=c \
    --action_env=PATH --action_env=GEM_PATH --action_env=GEM_HOME

Ruby (JRuby):

text
$ [[ $(ruby --version) == "jruby"* ]] || echo "Switch to Java Ruby!"
$ bazel test //ruby:conformance_test_jruby --define=ruby_platform=java \
    --action_env=PATH --action_env=GEM_PATH --action_env=GEM_HOME

Testing other Protocol Buffer implementations

To run these tests against a new Protocol Buffers implementation, write a
program in your language that uses the protobuf implementation you want
to test. This program should implement the testing protocol defined in
conformance.proto.
This is designed to be as easy as possible: the C++ version is only
150 lines and is a good example for what this program should look like
(see conformance_cpp.cc).
The program only needs to be able to read from stdin and write to stdout.

Portability

Note that the test runner currently does not work on Windows. Patches
to fix this are welcome! (But please get in touch first to settle on
a general implementation strategy).


Overview (`csharp/README.md`)

This directory contains the C# Protocol Buffers runtime library.

Usage

The easiest way how to use C# protobufs is via the Google.Protobuf
NuGet package. Just add the NuGet package to your VS project.

You will also want to install the Google.Protobuf.Tools NuGet package, which
contains precompiled version of protoc.exe and a copy of well known .proto
files under the package's tools directory.

To generate C# files from your .proto files, invoke protoc with the
--csharp_out option.

Supported platforms

The runtime library is built as a class library, supporting targets of:

  • .NET 4.5+ (net45)
  • .NET Standard 1.1 and 2.0 (netstandard1.1 and netstandard2.0)
  • .NET 5+ (net50)

You should be able to use Protocol Buffers in Visual Studio 2012 and
all later versions. This includes all code generated by protoc,
which only uses features from C# 3 and earlier. When compiling generated
code with old compilers (before C# 7.2) you need to define the
GOOGLE_PROTOBUF_REFSTRUCT_COMPATIBILITY_MODE symbol in your project
so that the generated classes don't implement IBufferMessage, which uses
ref struct types.

Building

Open the src/Google.Protobuf.sln solution in Visual Studio 2022 or
later.

Although users of this project are only expected to have Visual
Studio 2012 or later, developers of the library are required to
have Visual Studio 2022 or later, as the library uses C# 10 features
in its implementation and runs tests under .NET 6. These features
have no impact when using the compiled code - they're only relevant
when building the Google.Protobuf assembly.

Testing

The unit tests use NUnit 3. Tests can be
run using the Visual Studio Test Explorer or dotnet test.

.NET 3.5

We don't support .NET 3.5. It used to be feasible to build this library
targeting .NET 3.5, but a number of changes requiring newer runtime/framework
features have been added over time. While it would no doubt be possible to
rework the current implementation to allow most of the functionality to be built
in .NET 3.5, this would create an undue maintenance burden.

History of C# protobufs

This subtree was originally imported from https://github.com/jskeet/protobuf-csharp-port
and represents the latest development version of C# protobufs, that will now be developed
and maintained by Google. All the development will be done in open, under this repository
(https://github.com/protocolbuffers/protobuf).

The previous project differs from this project in a number of ways:

  • The old code only supported proto2; the new code initially only supported
    proto3 (so no unknown fields, no required/optional distinction, no
    extensions); since then proto2 support has been added
  • The old code was based on immutable message types and builders for
    them
  • The old code did not support maps or oneof
  • The old code had its own JSON representation, whereas the new code
    uses the standard protobuf JSON representation
  • The old code had no notion of the "well-known types" which have
    special support in the new code
  • The old project supported some older platforms (such as older
    versions of Silverlight) which are not currently supported in the
    new project

Overview (`examples/README.md`)

Protocol Buffers - Code Example

This directory contains example code that uses Protocol Buffers to manage an
address book. Two programs are provided for each supported language. The
add_person example adds a new person to an address book, prompting the user to
input the person's information. The list_people example lists people already in
the address book. The examples use the exact same format in all three languages,
so you can, for example, use add_person_java to create an address book and then
use list_people_python to read it.

These examples are part of the Protocol Buffers tutorial, located at:
https://developers.google.com/protocol-buffers/docs/tutorials

Build the example using bazel

The example requires bazel 0.5.4 or newer to build. You can download/install
the latest version of bazel from bazel's release page:

text
https://github.com/bazelbuild/bazel/releases

Once you have bazel installed, simply run the following command in this examples
directory to build the code:

text
$ bazel build :all

Then you can run the built binary:

text
$ bazel-bin/add_person_cpp addressbook.data

To use protobuf in your own bazel project, please follow instructions in the
BUILD.bazel file and WORKSPACE file.

Build the example using make

You must install the protobuf package before you can build it using make. The
minimum requirement is to install protocol compiler (i.e., the protoc binary)
and the protobuf runtime for the language you want to build.

You can simply run "make" to build the example for all languages (except for
Go). However, since different languages have different installation requirements,
it will likely fail. It's better to follow individual instructions below to
build only the language you are interested in.

C++

You can follow instructions in ../src/README.md to install
protoc from source.

Then run "make cpp" in this examples directory to build the C++ example. It
will create two executables: add_person_cpp and list_people_cpp. These programs
simply take an address book file as their parameter. The add_person_cpp
programs will create the file if it doesn't already exist.

To run the examples:

text
$ ./add_person_cpp addressbook.data
$ ./list_people_cpp addressbook.data

Note that on some platforms you may have to edit the Makefile and remove
"-lpthread" from the linker commands (perhaps replacing it with something else).
We didn't do this automatically because we wanted to keep the example simple.

Python

Follow instructions in ../README.md to install protoc and then
follow ../python/README.md to install protobuf python
runtime from source. You can also install python runtime using pip:

text
$ pip install protobuf

Make sure the runtime version is the same as protoc binary, or it may not work.

After you have install both protoc and python runtime, run "make python" to
build two executables (shell scripts actually): add_person_python and
list_people_python. They work the same way as the C++ executables.

Java

Follow instructions in ../README.md to install protoc and then
download protobuf Java runtime .jar file from maven:

text
https://mvnrepository.com/artifact/com.google.protobuf/protobuf-java

Then run the following:

text
$ export CLASSPATH=/path/to/protobuf-java-[version].jar
$ make java

This will create the add_person_java/list_people_java executables (shell
scripts) and can be used to create/display an address book data file.

Go

Follow instructions in ../README.md to install protoc. Then
install the Go protoc plugin (protoc-gen-go):

text
$ go install google.golang.org/protobuf/cmd/protoc-gen-go@latest

The "go install" command will install protoc-gen-go into the GOBIN
directory. You can set the $GOBIN environment variable before
running "go install" to change the install location. Make sure the
install directory is in your shell $PATH.

Build the Go samples with "make go". This creates the following
executable files in the current directory:

text
add_person_go      list_people_go

To run the example:

text
./add_person_go addressbook.data

to add a person to the protocol buffer encoded file addressbook.data. The file
is created if it does not exist. To view the data, run:

text
./list_people_go addressbook.data

Observe that the C++, Python, Java, and Dart examples in this directory run in a
similar way and can view/modify files created by the Go example and vice
versa.

Dart

First, follow the instructions in ../README.md to install the Protocol Buffer Compiler (protoc).

Then, install the Dart Protocol Buffer plugin as described here.
Note, the executable bin/protoc-gen-dart must be in your PATH for protoc to find it.

Build the Dart samples in this directory with make dart.

To run the examples:

sh
$ dart add_person.dart addressbook.data
$ dart list_people.dart addressbook.data

The two programs take a protocol buffer encoded file as their parameter.
The first can be used to add a person to the file. The file is created
if it does not exist. The second displays the data in the file.


Overview (`hpb_generator/README.md`)

hpb Generator

This directory contains the generator for the hpb
API
, an
experimental C++ protobuf implementation. Most users should use the standard
C++ implementation
here.


Overview (`java/README.md`)

Protocol Buffers - Google's data interchange format

Copyright 2008 Google Inc.

https://developers.google.com/protocol-buffers/

Use Java Protocol Buffers

To use protobuf in Java, first obtain the protocol compiler (a.k.a., protoc,
see instructions in the toplevel README.md) and use it to
generate Java code for your .proto files:

text
$ protoc --java_out=${OUTPUT_DIR} path/to/your/proto/file

Include the generated Java files in your project and add a dependency on the
protobuf Java runtime.

Maven

If you are using Maven, use the following:

xml
<dependency>
  <groupId>com.google.protobuf</groupId>
  <artifactId>protobuf-java</artifactId>
  <version></version>
</dependency>

And replace `` with a version from the
Maven Protocol Buffers Repository.

For example, 4.28.2.

Make sure the version number of the runtime matches (or is newer than) the
version number of the protoc.

If you want to use features like protobuf JsonFormat, add a dependency on the
protobuf-java-util package:

xml
<dependency>
  <groupId>com.google.protobuf</groupId>
  <artifactId>protobuf-java-util</artifactId>
  <version></version>
</dependency>

Use Java Protocol Buffers on Android

For Android users, it's recommended to use protobuf Java Lite runtime because
of its smaller code size. Java Lite runtime also works better with Proguard
because it doesn't rely on Java reflection and is optimized to allow as much
code stripping as possible. You can following these instructions to use Java
Lite runtime
.

Use Java Protocol Buffers with Bazel

Bazel has native build rules to work with protobuf. For Java, you can use the
java_proto_library rule for server and the java_lite_proto_library rule for
Android. Check out our build files examples to learn
how to use them.

Build from Source

Most users should follow the instructions above to use protobuf Java runtime.
If you are contributing code to protobuf or want to use a protobuf version
that hasn't been officially released yet, you can follow the instructions
below to build protobuf from source code.

Build from Source

You may follow these instructions to build from source. This does not require
Maven to be installed. Note that these instructions skip running unit tests and
only describes how to install the core protobuf library (without the util
package).

  1. Build the C++ code, or obtain a binary distribution of protoc (see
    the toplevel README.md). If you install a binary
    distribution, make sure that it is the same version as this package.
    If in doubt, run:

    $ protoc --version

    If you built the C++ code without installing, the compiler binary
    should be located in ../src.

  2. Invoke protoc to build DescriptorProtos.java:

    $ protoc --java_out=core/src/main/java -I../src
    ../src/google/protobuf/descriptor.proto

  3. Compile the code in core/src/main/java using whatever means you prefer.

  4. Install the classes wherever you prefer.

Kotlin Protocol Buffers

This directory also provides support for Kotlin protocol buffers, which are
built on top of Java protocol buffers. Kotlin protocol buffers require a
dependency on Java protocol buffers, and both Java and Kotlin protocol buffer
code must be generated for every proto file.

The main goal of Kotlin protobuf is to provide idiomatic ways to build and read
protocol buffers in Kotlin. Learn more about Kotlin protobufs in our
documentation.

Use Kotlin Protocol Buffers

To use protobuf in Kotlin, first install the protocol compiler (protoc --
instructions for installing are in the top-level README.md) and
use it to generate Java and Kotlin code for your .proto files:

text
$ protoc --java_out=${OUTPUT_DIR} --kotlin_out=${OUTPUT_DIR} path/to/your/proto/file

Include the generated Java and Kotlin files in your project and add a dependency on the
protobuf Java and Kotlin runtime.

Maven

If you are using Maven, use the following:

xml
<dependency>
  <groupId>com.google.protobuf</groupId>
  <artifactId>protobuf-java</artifactId>
  <version></version>
</dependency>

<dependency>
  <groupId>com.google.protobuf</groupId>
  <artifactId>protobuf-kotlin</artifactId>
  <version></version>
</dependency>

Replace `` with a version from the
Maven Protocol Buffers Repository,

such as 4.28.2.

Make sure the version number of the runtimes match each other and match (or are
newer than) the version number of the protoc.

Use Kotlin Protocol Buffers on Android

For Android users, it's recommended to use the Java Lite runtime for its smaller
code size. We provide a protobuf-kotlin-lite package in Maven and Bazel to
pair with the Java Lite runtime. Use these if you want to use Kotlin on
Android or in another context where you want to use Java Lite. Similar to the
full runtime, protobuf-kotlin-lite requires a dependency on
protobuf-java-lite.

Compatibility Notice

  • Protobuf minor version releases are backwards-compatible. If your code
    can build/run against the old version, it's expected to build/run against
    the new version as well. Both binary compatibility and source compatibility
    are guaranteed for minor version releases if the user follows the guideline
    described in this section.

  • Protobuf major version releases may also be backwards-compatible with the
    last release of the previous major version. See the release notice for more
    details.

  • APIs marked with the @ExperimentalApi annotation are subject to change. They
    can be modified in any way, or even removed, at any time. Don't use them if
    compatibility is needed. If your code is a library itself (i.e. it is used on
    the CLASSPATH of users outside your own control), you should not use
    experimental APIs, unless you repackage them (e.g. using ProGuard).

  • Deprecated non-experimental APIs will be removed two years after the release
    in which they are first deprecated. You must fix your references before this
    time. If you don't, any manner of breakage could result (you are not
    guaranteed a compilation error).

  • Protobuf message interfaces/classes are designed to be subclassed by protobuf
    generated code only. Do not subclass these message interfaces/classes
    yourself. We may add new methods to the message interfaces/classes which will
    break your own subclasses.

  • Don't use any method/class that is marked as "used by generated code only".
    Such methods/classes are subject to change.

  • Protobuf LITE runtime APIs are not stable yet. They are subject to change even
    in minor version releases.

Documentation

The complete documentation for Protocol Buffers is available via the
web at:

https://developers.google.com/protocol-buffers/


Overview (`lua/README.md`)

upb Lua bindings

These are some bare-bones upb bindings for Lua.

These bindings exist primarily for experimentation and testing.
They are incomplete and are not really intended for use in any application.
This is by no means a complete or supported protobuf library, and in fact
we don't even claim it to be functional.


Overview (`objectivec/README.md`)

Protocol Buffers - Google's data interchange format

Copyright 2008 Google Inc.

This directory contains the Objective C Protocol Buffers runtime library.

Requirements

The Objective C implementation requires:

  • Objective C 2.0 Runtime (32bit & 64bit iOS, 64bit OS X).
  • Xcode 13.3.1 (or later).
  • The library code does not use ARC (for performance reasons), but it all can
    be called from ARC code.

Installation

The distribution pulled from github includes the sources for both the
compiler (protoc) and the runtime (this directory). After cloning the distribution
and needed submodules (see the src directory's README),
to build the compiler and run the runtime tests, you can use:

text
$ objectivec/DevTools/full_mac_build.sh

This will generate the protoc binary.

Building

There are two ways to include the Runtime sources in your project:

Add objectivec/*.h, objectivec/google/protobuf/*.pbobjc.h, and
objectivec/GPBProtocolBuffers.m to your project.

or

Add objectivec/*.h, objectivec/google/protobuf/*.pbobjc.h,
objectivec/google/protobuf/*.pbobjc.m, and objectivec/*.m except for
objectivec/GPBProtocolBuffers.m to your project.

If the target is using ARC, remember to turn off ARC (-fno-objc-arc) for the
.m files.

The files generated by protoc for the *.proto files (*.pbobjc.h and
*.pbobjc.m) are then also added to the target.

Usage

The objects generated for messages should work like any other Objective C
object. They are mutable objects, but if you don't change them, they are safe
to share between threads (similar to passing an NSMutableDictionary between
threads/queues; as long as no one mutates it, things are fine).

There are a few behaviors worth calling out:

A property that is type NSString* will never return nil. If the value is
unset, it will return an empty string (@""). This is inpart to align things
with the Protocol Buffers spec which says the default for strings is an empty
string, but also so you can always safely pass them to isEqual:/compare:, etc.
and have deterministic results.

A property that is type NSData* also won't return nil, it will return an empty
data ([NSData data]). The reasoning is the same as for NSString not returning
nil.

A property that is another GPBMessage class also will not return nil. If the
field wasn't already set, you will get a instance of the correct class. This
instance will be a temporary instance unless you mutate it, at which point it
will be attached to its parent object. We call this pattern autocreators.
Similar to NSString and NSData properties it makes things a little safer when
using them with isEqual:/etc.; but more importantly, this allows you to write
code that uses Objective C's property dot notation to walk into nested objects
and access and/or assign things without having to check that they are not nil
and create them each step along the way. You can write this:

text
- (void)updateRecord:(MyMessage *)msg {
  ...
  // Note: You don't have to check subMessage and otherMessage for nil and
  // alloc/init/assign them back along the way.
  msg.subMessage.otherMessage.lastName = @"Smith";
  ...
}

If you want to check if a GPBMessage property is present, there is always as
has\[NAME\] property to go with the main property to check if it is set.

A property that is of an Array or Dictionary type also provides autocreator
behavior and will never return nil. This provides all the same benefits you
see for the message properties. Again, you can write:

text
- (void)updateRecord:(MyMessage *)msg {
  ...
  // Note: Just like above, you don't have to check subMessage and otherMessage
  // for nil and alloc/init/assign them back along the way. You also don't have
  // to create the siblingsArray, you can safely just append to it.
  [msg.subMessage.otherMessage.siblingsArray addObject:@"Pat"];
  ...
}

If you are inspecting a message you got from some other place (server, disk,
etc), you may want to check if the Array or Dictionary has entries without
causing it to be created for you. For this, there is always a \[NAME\]_Count
property also provided that can return zero or the real count, but won't trigger
the creation.

All message fields always have a value when accessed. For primitive type
fields (ints, floats, bools, enum) there the concept of presence, that allows
you to tell the difference between when the field is the default value because
it was not set and when it was set, but explicitly to the default
value for the field. For fields with that do support presence, you can test
if the value was explicitly set via the has\[NAME\] property. If the value
has been set, and you want to clear it, you can set the has\[NAME\] to NO.

The Objective C classes/enums can be used from Swift code.

Objective C Generator Proto File Options

*objc_class_prefix=* (no default)

This options allow you to provide a custom prefix for all the symbols generated
from a proto file (classes (from message), enums, the Root for extension
support).

If not set, the generation options package_to_prefix_mappings_path and
use_package_as_prefix (documented below) controls what is used instead. Since
Objective C uses a global namespace for all of its classes, there can be collisions.
use_package_as_prefix=yes should avoid collisions since proto package are used to
scope/name things in other languages, but this option can be used to get shorter
names instead. Convention is to base the explicit prefix on the proto package.

Objective C Generator `protoc` Options

When generating Objective C code, protoc supports a --objc_opt argument; the
argument is comma-delimited name/value pairs (key=value,key2=value2). The
keys are used to change the behavior during generation. The currently
supported keys are:

  • generate_for_named_framework: The value used for this key will be used
    when generating the #import statements in the generated code. Instead
    of being plain #import "some/path/file.pbobjc.h" lines, they will be
    framework based, i.e. - #import <VALUE/file.pbobjc.h>.

    NOTE: If this is used with named_framework_to_proto_path_mappings_path,
    then this is effectively the default to use for everything that wasn't
    mapped by the other.

  • named_framework_to_proto_path_mappings_path: The value used for this key
    is a path to a file containing the listing of framework names and proto
    files. The generator uses this to decide if another proto file referenced
    should use a framework style import vs. a user level import
    (#import <FRAMEWORK/file.pbobjc.h> vs #import "dir/file.pbobjc.h").

    The format of the file is:

    • An entry is a line of frameworkName: file.proto, dir/file2.proto.
    • Comments start with #.
    • A comment can go on a line after an entry.
      (i.e. - frameworkName: file.proto # comment)

    Any number of files can be listed for a framework, just separate them with
    commas.

    There can be multiple lines listing the same frameworkName in case it has a
    lot of proto files included in it; and having multiple lines makes things
    easier to read.

  • runtime_import_prefix: The value used for this key to be used as a
    prefix on #imports of runtime provided headers in the generated files.
    When integrating ObjC protos into a build system, this can be used to avoid
    having to add the runtime directory to the header search path since the
    generate #import will be more complete.

  • package_to_prefix_mappings_path: The value used for this key is a
    path to a file containing a list of proto packages and prefixes.
    The generator will use this to locate which ObjC class prefix to use when
    generating sources unless the objc_class_prefix file option is set.
    This option can be useful if multiple apps consume a common set of
    proto files but wish to use a different prefix for the generated sources
    between them. This option takes precedent over the use_package_as_prefix
    option.

    The format of the file is:

    • An entry is a line of "package=prefix".
    • Comments start with #.
    • A comment can go on a line after a expected package/prefix pair.
      (i.e. - "package=prefix # comment")
    • For files that do NOT have a proto package (not recommended), an
      entry can be made as "no_package:PATH=prefix", where PATH is the
      path for the .proto file.
  • use_package_as_prefix, package_as_prefix_forced_prefix and
    proto_package_prefix_exceptions_path: The value for
    use_package_as_prefix can be yes or no, and indicates if a prefix
    should be derived from the proto package for all the symbols for files that
    don't have the objc_class_prefix file option (mentioned above). This helps
    ensure the symbols are more unique and means there is less chance of ObjC
    class name collisions.

    To help in migrating code to using this support,
    proto_package_prefix_exceptions_path can be used to provide the path
    to a file that contains proto package names (one per line, comments allowed
    if prefixed with #). These package won't get the derived prefix, allowing
    migrations to the behavior one proto package at a time across a code base.

    package_as_prefix_forced_prefix can be used to provide a value that will
    be used before all prefixes derived from the packages to help group all of
    these types with a common prefix. Thus it only makes sense to use it when
    use_package_as_prefix is also enabled. For example, setting this to
    "XYZ_" and generating a file with the package "something" defining
    "MyMessage", would have Objective-C class be XYZ_Something_MyMessage.

    use_package_as_prefix currently defaults to no (existing behavior), but
    that could change in the future as it helps avoid collisions when more
    protos get added to the build. Note that this would be a breaking change.

  • headers_use_forward_declarations: The value for this can be yes or
    no, and indicates if the generated headers use forward declarations for
    Message and Enum types from other .proto files or if the files should be
    imported into the generated header instead.

    By using forward declarations, less code is likely to recompile when the
    files do change, but Swift generally doesn't like forward declarations and
    will fail to include properties when the concrete definition of the type is
    known at import time. If your proto usages span modules, this can be a
    problem.

    headers_use_forward_declarations currently defaults to yes (existing
    behavior), but in a future release, that default may change to provide
    better Swift support by default.

Contributing

Please make updates to the tests along with changes. If just changing the
runtime, the Xcode projects can be used to build and run tests. If your change
also requires changes to the generated code,
objectivec/DevTools/full_mac_build.sh can be used to easily rebuild and test
changes. Passing -h to the script will show the addition options that could
be useful.

Documentation

The complete documentation for Protocol Buffers is available via the
web at:

https://developers.google.com/protocol-buffers/


Overview (`php/README.md`)

This directory contains the Protocol Buffers runtime implementation via both a
pure PHP package and a native c extension. The pure PHP package is intended to
provide usability to wider range of PHP platforms, while the c extension is
intended to provide higher performance. Both implementations provide the same
runtime APIs and share the same generated code. Users don’t need to re-generate
code for the same proto definition when they want to switch the implementation
later.

Both implementations make use of generated PHP code that defines message and
enum types in PHP. We strongly recommend using protoc's PHP generation support
with .proto files. The build process in this directory only installs the
extension/package; you need to install protoc as well to have PHP code
generation functionality.

Requirements

For information on supported language versions and how the support levels for
PHP versions will change over time, see
Supported PHP versions.

Installation

C Extension

Prerequirements

To install the c extension, the following tools are needed: * gcc * libtool *
make * pear * pecl * phpize

On Ubuntu, you can install them with:

text
sudo apt-get install -y php-pear php-dev libtool make gcc

On other platforms, please use the corresponding package managing tool to
install them before proceeding.

Installation from Source (Building extension)

To build the c extension, run the following command:

text
cd ext/google/protobuf
pear package
sudo pecl install protobuf-{VERSION}.tgz

Installation from PECL

When we release a version of Protocol Buffers, we will upload the extension to
PECL. To use this pre-packaged extension, simply
install it as you would any other extension:

text
sudo pecl install protobuf-{VERSION}

PHP Package

Installation from composer

Simply add "google/protobuf" to the 'require' section of composer.json in your
project.

To use the pure PHP implementation, you need to install bcmath.

Protoc

Once the extension or package is installed, if you wish to generate PHP code
from a .proto file, you will also want to install the Protocol Buffers
compiler (protoc), as described in this repository's main README file. The
version of protoc included in the latest release supports the --php_out
option to generate PHP code:

text
protoc --php_out=out_dir test.proto

Usage

For generated code:
https://developers.google.com/protocol-buffers/docs/reference/php-generated

Known Issues

  • Missing native support for well known types.
  • Missing support for proto2.
  • No API provided for clear/copy messages.
  • No API provided for encoding/decoding with stream.
  • Map fields may not be garbage-collected if there is cycle reference.
  • No debug information for messages in c extension.
  • HHVM not tested.
  • C extension not tested on windows, mac, php 7.0.
  • Message name cannot be Empty.

Development

Test Native PHP

text
# Install Dependencies (Linux)
apt-get install bazel composer php-dev

# Download protobuf
git clone https://github.com/protocolbuffers/protobuf.git
cd protobuf

# Build protoc
bazel build :protoc

# Test native php
cd php
composer install
composer test

Test C Extension

After you have finished testing the native php, you can test the c extension:

text
cd tests
./test.sh 5.6 # The php runtime version.
              # We provide 5.5, 5.5-zts, 5.6, 5.6-zts, 7.0, 7.0-zts, 7.1, 7.1-zts, 7.2, 7.2-zts, 7.3 and 7.3-zts
              # ls /usr/local for more details

If you want to use gdb to debug the c extension, you can do:

text
./gdb_test.sh

Overview (`pkg/README.md`)

Protobuf packaging

This directory contains Bazel rules for building packaging and distribution
artifacts.

Everything in this directory should be considered internal and subject to
change.

Protocol compiler binary packaging

The protocol compiler is used in binary form in various places. There are rules
which package it, along with commonly used .proto files, for distribution.

Source distribution packaging

Protobuf releases include source distributions, sliced by target language (C++,
Java, etc.). There are rules in this package to define those source archives.
These depend upon pkg_files rules elsewhere in the repo to get the contents.

The source distribution files should include the outputs from autogen.sh, but
this isn't something we can reliably do from Bazel. To produce fully functioning
source distributions, run autogen.sh before building the archives (this
populates the necessary files directly into the source tree).

C++ runtime binary distribution

The cc_dist_library rule creates composite libraries from several other
cc_library targets. Bazel uses a "fine-grained" library model, where each
cc_library produces its own library artifacts, without transitive
dependencies. The cc_dist_library rule combines several other libraries
together, creating a single library that may be suitable for distribution.


Overview (`python/README.md`)

Protocol Buffers Python

This directory contains the Protobuf library for Python.

For user documentation about how to use Protobuf Python, see
https://protobuf.dev/getting-started/pythontutorial/

Installation

In most cases you should install the library using pip or another package
manager:

text
$ pip install protobuf

The packages released on https://pypi.org/project/protobuf/#files include both a
source distribution and binary wheels.

Building packages from this repo

If for some reason you wish to build the packages directly from this repo, you
can use the following Bazel commands:

text
$ bazel build //python/dist:source_wheel
$ bazel build //python/dist:binary_wheel

The binary wheel will build against whatever version of Python is installed on
your system. The source package is always the same and does not depend on a
local version of Python.

Building from `setup.py`

We support building from setup.py, but only from a Python source package.
You cannot build from setup.py using the GitHub repo or the GitHub source
tarball.

To build a source package from this repo, see the instructions in the previous
section.

Implementation backends

There are three separate implementations of Python Protobuf. All of them offer
the same API and are thus functionally the same, though they have very different
performance characteristics.

The runtime library contains a switching layer that can choose between these
backends at runtime. Normally it will choose between them automatically, using
priority-ordered list, and skipping any backends that are not available. However
you can request a specific backend by setting the
PROTOCOL_BUFFERS_PYTHON_IMPLEMENTATION environment variable to one of the
following values:

  1. upb: Built on the
    upb C library,
    this is a new extension module
    released in 4.21.0. It offers
    better performance than any of the previous backends, and it is now the
    default. It is distributed in our PyPI packages, and requires no special
    installation. The code for this module lives in this directory.
  2. cpp: This extension module wraps the C++ protobuf library. It is
    deprecated and is no longer released in our PyPI packages, however it is
    still used in some legacy cases where apps want to perform zero-copy message
    sharing between Python and C++. It must be installed separately before it
    can be used. The code for this module lives in
    google/protobuf/pyext.
  3. python: The pure-Python backend, this does not require any extension
    module to be present on the system. The code for the pure-Python backend
    lives in google/protobuf/internal

The order given above is the general priority order, with upb being preferred
the most and the python backend preferred the least. However this ordering can
be overridden by the presence of a
google.protobuf.internal._api_implementation module. See the logic in
api_implementation.py
for details.

You can check which backend you are using with the following snippet:

text
$ python
Python 3.10.9 (main, Dec  7 2022, 13:47:07) [GCC 12.2.0] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> from google.protobuf.internal import api_implementation
>>> print(api_implementation.Type())
upb

This is not an officially-supported or stable API, but it is useful for ad hoc
diagnostics.

More information about sharing messages between Python and C++ is available
here: https://protobuf.dev/reference/python/python-generated/#sharing-messages

Code generator

The code for the Protobuf Python code generator lives in
//src/google/protobuf/compiler/python.
The code generator can output two different files for each proto foo.proto:

  • foo_pb2.py: The module you import to actually use the protos.
  • foo_pb2.pyi: A stub file that describes the interface of the protos.

The foo_pb2.pyi file is useful for IDEs or for users who want to read the
output file. The foo_pb2.py file is optimized for fast loading and is not
readable at all.

Note that the pyi file is only generated if you pass the pyi_out option to
protoc:

text
$ protoc --python_out=pyi_out:output_dir

Overview (`ruby/README.md`)

This directory contains the Ruby extension that implements Protocol Buffers
functionality in Ruby.

The Ruby extension makes use of generated Ruby code that defines message and
enum types in a Ruby DSL. You may write definitions in this DSL directly, but we
recommend using protoc's Ruby generation support with .proto files. The build
process in this directory only installs the extension; you need to install
protoc as well to have Ruby code generation functionality. You can build protoc
from source using bazel build //:protoc.

Installation from Gem

In Gemfile (Please check which version of Protocol Buffers you need: RubyGems):

text
gem 'google-protobuf'

Or for using this pre-packaged gem, simply install it as you would any other gem:

text
$ gem install [--prerelease] google-protobuf

Once the gem is installed, you may or may not need protoc. If you write your
message type descriptions directly in the Ruby DSL, you do not need it.
However, if you wish to generate the Ruby DSL from a .proto file, you will
also want to install Protocol Buffers itself, as described in this repository's
main README file. The version of protoc included in the latest release
supports the --ruby_out option to generate Ruby code.

A simple example of using the Ruby extension follows. More extensive
documentation may be found on
rubydoc.info.

ruby
require 'google/protobuf'

# generated from my_proto_types.proto with protoc:
#  $ protoc --ruby_out=. my_proto_types.proto
require 'my_proto_types'

mymessage = MyTestMessage.new(:field1 => 42, :field2 => ["a", "b", "c"])
mymessage.field1 = 43
mymessage.field2.push("d")
mymessage.field3 = SubMessage.new(:foo => 100)

encoded_data = MyTestMessage.encode(mymessage)
decoded = MyTestMessage.decode(encoded_data)
assert_equal mymessage, decoded
puts "JSON:"
puts MyTestMessage.encode_json(mymessage)

Installation from Source (Building Gem)

Protocol Buffers has a new experimental backend that uses the
ffi gem to provide a unified C-based
implementation across Ruby interpreters based on
UPB. For now, use
of the FFI implementation is opt-in. If any of the following are true, the
traditional platform-native implementations (MRI-ruby based on CRuby, Java based
on JRuby) are used instead of the new FFI-based implementation: 1. ffi and
ffi-compiler gems are not installed 2. PROTOCOL_BUFFERS_RUBY_IMPLEMENTATION
environment variable has a value other than FFI (case-insensitive). 3. FFI is
unable to load the native library at runtime.

To build this Ruby extension, you will need:

  • Rake
  • Bundler
  • Ruby development headers
  • a C compiler

To Build the JRuby extension, you will need:

  • Maven
  • The latest version of the protobuf java library (see ../java/README.md)
  • Install JRuby via rbenv or RVM

First switch to the desired platform with rbenv or RVM.

Then install the required Ruby gems:

text
$ gem install bundler
$ bundle

Then build the Gem:

text
$ rake
$ rake clobber_package gem
$ gem install `ls pkg/google-protobuf-*.gem`

If you intend to debug the protobuf_c Ruby bindings with gdb, you can also
build a version with debug symbols enabled by setting the PROTOBUF_CONFIG
environment variable when you build the native extension:

text
$ PROTOBUF_CONFIG=dbg rake

To run the specs:

text
$ rake test

To run the specs while using the FFI-based implementation:

text
$ PROTOCOL_BUFFERS_RUBY_IMPLEMENTATION=FFI rake test

This gem includes the upb parsing and serialization library as a single-file
amalgamation. It is up-to-date with upb git commit
535bc2fe2f2b467f59347ffc9449e11e47791257.

Alternatively, you can use Bazel to build and to run tests.

From the project root (rather than the ruby directory):

text
$ bazel test //ruby/tests/...

To run tests against the FFI implementation:

text
$ bazel test //ruby/tests/... //ruby:ffi_enabled --test_env=PROTOCOL_BUFFERS_RUBY_IMPLEMENTATION=FFI

Version Number Scheme

We are using a version number scheme that is a hybrid of Protocol Buffers'
overall version number and some Ruby-specific rules. Gem does not allow
re-uploads of a gem with the same version number, so we add a sequence number
("upload version") to the version. We also format alphabetical tags (alpha,
pre, ...) slightly differently, and we avoid hyphens. In more detail:

  • First, we determine the prefix: a Protocol Buffers version "3.0.0-alpha-2"
    becomes "3.0.0.alpha.2". When we release 3.0.0, this prefix will be simply
    "3.0.0".
  • We then append the upload version: "3.0.0.alpha.2.0" or "3.0.0.0". If we need
    to upload a new version of the gem to fix an issue, the version becomes
    "3.0.0.alpha.2.1" or "3.0.0.1".
  • If we are working on a prerelease version, we append a prerelease tag:
    "3.0.0.alpha.3.0.pre". The prerelease tag comes at the end so that when
    version numbers are sorted, any prerelease builds are ordered between the
    prior version and current version.

These rules are designed to work with the sorting rules for
Gem::Version:
release numbers should sort in actual release order.


Overview (`src/README.md`)

Protocol Buffers - Google's data interchange format

Copyright 2008 Google Inc.

https://protobuf.dev.

CMake Installation

To compile or install protobuf from source using CMake, see
cmake/README.md.

C++ Protobuf - Unix

To build protobuf from source, the following tools are needed:

  • Bazel
  • git
  • g++
  • Abseil

On Ubuntu/Debian, for example, you can install them with:

text
sudo apt-get install g++ git bazel

On other platforms, use the corresponding package managing tool to install them
before proceeding. See https://bazel.build/install for further instructions on
installing Bazel, or to build from source using CMake, see
cmake/README.md. See https://github.com/abseil/abseil-cpp
for instructions on installing Abseil.

To get the source, download the release .tar.gz or .zip package in the
release page:

text
https://github.com/protocolbuffers/protobuf/releases/latest

You can also get the source by "git clone" our git repository. Make sure you
have also cloned the submodules and generated the configure script (skip this
if you are using a release .tar.gz or .zip package):

text
git clone https://github.com/protocolbuffers/protobuf.git
cd protobuf
git submodule update --init --recursive

To build the C++ Protocol Buffer runtime and the Protocol Buffer compiler
(protoc) execute the following:

text
bazel build :protoc :protobuf

You can then install the compiler, for example on Linux:

text
cp bazel-bin/protoc /usr/local/bin

For more usage information on Bazel, see http://bazel.build.

Compiling dependent packages

To compile a package that uses Protocol Buffers, you need to setup a Bazel
WORKSPACE that's hooked up to the protobuf repository and loads its
dependencies. For an example, see WORKSPACE.

Note for Mac users

For a Mac system, Unix tools are not available by default. You will first need
to install Xcode from the Mac AppStore and then run the following command from
a terminal:

text
sudo xcode-select --install

To install Unix tools, you can install "port" following the instructions at
https://www.macports.org . This will reside in /opt/local/bin/port for most
Mac installations.

text
sudo /opt/local/bin/port install bazel

Alternative for Homebrew users:

text
brew install bazel

Then follow the Unix instructions above.

C++ Protobuf - Windows

If you only need the protoc binary, you can download it from the release
page:

text
https://github.com/protocolbuffers/protobuf/releases/latest

In the downloads section, download the zip file protoc-$VERSION-win32.zip. It
contains the protoc binary as well as public proto files of protobuf library.
The
protoc binary and the gencode version must match.

You can install protobuf and its dependencies directly by using vcpkg:

text
>vcpkg install protobuf protobuf:x64-windows

If you want to use zlib, you'll also need to install the zlib feature:

text
>vcpkg install protobuf[zlib] protobuf[zlib]:x64-windows

See https://github.com/Microsoft/vcpkg for more information.

To build from source using Microsoft Visual C++, see cmake/README.md.

To build from source using Cygwin or MinGW, follow the Unix installation
instructions, above.

Binary Compatibility Warning

Due to the nature of C++, it is unlikely that any two versions of the
Protocol Buffers C++ runtime libraries will have compatible ABIs.
That is, if you linked an executable against an older version of
libprotobuf, it is unlikely to work with a newer version without
re-compiling. This problem, when it occurs, will normally be detected
immediately on startup of your app. Still, you may want to consider
using static linkage. You can configure this in your cc_binary Bazel rules
by specifying:

text
linkstatic=True

Usage

The complete documentation for Protocol Buffers is available at:

https://protobuf.dev/


Overview (`upb/README.md`)

μpb: small, fast C protos

μpb (often written 'upb') is a small
protobuf implementation written
in C.

upb is the core runtime for protobuf languages extensions in
Ruby,
PHP, and
Python.

While upb offers a C API, the C API & ABI are not stable. For this reason,
upb is not generally offered as a C library for direct consumption, and there
are no releases.

Features

upb has comparable speed to protobuf C++, but is an order of magnitude smaller
in code size.

Like the main protobuf implementation in C++, it supports:

  • a generated API (in C)
  • reflection
  • binary & JSON wire formats
  • text format serialization
  • all standard features of protobufs (oneofs, maps, unknown fields, extensions,
    etc.)
  • full conformance with the protobuf conformance tests

upb also supports some features that C++ does not:

  • optional reflection: generated messages are agnostic to whether
    reflection will be linked in or not.
  • no global state: no pre-main registration or other global state.
  • fast reflection-based parsing: messages loaded at runtime parse
    just as fast as compiled-in messages.

However there are a few features it does not support:

  • text format parsing
  • deep descriptor verification: upb's descriptor validation is not as exhaustive
    as protoc.

Install

For Ruby, use RubyGems:

text
$ gem install google-protobuf

For PHP, use PECL:

text
$ sudo pecl install protobuf

For Python, use PyPI:

text
$ sudo pip install protobuf

Alternatively, you can build and install upb using
vcpkg dependency manager:

text
git clone https://github.com/Microsoft/vcpkg.git
cd vcpkg
./bootstrap-vcpkg.sh
./vcpkg integrate install
./vcpkg install upb

The upb port in vcpkg is kept up to date by microsoft team members and community
contributors.

If the version is out of date, please
create an issue or pull request on the
vcpkg repository.

Contributing

Please see CONTRIBUTING.md.


Overview (`.github/workflows/README.md`)

This directory contains all of our automatically triggered workflows.

Test runner

Our top level test_runner.yml is responsible for kicking off all tests, which
are represented as reusable workflows. This is carefully constructed to satisfy
the design laid out in go/protobuf-gha-protected-resources (see below), and
duplicating it across every workflow file would be difficult to maintain. As an
added bonus, we can manually dispatch our full test suite with a single button
and monitor the progress of all of them simultaneously in GitHub's actions UI.

There are five ways our test suite can be triggered:

  • Post-submit tests (push): These are run over newly submitted code
    that we can assume has been thoroughly reviewed. There are no additional
    security concerns here and these jobs can be given highly privileged access to
    our internal resources and caches.

  • Pre-submit tests from a branch (push_request): These are run over
    every PR as changes are made. Since they are coming from branches in our
    repository, they have secret access by default and can also be given highly
    privileged access. However, we expect many of these events per change,
    and likely many from abandoned/exploratory changes. Given the much higher
    frequency, we restrict the ability to write to our more expensive caches.

  • Pre-submit tests from a fork (pull_request_target): These are run
    over every PR from a forked repository as changes are made. These have much
    more restricted access, since they could be coming from anywhere. To protect
    our secret keys and our resources, tests will not run until a commit has been
    labeled safe to submit. Further commits will require further approvals to
    run our test suite. Once marked as safe, we will provide read-only access to
    our caches and Docker images, but will generally disallow any writes to shared
    resources.

  • Continuous tests (schedule): These are run on a fixed schedule. We
    currently have them set up to run daily, and can help identify non-hermetic
    issues in tests that don't get run often (such as due to test caching) or during
    slow periods like weekends and holidays. Similar to post-submit tests, these
    are run over submitted code and are highly privileged in the resources they
    can use.

  • Manual testing (workflow_dispatch): Our test runner can be triggered
    manually over any branch. This is treated similarly to pre-submit tests,
    which should be highly privileged because they can only be triggered by the
    protobuf team.

Staleness handling

While Bazel handles code generation seamlessly, we do support build systems that
don't. There are a handful of cases where we need to check in generated files
that can become stale over time. In order to provide a good developer
experience, we've implemented a system to make this more manageable.

  • Stale files should have a corresponding staleness_test Bazel target. This
    should be marked manual to avoid getting picked up in CI, but will fail if
    files become stale. It also provides a --fix flag to update the stale files.

  • Bazel tests will never depend on the checked-in versions, and will generate
    new ones on-the-fly during build.

  • Non-Bazel tests will always regenerate necessary files before starting. This
    is done using our bash and docker actions, which should be used for any
    non-Bazel tests. This way, no tests will fail due to stale files.

  • A post-submit job will immediately regenerate any stale files and commit them
    if they've changed.

  • A scheduled job will run late at night every day to make sure the post-submit
    is working as expected (that is, it will run all the staleness tests).

The regenerate_stale_files.sh script is the central script responsible for all
the re-generation of stale files.

Forked PRs

Because we need secret access to run our tests, we use the pull_request_target
event for PRs coming from forked repositories. We do checkout the code from the
PR's head, but the workflow files themselves are always fetched from the base
branch (that is, the branch we're merging to). Therefore, any changes to these
files won't be tested, so we explicitly ban PRs that touch these files.

Caches

We have a number of different caching strategies to help speed up tests. These
live either in GCP buckets or in our GitHub repository cache. The former has
a lot of resources available and we don't have to worry as much about bloat.
On the other hand, the GitHub repository cache is limited to 10GB, and will
start pruning old caches when it exceeds that threshold. Therefore, we need
to be very careful about the size and quantity of our caches in order to
maximize the gains.

Bazel remote cache

As described in https://bazel.build/remote/caching, remote caching allows us to
offload a lot of our build steps to a remote server that holds a cache of
previous builds. We use our GCP project for this storage, and configure
every Bazel call to use it. This provides substantial performance
improvements at minimal cost.

We do not allow forked PRs to upload updates to our Bazel caches, but they
do use them. Every other event is given read/write access to the caches.
Because Bazel behaves poorly under certain environment changes (such as
toolchain, operating system), we try to use finely-grained caches. Each job
should typically have its own cache to avoid cross-pollution.

Bazel repository cache

When Bazel starts up, it downloads all the external dependencies for a given
build and stores them in the repository cache. This cache is separate from
the remote cache, and only exists locally. Because we have so many Bazel
dependencies, this can be a source of frequent flakes due to network issues.

To avoid this, we keep a cached version of the repository cache in GitHub's
action cache. Our full set of repository dependencies ends up being ~300MB,
which is fairly expensive given our 10GB maximum. The most expensive ones seem
to come from Java, which has some very large downstream dependencies.

Given the cost, we take a more conservative approach for this cache. Only push
events will ever write to this cache, but all events can read from them.
Additionally, we only store three caches for any given commit, one per platform.
This means that multiple jobs are trying to update the same cache, leading to a
race. GitHub rejects all but one of these updates, so we designed the system so
that caches are only updated if they've actually changed. That way, over time
(and multiple pushes) the repository caches will incrementally grow to encompass
all of our dependencies. A scheduled job will run monthly to clear these caches
to prevent unbounded growth as our dependencies evolve.

ccache

In order to speed up non-Bazel builds to be on par with Bazel, we make use of
ccache. This intercepts all calls to the compiler, and
caches the result. Subsequent calls with a cache-hit will very quickly
short-circuit and return the already computed result. This has minimal affect
on any single job, since we typically only run a single build. However, by
caching the ccache results in GitHub's action cache we can substantially
decrease the build time of subsequent runs.

One useful feature of ccache is that you can set a maximum cache size, and it
will automatically prune older results to keep below that limit. On Linux and
Mac cmake builds, we generally get 30MB caches and set a 100MB cache limit. On
Windows, with debug symbol stripping we get ~70MB and set a 200MB cache limit.

Because CMake build tend to be our slowest, bottlenecking the entire CI process,
we use a fairly expensive strategy with ccache. All events will cache their
ccache directory, keyed by the commit and the branch. This means that each
PR and each branch will write its own set of caches. When looking up which
cache to use initially, each job will first look for a recent cache in its
current branch. If it can't find one, it will accept a cache from the base
branch (for example, PRs will initially use the latest cache from their target
branch).

While the ccache caches quickly over-run our GitHub action cache, they also
quickly become useless. Since GitHub prunes caches based on the time they were
last used, this just means that we'll see quicker turnover.

sccache

An alternative to ccache is sccache. The
two tools are very similar in function, but sccache requires (and allows) much
less configuration and supports GCS storage right out of the box. By hooking
this up to our project that we already use for Bazel caching, we're able to get
even bigger CMake wins in CI because we're no longer constrained by GitHub's
10GB cache limit.

Similar to the Bazel remote cache, we give read access to every CI run, but
disallow writing in PRs from forks.

Bazelisk

Bazelisk will automatically download a pinned version of Bazel on first use.
This can lead to flakes, and to avoid that we cache the result keyed on the
Bazel version. Only push events will write to this cache, but it's unlikely
to change very often.

Docker images

Instead of downloading a fresh Docker image for every test run, we can save it
as a tar and cache it using docker image save and later restore using
docker image load. This can decrease download times and also reduce flakes.
Note, Docker's load can actually be significantly slower than a pull in certain
situations. Therefore, we should reserve this strategy for only Docker images
that are causing noticeable flakes.

Pip dependencies

The actions/setup-python action we use for Python supports automated caching
of pip dependencies. We enable this to avoid having to download these
dependencies on every run, which can lead to flakes.

Custom actions

We've defined a number of custom actions to abstract out shared pieces of our
workflows.

  • Bazel use this for running all Bazel tests. It can take either a single
    Bazel command or a more general bash command. In the latter case, it provides
    environment variables for running Bazel with all our standardized settings.

  • Bazel-Docker nearly identical to the Bazel action, this additionally
    runs everything in a specified Docker image.

  • Bash use this for running non-Bazel tests. It takes a bash command and
    runs it verbatim. It also handles the regeneration of stale files (which does
    use Bazel), which non-Bazel tests might depend on.

  • Docker nearly identical to the Bash action, this additionally runs
    everything in a specified Docker image.

  • ccache this sets up a ccache environment, and initializes some
    environment variables for standardized usage of ccache.

  • Cross-compile protoc this abstracts out the compilation of protoc using
    our cross-compilation infrastructure. It will set a PROTOC environment
    variable that gets automatically picked up by a lot of our infrastructure.
    This is most useful in conjunction with the Bash action with non-Bazel
    tests.


Overview (`csharp/keys/README.md`)

Contents

  • Google.Protobuf.public.snk:
    Public key to verify strong name of Google.Protobuf assemblies.
  • Google.Protobuf.snk:
    Signing key to provide strong name of Google.Protobuf assemblies.
    As per Microsoft guidance
    signing key should be checked into the repository.

Overview (`csharp/protos/README.md`)

This directory contains unit test protos adapted from those in
src/google/protobuf, and C#-specific test protos for regression
tests against bugs found in the C# codegen or library.


Overview (`java/protoc/README.md`)

Publish pre-compiled protoc artifacts

protoc is the compiler for .proto files. It generates language bindings
for the messages and/or RPC services from .proto files.

Because protoc is a native executable, the scripts under this directory
publish a protoc executable (a.k.a. artifact) to Maven repositories. The
artifact can be used by build automation tools so that users would not need to
compile and install protoc for their systems.

If you would like us to publish protoc artifact for a new platform, please
open an issue to request it.

Maven Location

The published protoc artifacts are available on Maven here:

text
https://repo.maven.apache.org/maven2/com/google/protobuf/protoc/

Versioning

The version of the protoc artifact must be the same as the version of the
Protobuf project.

Artifact name

The name of a published protoc artifact is in the following format:
protoc-<version>-<os>-<arch>.exe, e.g., protoc-3.6.1-linux-x86_64.exe.

Note that artifacts for linux/macos also have the .exe suffix but they are
not windows binaries.

System requirement

Install Apache Maven if you don't have it.

The scripts only work under Unix-like environments, e.g., Linux, MacOSX, and
Cygwin or MinGW for Windows. Please see README.md of the Protobuf project
for how to set up the build environment.

Tested build environments

We have successfully built artifacts on the following environments:

  • Linux x86_32 and x86_64:
    • Centos 6.9 (within Docker 1.6.1)
    • Ubuntu 14.04.5 64-bit
  • Linux aarch_64: Cross compiled with g++-aarch64-linux-gnu on Ubuntu 14.04.5 64-bit
  • Mac OS X x86_32 and x86_64: Mac OS X 10.9.5

Overview (`python/protobuf_distutils/README.md`)

Python setuptools extension

This is an extension for Python setuptools which uses an installed protobuf
compiler (protoc) to generate Python sources.

Installing

To use this extension, it needs to be installed so it can be imported by other
projects' setup.py.

shell
$ python setup.py build
$ python -m pip install .

(If you want to test changes to the extension, you can use python setup.py develop.)

Usage

Example setup.py configuration

python
from setuptools import setup
setup(
    # ...
    name='example_project',

    # Require this package, but only for setup (not installation):
    setup_requires=['protobuf_distutils'],

    options={
        # See below for details.
        'generate_py_protobufs': {
            'source_dir':        'path/to/protos',
            'extra_proto_paths': ['path/to/other/project/protos'],
            'output_dir':        'path/to/project/sources',  # default '.'
            'proto_files':       ['relative/path/to/just_this_file.proto'],
            'protoc':            'path/to/protoc.exe',
        },
    },
)

Example build invocation

These steps will generate protobuf sources so they are included when building
and installing example_project (see above):

shell
$ python setup.py generate_py_protobufs
$ python setup.py build
$ python -m pip install .

Options

  • source_dir:

    This is the directory holding .proto files to be processed.

    The default behavior is to generate sources for all .proto files found under
    source_dir, recursively. This behavior can be controlled with options below.

  • proto_root_path:

    This is the root path for resolving imports in source .proto files.

    The default is the shortest prefix of source_dir among [source_dir] + self.extra_proto_paths.

  • extra_proto_paths:

    Specifies additional paths that should be used to find imports, in
    addition to source_dir.

    This option can be used to specify the path to other protobuf sources,
    which are imported by files under source_dir. No Python code will
    be generated for .proto files under extra_proto_paths.

  • output_dir:

    Specifies where generated code should be placed.

    Typically, this should be the root package that generated Python modules
    should be below.

    The generated files will be placed under output_dir according to the
    relative source paths under proto_root_path. For example, the source file
    ${proto_root_path}/subdir/message.proto will be generated as the Python
    module ${output_dir}/subdir/message_pb2.py.

  • proto_files:

    A list of strings, specific .proto file paths for generating code, instead of
    searching for all .proto files under source_path.

    These paths are relative to source_dir. For example, to generate code
    for just ${source_dir}/subdir/message.proto, specify
    ['subdir/message.proto'].

  • protoc:

    By default, the protoc binary (the Protobuf compiler) is found by
    searching the environment path. To use a specific protoc binary, its
    path can be specified. Resolution of the protoc value is as follows:

    1. If the --protoc=VALUE flag is passed to generate_py_protobufs,
      then VALUE will be used.
      For example:
      shell
      $ python setup.py generate_py_protobufs --protoc=/path/to/protoc
    2. Otherwise, if a value was set in the options, it will be used.
      (See "Example setup.py configuration," above.)
    3. Otherwise, if the PROTOC environment variable is set, it will be
      used. For example:
      For example:
      shell
      $ PROTOC=/path/to/protoc python setup.py generate_py_protobufs
    4. Otherwise, $PATH will be searched.

Overview (`rust/release_crates/README.md`)

This directory contains one directory per crate that we release.

Each directory contains a pkg_tar() which packages up the crate into a tar which
can be pushed to crates.io.

New development happens in the google_protobuf* crates. The legacy
protobuf* crates are kept as re-exports of their google_protobuf*
equivalents so that existing users keep building, but they set
publish = false and are no longer pushed to crates.io.


Overview (`rust/upb/README.md`)

Rust upb bindings

This directory contains Rust bindings for upb, attempting to be the a direct
mapping to expose upb to Rust. This should not be used directly by application
code, instead is an implementation detail for Rust Protobuf, which is still
under development but can be found here:
here.


Overview (`third_party/utf8_range/README.md`)

Fast UTF-8 validation with Range algorithm (NEON+SSE4+AVX2)

This is a brand new algorithm to leverage SIMD for fast UTF-8 string validation. Both NEON(armv8a) and SSE4 versions are implemented. AVX2 implementation contributed by ioioioio.

Four UTF-8 validation methods are compared on both x86 and Arm platforms. Benchmark result shows range base algorithm is the best solution on Arm, and achieves same performance as Lemire's approach on x86.

  • Range based algorithm
    • range-neon.c: NEON version
    • range-sse.c: SSE4 version
    • range-avx2.c: AVX2 version
    • range2-neon.c, range2-sse.c: Process two blocks in one iteration
  • Lemire's SIMD implementation
    • lemire-sse.c: SSE4 version
    • lemire-avx2.c: AVX2 version
    • lemire-neon.c: NEON porting
  • naive.c: Naive UTF-8 validation byte by byte
  • lookup.c: Lookup-table method

About the code

  • Run "make" to build. Built and tested with gcc-7.3.
  • Run "./utf8" to see all command line options.
  • Benchmark
    • Run "./utf8 bench" to benchmark all algorithms with default test file.
    • Run "./utf8 bench size NUM" to benchmark specified string size.
  • Run "./utf8 test" to test all algorithms with positive and negative test cases.
  • To benchmark or test specific algorithm, run something like "./utf8 bench range".

Benchmark result (MB/s)

Method

  1. Generate UTF-8 test buffer per test file or buffer size.
  2. Call validation sub-routines in a loop until 1G bytes are checked.
  3. Calculate speed(MB/s) of validating UTF-8 strings.

NEON(armv8a)

Test case naive lookup lemire range range2
UTF-demo.txt 562.25 412.84 1198.50 1411.72 1579.85
32 bytes 651.55 441.70 891.38 1003.95 1043.58
33 bytes 660.00 446.78 588.77 1009.31 1048.12
129 bytes 771.89 402.55 938.07 1283.77 1401.76
1K bytes 811.92 411.58 1188.96 1398.15 1560.23
8K bytes 812.25 412.74 1198.90 1412.18 1580.65
64K bytes 817.35 412.24 1200.20 1415.11 1583.86
1M bytes 815.70 411.93 1200.93 1415.65 1585.40

SSE4(E5-2650)

Test case naive lookup lemire range range2
UTF-demo.txt 753.70 310.41 3954.74 3945.60 3986.13
32 bytes 1135.76 364.07 2890.52 2351.81 2173.02
33 bytes 1161.85 376.29 1352.95 2239.55 2041.43
129 bytes 1161.22 322.47 2742.49 3315.33 3249.35
1K bytes 1310.95 310.72 3755.88 3781.23 3874.17
8K bytes 1348.32 307.93 3860.71 3922.81 3968.93
64K bytes 1301.34 308.39 3935.15 3973.50 3983.44
1M bytes 1279.78 309.06 3923.51 3953.00 3960.49

Range algorithm analysis

Basic idea:

  • Load 16 bytes
  • Leverage SIMD to calculate value range for each byte efficiently
  • Validate 16 bytes at once

UTF-8 coding format

http://www.unicode.org/versions/Unicode6.0.0/ch03.pdf, page 94

Table 3-7. Well-Formed UTF-8 Byte Sequences

Code Points First Byte Second Byte Third Byte Fourth Byte
U+0000..U+007F 00..7F
U+0080..U+07FF C2..DF 80..BF
U+0800..U+0FFF E0 A0..BF 80..BF
U+1000..U+CFFF E1..EC 80..BF 80..BF
U+D000..U+D7FF ED 80..9F 80..BF
U+E000..U+FFFF EE..EF 80..BF 80..BF
U+10000..U+3FFFF F0 90..BF 80..BF 80..BF
U+40000..U+FFFFF F1..F3 80..BF 80..BF 80..BF
U+100000..U+10FFFF F4 80..8F 80..BF 80..BF

To summarise UTF-8 encoding:

  • Depending on First Byte, one legal character can be 1, 2, 3, 4 bytes
    • For First Byte within C0..DF, character length = 2
    • For First Byte within E0..EF, character length = 3
    • For First Byte within F0..F4, character length = 4
  • C0, C1, F5..FF are not allowed
  • Second,Third,Fourth Bytes must lie in 80..BF.
  • There are four special cases for Second Byte, shown bold italic in above table.

Range table

Range table maps range index 0 ~ 15 to minimal and maximum values allowed. Our task is to observe input string, find the pattern and set correct range index for each byte, then validate input string.

Index Min Max Byte type
0 00 7F First Byte, ASCII
1,2,3 80 BF Second, Third, Fourth Bytes
4 A0 BF Second Byte after E0
5 80 9F Second Byte after ED
6 90 BF Second Byte after F0
7 80 8F Second Byte after F4
8 C2 F4 First Byte, non-ASCII
9..15(NEON) FF 00 Illegal: unsigned char >= 255 && unsigned char <= 0
9..15(SSE) 7F 80 Illegal: signed char >= 127 && signed char <= -128

Calculate byte ranges (ignore special cases)

Ignoring the four special cases(E0,ED,F0,F4), how should we set range index for each byte?

  • Set range index to 0(00..7F) for all bytes by default
  • Find non-ASCII First Byte (C0..FF), set their range index to 8(C2..F4)
  • For First Byte within C0..DF, set next byte's range index to 1(80..BF)
  • For First Byte within E0..EF, set next two byte's range index to 2,1(80..BF) in sequence
  • For First Byte within F0..FF, set next three byte's range index to 3,2,1(80..BF) in sequence

To implement above operations efficiently with SIMD:

  • For 16 input bytes, use lookup table to map C0..DF to 1, E0..EF to 2, F0..FF to 3, others to 0. Save to first_len.
  • Map C0..FF to 8, we get range indices for First Byte.
  • Shift first_len one byte, we get range indices for Second Byte.
  • Saturate subtract first_len by one(3->2, 2->1, 1->0, 0->0), then shift two bytes, we get range indices for Third Byte.
  • Saturate subtract first_len by two(3->1, 2->0, 1->0, 0->0), then shift three bytes, we get range indices for Fourth Byte.

Example(assume no previous data)

Input F1 80 80 80 80 C2 80 80 ...
first_len 3 0 0 0 0 1 0 0 ...
First Byte 8 0 0 0 0 8 0 0 ...
Second Byte 0 3 0 0 0 0 1 0 ...
Third Byte 0 0 2 0 0 0 0 0 ...
Fourth Byte 0 0 0 1 0 0 0 0 ...
Range index 8 3 2 1 0 8 1 0 ...
c
Range_index = First_Byte | Second_Byte | Third_Byte | Fourth_Byte

Error handling

  • C0,C1,F5..FF are not included in range table and will always be detected.
  • Illegal 80..BF will have range index 0(00..7F) and be detected.
  • Based on First Byte, according Second, Third and Fourth Bytes will have range index 1/2/3, to make sure they must lie in 80..BF.
  • If non-ASCII First Byte overlaps, above algorithm will set range index of the latter First Byte to 9,10,11, which are illegal ranges. E.g, Input = F1 80 C2 90 --> Range index = 8 3 10 1, where 10 indicates error. See table below.

Overlapped non-ASCII First Byte

Input F1 80 C2 90
first_len 3 0 1 0
First Byte 8 0 8 0
Second Byte 0 3 0 1
Third Byte 0 0 2 0
Fourth Byte 0 0 0 1
Range index 8 3 10 1

Adjust Second Byte range for special cases

Range index adjustment for four special cases

First Byte Second Byte Before adjustment Correct index Adjustment
E0 A0..BF 2 4 2
ED 80..9F 2 5 3
F0 90..BF 3 6 3
F4 80..8F 3 7 4

Range index adjustment can be reduced to below problem:

Given 16 bytes, replace E0 with 2, ED with 3, F0 with 3, F4 with 4, others with 0.

A naive SIMD approach:

  1. Compare 16 bytes with E0, get the mask for eacy byte (FF if equal, 00 otherwise)
  2. And the mask with 2 to get adjustment for E0
  3. Repeat step 1,2 for ED,F0,F4

At least eight operations are required for naive approach.

Observing special bytes(E0,ED,F0,F4) are close to each other, we can do much better using lookup table.

NEON

NEON tbl instruction is very convenient for table lookup:

  • Table can be up to 16x4 bytes in size
  • Return zero if index is out of range

Leverage these features, we can solve the problem with as few as two operations:

  • Precreate a 16x2 lookup table, where table[0]=2, table[13]=3, table[16]=3, table[20]=4, table[others]=0.
  • Subtract input bytes with E0 (E0 -> 0, ED -> 13, F0 -> 16, F4 -> 20).
  • Use the subtracted byte as index of lookup table and get range adjustment directly.
    • For indices less than 32, we get zero or required adjustment value per input byte
    • For out of bound indices, we get zero per tbl behaviour

SSE

SSE pshufb instruction is not as friendly as NEON tbl in this case:

  • Table can only be 16 bytes in size
  • Out of bound indices are handled this way:
    • If 7-th bit of index is 0, least four bits are used as index (E.g, index 0x73 returns 3rd element)
    • If 7-th bit of index is 1, return 0 (E.g, index 0x83 returns 0)

We can still leverage these features to solve the problem in five operations:

  • Precreate two tables:
    • table_df[1] = 2, table_df[14] = 3, table_df[others] = 0
    • table_ef[1] = 3, table_ef[5] = 4, table_ef[others] = 0
  • Subtract input bytes with EF (E0 -> 241, ED -> 254, F0 -> 1, F4 -> 5) to get the temporary indices
  • Get range index for E0,ED
    • Saturate subtract temporary indices with 240 (E0 -> 1, ED -> 14, all values below 240 becomes 0)
    • Use subtracted indices to look up table_df, get the correct adjustment
  • Get range index for F0,F4
    • Saturate add temporary indices with 112(0x70) (F0 -> 0x71, F4 -> 0x75, all values above 16 will be larger than 128(7-th bit set))
    • Use added indices to look up table_ef, get the correct adjustment (index 0x71,0x75 returns 1st,5th elements, per pshufb behaviour)

Error handling

  • For overlapped non-ASCII First Byte, range index before adjustment is 9,10,11. After adjustment (adds 2,3,4 or 0), the range index will be 9 to 15, which is still illegal in range table. So the error will be detected.

Handling remaining bytes

For remaining input less than 16 bytes, we will fallback to naive byte by byte approach to validate them, which is actually faster than SIMD processing.

  • Look back last 16 bytes buffer to find First Byte. At most three bytes need to look back. Otherwise we either happen to be at character boundray, or there are some errors we already detected.
  • Validate string byte by byte starting from the First Byte.

Tests

It's necessary to design test cases to cover corner cases as more as possible.

Positive cases

  1. Prepare correct characters
  2. Validate correct characters
  3. Validate long strings
    • Round concatenate characters starting from first character to 1024 bytes
    • Validate 1024 bytes string
    • Shift 1 byte, validate 1025 bytes string
    • Shift 2 bytes, Validate 1026 bytes string
    • ...
    • Shift 16 bytes, validate 1040 bytes string
  4. Repeat step3, test buffer starting from second character
  5. Repeat step3, test buffer starting from third character
  6. ...

Negative cases

  1. Prepare bad characters and bad strings
    • Bad character
    • Bad character cross 16 bytes boundary
    • Bad character cross last 16 bytes and remaining bytes boundary
  2. Test long strings
    • Prepare correct long strings same as positive cases
    • Append bad characters
    • Shift one byte for each iteration
    • Validate each shift

Code breakdown

Below table shows how 16 bytes input are processed step by step. See range-neon.c for according code.


Overview (`upb/util/README.md`)

upb util library

The libraries in this directory contain useful functionality that is layered
on top of the main upb APIs. In other words, the APIs in this directory have
no special access to upb internals; you could easily implement the same things
yourself.


Overview (`upb_generator/common/README.md`)

This directory contains APIs that are used by multiple code generators, but not
public to users.


Overview (`bazel/private/oss/README.md`)

Bazel-specific Build Rules

This directory contains rule implementations that are specific to Bazel (ie.
where Blaze and Bazel rules have diverged).


Overview (`docs/design/editions/README.md`)

Protocol Buffers - Protobuf Editions design documents

This directory contains historical design documents that describe plans for
implementing Protobuf Editions. For an up-to-date overview of this feature of
Protocol Buffers, see
Protobuf Editions Overview.

These files represent the state that the original documents were in at the time
that they were published to this repository. While some updates may be made to
the files after their initial upload, you should consider the possibility that
they are outdated as you read them. These are purely for historical value and
should not be treated as documentation of the current state.

Note that the authors listed in the topics were the authors of the original
version of the document; it may have changed since they last worked on the
document.

Design Documents

The following topics are in this repository:


Overview (`docs/design/prototiller/README.md`)

Protocol Buffers - Prototiller design documents

This directory contains historical design documents that describe plans for
implementing Prototiller, a tool for updating proto files to be conformant with Protobuf Editions functionality. For design documents related to Protobuf Editions, see the README.

These files represent the state that the original documents were in at the time
that they were published to this repository. While some updates may be made to
the files after their initial upload, you should consider the possibility that
they are outdated as you read them. These are purely for historical value and
should not be treated as documentation of the current state.

Note that the authors listed in the topics were the authors of the original
version of the document; it may have changed since they last worked on the
document.

Design Documents

The following topics are in this repository:


Overview (`rust/release_crates/google_protobuf/README.md`)

The runtime of the official Google Rust Protobuf implementation.

This is currently a beta release: the API is subject to change, and there may be
some rough edges, including missing documentation and features.

An example for how to use this crate can be found in the
google-protobuf-example crate

Ownership and implementation change

This crate is officially supported by the Protobuf team at Google.

This is a completely new implementation with a different API, as well as a
fundamentally different approach than prior versions of the protobuf crate. It
focuses on delivering a high-quality Rust API which is backed by either a pure C
implementation (upb) or the Protobuf C++ implementation. This choice was made
for performance, feature parity, development velocity, and security reasons.

It is not planned for the pure Rust lineage to be actively developed going
forward. While it is not expected to receive significant further development, as
a stable and high quality pure Rust implementation, many open source projects
may reasonably continue to stay on the protobuf crate.

Development was previously done under the protobuf crate by a community
project by stepancheg who generously donated
the crate name to Google. V4.36.0 of the protobuf crate corresponds to V0.36.0
of this crate.

How to get a compatible version of protoc

The protoc binary that you use to generate code needs to have a version that
exactly matches the version of the protobuf crate you are using. More
specifically, if you are using Rust protobuf x.y.z then you need to use protoc
y.z. See here for more
details on our versioning scheme.

The easiest way to get ahold of protoc is to download a prebuilt binary from the
matching release here.
Just make sure protoc is on your $PATH when you run cargo.


Overview (`rust/release_crates/google_protobuf_codegen/README.md`)

The build time dependency crate for generating code for the official Google Rust
Protobuf implementation.

The code generated by this is expected to be used in conjunction with the
official Google Protobuf crate.

This is currently a beta release: the API is subject to change,
and there may be some rough edges, including missing documentation and features.

How to get a compatible version of protoc

The protoc binary that you use to generate code needs to have a version that
exactly matches the version of the protobuf crate you are using. More
specifically, if you are using Rust protobuf x.y.z then you need to use protoc
y.z. See here for more
details on our versioning scheme.

The easiest way to get ahold of protoc is to download a prebuilt binary from the
matching release here.
Just make sure protoc is on your $PATH when you run cargo.


Overview (`rust/release_crates/google_protobuf_example/README.md`)

An example that demonstrates how to use the google-protobuf and
google-protobuf-codegen crates together.

The source of the latest version of this example can be read
here.

How to get a compatible version of protoc

The protoc binary that you use to generate code needs to have a version that
exactly matches the version of the protobuf crate you are using. More
specifically, if you are using Rust protobuf x.y.z then you need to use protoc
y.z. See here for more
details on our versioning scheme.

The easiest way to get ahold of protoc is to download a prebuilt binary from the
matching release here.
Just make sure protoc is on your $PATH when you run cargo.


Overview (`rust/release_crates/google_protobuf_macros/README.md`)

Internal crate. Use google-protobuf
instead.


Overview (`rust/release_crates/google_protobuf_well_known_types/README.md`)

This crate contains the protobuf
well-known types for
the official Google Protobuf crate.


Overview (`rust/release_crates/protobuf/README.md`)

The runtime of the official Google Rust Protobuf implementation.

This is currently a beta release: the API is subject to change, and there may be
some rough edges, including missing documentation and features.

An example for how to use this crate can be found in the
protobuf_example crate

This crate is superseded by
google-protobuf.
New development
happens there; please migrate there to get the latest releases.

V4 ownership and implementation change

V4 of this crate is officially supported by the Protobuf team at Google. Prior
major versions were developed by as a community project by
stepancheg who generously donated the crate
name to Google.

V4 is a completely new implementation with a different API, as well as a
fundamentally different approach than prior versions of this crate. It focuses
on delivering a high-quality Rust API which is backed by either a pure C
implementation (upb) or the Protobuf C++ implementation. This choice was made
for performance, feature parity, development velocity, and security reasons.
More discussion about the rationale and design philosophy can be found at
https://protobuf.dev/reference/rust/.

It is not planned for the V3 pure Rust lineage to be actively developed going
forward. While it is not expected to receive significant further development, as
a stable and high quality pure Rust implementation, many open source projects
may reasonably continue to stay on the V3 API.


Overview (`rust/release_crates/protobuf_codegen/README.md`)

The build time dependency crate for generating code for the official Google Rust
Protobuf implementation.

This crate is superseded by
google-protobuf-codegen.

New development happens there; please migrate there to get the latest
releases.

The code generated by this is expected to be used in conjunction with the
official Protobuf crate.

This is currently a beta release: the API is subject to change,
and there may be some rough edges, including missing documentation and features.

How to get a compatible version of protoc

The protoc binary that you use to generate code needs to have a version that
exactly matches the version of the protobuf crate you are using. More
specifically, if you are using Rust protobuf x.y.z then you need to use protoc
y.z. See here for more
details on our versioning scheme.

The easiest way to get ahold of protoc is to download a prebuilt binary from the
matching release here.
Just make sure protoc is on your $PATH when you run cargo.


Overview (`rust/release_crates/protobuf_example/README.md`)

An example that demonstrates how to use the protobuf and protobuf_codegen
crates together.

The source of the latest version of this example can be read
here.

How to get a compatible version of protoc

The protoc binary that you use to generate code needs to have a version that
exactly matches the version of the protobuf crate you are using. More
specifically, if you are using Rust protobuf x.y.z then you need to use protoc
y.z. See here for more
details on our versioning scheme.

The easiest way to get ahold of protoc is to download a prebuilt binary from the
matching release here.
Just make sure protoc is on your $PATH when you run cargo.


Overview (`rust/release_crates/protobuf_macros/README.md`)

Internal crate. Use protobuf instead.

This crate is superseded by
google-protobuf-macros,

which it re-exports. New development happens there.


Overview (`rust/release_crates/protobuf_well_known_types/README.md`)

This crate contains the protobuf
well-known types.

This crate is superseded by
google-protobuf-well-known-types,

which it re-exports. New development happens there; please migrate there to
get the latest releases.


Overview (`src/google/protobuf/compiler/kotlin/README.md`)

Kotlin DSL Generator

This code generator implements the Kotlin DSL. The Kotlin DSL sits on top of
another proto implementation (written in Java or Kotlin) and adds convenient
support for building proto messages using DSL syntax, as documented in
Kotlin Generated Code Guide.

This code generator is invoked by passing --kotlin_out to protoc.

When using Kotlin on the JVM, you will also need to pass --java_out to
generate the Java code that implements the generated classes themselves.

When using Kotlin on other platforms (eg. Kotlin Native), there is currently no
support for generating message classes, so it is not possible to use the Kotlin
DSL at this time.


Overview (`src/google/protobuf/util/python/README.md`)

Python wrappers protobuf/util

The field_mask_util wrappers are maintained by the
Risk AI Team for their own
benefit. Other teams may add themselves to the visibility rule, but have to sign
up as OWNERs in return.


Cmake Protobuf Generate (`docs/cmake_protobuf_generate.md`)

How to use `protobuf_generate`

This document explains how to use the function protobuf_generate which is
provided by protobuf's CMake module.

Usage

In the same directory that called find_package(protobuf CONFIG) and any of its
subdirectories, the CMake function protobuf_generate is made available by
protobuf-generate.cmake. It can be used to
automatically generate source files from .proto schema files at build time.

Basic example

Let us see how protobuf_generate can be used to generate and compile the
source files of a proto schema whenever an object target called proto-objects
is built.

Given the following directory structure:

  • proto/helloworld/helloworld.proto
  • CMakeLists.txt

where helloworld.proto is a protobuf schema file and CMakeLists.txt
contains:

cmake
find_package(protobuf CONFIG REQUIRED)

add_library(proto-objects OBJECT "${CMAKE_CURRENT_LIST_DIR}/proto/helloworld/helloworld.proto")

target_link_libraries(proto-objects PUBLIC protobuf::libprotobuf)

set(PROTO_BINARY_DIR "${CMAKE_CURRENT_BINARY_DIR}/generated")

target_include_directories(proto-objects PUBLIC "$<BUILD_INTERFACE:${PROTO_BINARY_DIR}>")

protobuf_generate(
    TARGET proto-objects
    IMPORT_DIRS "${CMAKE_CURRENT_LIST_DIR}/proto"
    PROTOC_OUT_DIR "${PROTO_BINARY_DIR}")

Building the target proto-objects will generate the files:

  • ${CMAKE_CURRENT_BINARY_DIR}/generated/helloworld/helloworld.pb.h
  • ${CMAKE_CURRENT_BINARY_DIR}/generated/helloworld/helloworld.pb.cc

and (depending on the build system) output:

shell
[build] [1/2] Running cpp protocol buffer compiler on /proto/helloworld/helloworld.proto
[build] [2/2] Building CXX object /build/generated/helloworld/helloworld.pb.cc.o

gRPC example

protobuf_generate can also be customized to invoke plugins like gRPC's
grpc_cpp_plugin. Given the same directory structure as in the
basic example and let CMakeLists.txt contain:

cmake
find_package(gRPC CONFIG REQUIRED)

add_library(proto-objects OBJECT "${CMAKE_CURRENT_LIST_DIR}/proto/helloworld/helloworld.proto")

target_link_libraries(proto-objects PUBLIC protobuf::libprotobuf gRPC::grpc++)

set(PROTO_BINARY_DIR "${CMAKE_CURRENT_BINARY_DIR}/generated")
set(PROTO_IMPORT_DIRS "${CMAKE_CURRENT_LIST_DIR}/proto")

target_include_directories(proto-objects PUBLIC "$<BUILD_INTERFACE:${PROTO_BINARY_DIR}>")

protobuf_generate(
    TARGET proto-objects
    IMPORT_DIRS ${PROTO_IMPORT_DIRS}
    PROTOC_OUT_DIR "${PROTO_BINARY_DIR}")

protobuf_generate(
    TARGET proto-objects
    LANGUAGE grpc
    GENERATE_EXTENSIONS .grpc.pb.h .grpc.pb.cc
    PLUGIN "protoc-gen-grpc=\$<TARGET_FILE:gRPC::grpc_cpp_plugin>"
    IMPORT_DIRS ${PROTO_IMPORT_DIRS}
    PROTOC_OUT_DIR "${PROTO_BINARY_DIR}")

Then building proto-objects will generate and compile:

  • ${CMAKE_CURRENT_BINARY_DIR}/generated/helloworld/helloworld.pb.h
  • ${CMAKE_CURRENT_BINARY_DIR}/generated/helloworld/helloworld.pb.cc
  • ${CMAKE_CURRENT_BINARY_DIR}/generated/helloworld/helloworld.grpc.pb.h
  • ${CMAKE_CURRENT_BINARY_DIR}/generated/helloworld/helloworld.grpc.pb.cc

And protoc will automatically be re-run whenever the schema files change and
proto-objects is built.

Note on unity builds

Since protobuf's generated source files are unsuited for
jumbo/unity builds
it is recommended to exclude them from such builds which can be achieved by
adjusting their properties:

cmake
protobuf_generate(
    OUT_VAR PROTO_GENERATED_FILES
    ...)

set_source_files_properties(${PROTO_GENERATED_FILES} PROPERTIES SKIP_UNITY_BUILD_INCLUSION on)

How it works

For each source file ending in proto of the argument provided to TARGET or
each file provided through PROTOS, protobuf_generate will set up a
add_custom_command
which depends on protobuf::protoc and the proto files. It declares the
generated source files as OUTPUT which means that any target that depends on
them will automatically cause the custom command to execute when it is brought
up to date. The command itself is made up of the arguments for protoc, like
the output directory, the schema files, the language to generate for, the
plugins to use, etc.

Reference

Arguments accepted by protobuf_generate.

Flag arguments:

  • APPEND_PATH — A flag that causes the base path of all proto schema files
    to be added to IMPORT_DIRS.

Single-value arguments:

  • LANGUAGE — A single value: cpp or python. Determines what kind of source
    files are being generated.
  • OUT_VAR — Name of a CMake variable that will be filled with the paths to
    the generated source files.
  • EXPORT_MACRO — Name of a macro that is applied to all generated Protobuf
    message classes and extern variables. It can, for example, be used to
    declare DLL exports.
  • PROTOC_EXE — Command name, path, or CMake executable used to run protoc
    commands. Defaults to protobuf::protoc.
  • PROTOC_OUT_DIR — Output directory of generated source files. Defaults to
    CMAKE_CURRENT_BINARY_DIR.
  • PLUGIN — An optional plugin executable. This could, for example, be the
    path to grpc_cpp_plugin.
  • PLUGIN_OPTIONS — Additional options provided to the plugin, such as
    generate_mock_code=true for the gRPC cpp plugin.
  • DEPENDENCIES — Arguments forwarded to the DEPENDS of the underlying
    add_custom_command invocation.
  • TARGET — CMake target that will have the generated files added as sources.

Multi-value arguments:

  • PROTOS — List of proto schema files. If omitted, then every source file
    ending in proto of TARGET will be used.
  • IMPORT_DIRS — A common parent directory for the schema files. For example,
    if the schema file is proto/helloworld/helloworld.proto and the import
    directory proto/ then the generated files are
    ${PROTOC_OUT_DIR}/helloworld/helloworld.pb.h and
    ${PROTOC_OUT_DIR}/helloworld/helloworld.pb.cc.
  • GENERATE_EXTENSIONS — If LANGUAGE is omitted then this must be set to the
    extensions that protoc generates.
  • PROTOC_OPTIONS — Additional arguments that are forwarded to protoc. Don't
    use it for flags that protobuf_generate sets from its own arguments:
    import paths (-I/--proto_path; use IMPORT_DIRS or APPEND_PATH), the
    output directory and generator options (--<lang>_out; use
    PROTOC_OUT_DIR and PLUGIN_OPTIONS) or plugins (--plugin; use
    PLUGIN). The generated file names are worked out from those arguments,
    so flags passed here aren't taken into account.

Cpp Build Systems (`docs/cpp_build_systems.md`)

How Protobuf supports multiple C++ build systems

This document explains how the Protobuf project supports multiple C++ build
systems.

Background

Protobuf primarily uses Bazel to build the Protobuf C++
runtime and Protobuf compiler[^historical_sot]. However, there are several
different build systems in common use for C++, each one of which requires
essentially a complete copy of the same build definitions.

[^historical_sot]: On a historical note, prior to its
release as Open Source Software,
the Protobuf project was developed using Google's internal
build system, which was the predecessor to Bazel (the vast
majority of Google's contributions continue to be developed
this way). The Open Source Protobuf project, however,
historically used Autoconf to build the C++ implementation.
Over time, other build systems (including Bazel) have been
added, thanks in large part to substantial contributions from
the Open Source community. Since the Protobuf project deals
with multiple languages (all of which ultimately rely upon
C++, for the Protobuf compiler), Bazel is a natural choice
for a project-wide build system -- in fact, Bazel (and its
predecessor, Blaze) was designed in large part to support
exactly this type of rich, multi-language build.

Currently, C++ Protobuf can be built with Bazel and CMake. Each of these build
systems has different semantics and structure, but share in common the list of
files needed to build the runtime and compiler.

Design

Extracting information from Bazel

Bazel's Starlark API provides aspects to
traverse the build graph, inspect build rules, define additional actions, and
expose information through
providers. For example, the
cc_proto_library rule uses an aspect to traverse the dependency graph of
proto_library rules, and dynamically attaches actions to generate C++ code
using the Protobuf compiler and compile using the C++ compiler.

In order to support multiple build systems, the overall build structure is
defined once for each system, and expose frequently-changing metadata from Bazel
in a way that can be included from the build definition. Primarily, this means
exposing the list of source files in a way that can be included in other build
definitions.

Two aspects are used to extract this information from the Bazel build
definitions:

  • cc_file_list_aspect extracts srcs, hdrs, and textual_hdrs from build
    rules like cc_library. The sources are exposed through a provider named
    CcFileList.
  • proto_file_list_aspect extracts the srcs from a proto_library, and
    also generates the expected filenames that would be generated by the
    Protobuf compiler. This information is exposed through a provider named
    ProtoFileList.

On their own, these aspects have limited utility. However, they can be
instantiated by custom rules, so that an ordinary BUILD.bazel target can
produce outputs based on the information gleaned from these aspects.

(Aside) Distribution libraries

Bazel's native cc_library rule is typically used on a "fine-grained" level, so
that, for example, lightweight unit tests can be written with narrow scope.
Although Bazel does build library artifacts (such as .so and .a files on
Linux), they correspond to cc_library rules.

Since the entire "Protobuf library" includes many constituent cc_library
rules, a special rule, cc_dist_library, combines several fine-grained
libraries into a single, monolithic library.

For the Protobuf project, these "distribution libraries" are intended to match
the granularity of the CMake-based builds. Since the Bazel-built distribution
library covers the rules with the source files needed by other builds, the
cc_dist_library rule invokes the cc_file_list_aspect on its input libraries.
The result is that a cc_dist_library rule not only produces composite library
artifacts, but also collect and provide the list of sources that were inputs.

For example:

text
$ cat cc_dist_library_example/BUILD.bazel
load("@rules_cc//cc:defs.bzl", "cc_library")
load("//pkg:cc_dist_library.bzl", "cc_dist_library")

cc_library(
    name = "a",
    srcs = ["a.cc"],
)

cc_library(
    name = "b",
    srcs = ["b.cc"],
    deps = [":c"],
)

# N.B.: not part of the cc_dist_library, even though it is in the deps of 'b':
cc_library(
    name = "c",
    srcs = ["c.cc"],
)

cc_dist_library(
    name = "lib",
    deps = [
        ":a",
        ":b",
    ],
    visibility = ["//visibility:public"],
)

# Note: the output below has been formatted for clarity:
$ bazel cquery //cc_dist_library_example:lib \
    --output=starlark \
    --starlark:expr='providers(target)["//pkg:cc_dist_library.bzl%CcFileList"]'
struct(
    hdrs = depset([]),
    internal_hdrs = depset([]),
    srcs = depset([
        <source file cc_dist_library_example/a.cc>,
        <source file cc_dist_library_example/b.cc>,
    ]),
    textual_hdrs = depset([]),
)

The upshot is that the "coarse-grained" library can be defined by the Bazel
build, and then export the list of source files that are needed to reproduce the
library in a different build system.

One major difference from most Bazel rule types is that the file list aspects do
not propagate. In other words, they only expose the immediate dependency's
sources, not transitive sources. This is for two reasons:

  1. Immediate dependencies are conceptually simple, while transitivity requires
    substantially more thought. For example, if transitive dependencies were
    considered, then some way would be needed to exclude dependencies that
    should not be part of the final library (for example, a distribution library
    for //:protobuf could be defined not to include all of
    //:protobuf_lite). While dependency elision is an interesting design
    problem, the protobuf library is small enough that directly listing
    dependencies should not be problematic.
  2. Dealing only with immediate dependencies gives finer-grained control over
    what goes into the composite library. For example, a Starlark select()
    could conditionally add fine-grained libraries to some builds, but not
    others.

Another subtlety for tests is due to Bazel internals. Internally, a slightly
different configuration is used when evaluating cc_test rules as compared to
cc_dist_library. If cc_test targets are included in a cc_dist_library
rule, and both are evaluated by Bazel, this can result in a build-time error:
the config used for the test contains additional options that tell Bazel how to
execute the test that the cc_file_list_aspect build config does not. Bazel
detects this as two conflicting actions generating the same outputs. (For
cc_test rules, the simplest workaround is to provide sources through a
filegroup or similar.)

File list generation

Lists of input files are generated by Bazel in a format that can be imported to
other build systems. Currently only CMake-style files can be generated.

The lists of files are derived from Bazel build targets. The sources can be: *
cc_dist_library rules (as described above) * proto_library rules *
individual files * filegroup rules * pkg_files or pkg_filegroup rules from
https://github.com/bazelbuild/rules_pkg

For example:

text
/* Detailed source-code truncated for AI context efficiency. */

A hand-written CMake build rule could then use the generated file to define
libraries, such as:

text
include(source_lists.cmake)
add_library(distlib ${distlib_srcs} ${buff_srcs})

Protobuf usage

The main C++ runtimes (lite and full) and the Protobuf compiler use their
corresponding cc_dist_library rules to generate file lists. For
proto_library targets, the file list generation can extract the source files
directly. For other targets, notably cc_test targets, the file list generators
use filegroup rules.

In general, adding new targets to a non-Bazel build system in Protobuf (or
adding a new build system altogether) requires some one-time setup:

  1. The overall structure of the new build system has to be defined. It should
    import lists of files and refer to them by variable, instead of listing
    files directly.
  2. (Only if the build system is new) A new rule type has to be added to
    //pkg:build_systems.bzl. Most of the implementation is shared, but a
    "fragment generator" is need to declare a file list variable, and the rule
    type itself has to be defined and call the shared implementation.

When files are added or deleted, or when the Protobuf Bazel structure is
changed, these changes may need to be reflected in the file list logic. These
are some example scenarios:

  • Files are added to (or removed from) the srcs of an existing cc_library:
    no changes needed. If the cc_library is already part of a
    cc_dist_library, then regenerating the source lists will reflect the
    change.
  • A cc_library is added: the new target may need to be added to the Protobuf
    cc_dist_library targets, as appropriate.
  • A cc_library is deleted: if a cc_dist_library depends upon the deleted
    target, then a build-time error will result. The library needs to be removed
    from the cc_dist_library.
  • A cc_test is added or deleted: test sources are handled by filegroup
    rules defined in the same package as the cc_test rule. The filegroups
    are usually given a name like "test_srcs", and often use glob() to find
    sources. This means that adding or removing a test may not require any extra
    work, but this can be verified within the same package as the test rule.
  • Test-only proto files are added: the proto_library might need to be added
    to the file list map in //pkg:BUILD.bazel, and then the file added to
    various build systems. However, most test-only protos are already exposed
    through libraries like //src/google/protobuf:test_protos.

If there are changes, then the regenerated file lists need to be copied back
into the repo. That way, the corresponding build systems can be used with a git
checkout, without needing to run Bazel first.

(Aside) Distribution archives

A very similar set of rules is defined in //pkg to build source distribution
archives for releases. In addition to the full sources, Protobuf releases also
include source archives sliced by language, so that, for example, a Ruby-based
project can get just the sources needed to build the Ruby runtime. (The
per-language slices also include sources needed to build the protobuf compiler,
so they all effectively include the C++ runtime.)

These archives are defined using rules from the
rules_pkg project. Although they are
similar to cc_dist_library and the file list generation rules, the goals are
different: the build system file lists described above only apply to C++, and
are organized according to what should or should not be included in different
parts of the build (e.g., no tests are included in the main library). On the
other hand, the distribution archives deal with languages other than C++, and
contain all the files that need to be distributed as part of a release (even for
C++, this is more than just the C++ sources).

While it might be possible to use information from the CcFileList and
ProtoFileList providers to define the distribution files, additional files
(such as the various BUILD.bazel files) are also needed in the distribution
archive. The lists of distribution files can usually be generated by glob(),
anyhow, so sharing logic with the file list aspects may not be beneficial.

Currently, all of the file lists are checked in. However, it would be possible
to build the file lists on-the-fly and include them in the distribution
archives, rather than checking them in.


Field Presence (`docs/field_presence.md`)

Application note: Field presence

This application note explains the various presence tracking disciplines for
protobuf fields. It also explains the behaviour of explicit presence tracking
for singular proto3 fields with basic types.

Background

Field presence is the notion of whether a protobuf field has a value. There
are two different manifestations of presence for protobufs: no presence, where
the generated message API stores field values (only), and explicit presence,
where the API also stores whether or not a field has been set.

Historically, proto2 has mostly followed explicit presence, while proto3
exposes only no presence semantics. Singular proto3 fields of basic types
(numeric, string, bytes, and enums) which are defined with the optional label
have explicit presence, like proto2 (this feature is enabled by default as
release 3.15).

Presence disciplines

Presence disciplines define the semantics for translating between the API
representation
and the serialized representation. The no presence
discipline relies upon the field value itself to make decisions at
(de)serialization time, while the explicit presence discipline relies upon the
explicit tracking state instead.

Presence in *tag-value stream* (wire format) serialization

The wire format is a stream of tagged, self-delimiting values. By definition,
the wire format represents a sequence of present values. In other words, every
value found within a serialization represents a present field; furthermore,
the serialization contains no information about not-present values.

The generated API for a proto message includes (de)serialization definitions
which translate between API types and a stream of definitionally present (tag,
value) pairs. This translation is designed to be forward- and
backward-compatible across changes to the message definition; however, this
compatibility introduces some (perhaps surprising) considerations when
deserializing wire-formatted messages:

  • When serializing, fields with no presence are not serialized if they
    contain their default value.
    • For numeric types, the default is 0.
    • For enums, the default is the zero-valued enumerator.
    • For strings, bytes, and repeated fields, the default is the zero-length
      value.
    • For messages, the default is the language-specific null value.
  • "Empty" length-delimited values (such as empty strings) can be validly
    represented in serialized values: the field is "present," in the sense that
    it appears in the wire format. However, if the generated API does not track
    presence, then these values may not be re-serialized; i.e., the empty field
    may be "not present" after a serialization round-trip.
  • When deserializing, duplicate field values may be handled in different ways
    depending on the field definition.
    • Duplicate repeated fields are typically appended to the field's API
      representation. (Note that serializing a packed repeated field
      produces only one, length-delimited value in the tag stream.)
    • Duplicate optional field values follow the rule that "the last one
      wins."
  • oneof fields expose the API-level invariant that only one field is set at
    a time. However, the wire format may include multiple (tag, value) pairs
    which notionally belong to the oneof. Similar to optional fields, the
    generated API follows the "last one wins" rule.
  • Out-of-range values are not returned for enum fields in generated proto2
    APIs. However, out-of-range values may be stored as unknown fields in the
    API, even though the wire-format tag was recognized.

Presence in *named-field mapping* formats

Protobufs can be represented in human-readable, textual forms. Two notable
formats are TextFormat (the output format produced by generated message
DebugString methods) and JSON.

These formats have correctness requirements of their own, and are generally
stricter than tagged-value stream formats. However, TextFormat more closely
mimics the semantics of the wire format, and does, in certain cases, provide
similar semantics (for example, appending repeated name-value mappings to a
repeated field). In particular, similar to the wire format, TextFormat only
includes fields which are present.

JSON is a much stricter format, however, and cannot validly represent some
semantics of the wire format or TextFormat.

  • Notably, JSON elements are semantically unordered, and each member must
    have a unique name. This is different from TextFormat rules for repeated
    fields.
  • JSON may include fields that are "not present," unlike the no presence
    discipline for other formats:
    • JSON defines a null value, which may be used to represent a defined
      but not-present field
      .
    • Repeated field values may be included in the formatted output, even if
      they are equal to the default (an empty list).
  • Because JSON elements are unordered, there is no way to unambiguously
    interpret the "last one wins" rule.
    • In most cases, this is fine: JSON elements must have unique names:
      repeated field values are not valid JSON, so they do not need to be
      resolved as they are for TextFormat.
    • However, this means that it may not be possible to interpret oneof
      fields unambiguously: if multiple cases are present, they are unordered.

In theory, JSON can represent presence in a semantic-preserving fashion. In
practice, however, presence correctness can vary depending upon implementation
choices, especially if JSON was chosen as a means to interoperate with clients
not using protobufs.

Presence in proto2 APIs

This table outlines whether presence is tracked for fields in proto2 APIs (both
for generated APIs and using dynamic reflection):

Field type Explicit Presence
Singular numeric (integer or floating point) ✔️
Singular enum ✔️
Singular string or bytes ✔️
Singular message ✔️
Repeated
Oneofs ✔️
Maps

Singular fields (of all types) track presence explicitly in the generated API.
The generated message interface includes methods to query presence of fields.
For example, the field foo has a corresponding has_foo method. (The specific
name follows the same language-specific naming convention as the field
accessors.) These methods are sometimes referred to as "hazzers" within the
protobuf implementation.

Similar to singular fields, oneof fields explicitly track which one of the
members, if any, contains a value. For example, consider this example oneof:

protobuf
oneof foo {
  int32 a = 1;
  float b = 2;
}

Depending on the target language, the generated API would generally include
several methods:

  • A hazzer for the oneof: has_foo
  • A oneof case method: foo
  • Hazzers for the members: has_a, has_b
  • Getters for the members: a, b

Repeated fields and maps do not track presence: there is no distinction between
an empty and a not-present repeated field.

Presence in proto3 APIs

This table outlines whether presence is tracked for fields in proto3 APIs (both
for generated APIs and using dynamic reflection):

Field type optional Explicit Presence
Singular numeric (integer or floating point) No
Singular enum No
Singular string or bytes No
Singular numeric (integer or floating point) Yes ✔️
Singular enum Yes ✔️
Singular string or bytes Yes ✔️
Singular message Yes ✔️
Singular message No ✔️
Repeated N/A
Oneofs N/A ✔️
Maps N/A

Similar to proto2 APIs, proto3 does not track presence explicitly for repeated
fields. Without the optional label, proto3 APIs do not track presence for
basic types (numeric, string, bytes, and enums), either. Oneof fields
affirmatively expose presence, although the same set of hazzer methods may not
generated as in proto2 APIs.

Under the no presence discipline, the default value is synonymous with "not
present" for purposes of serialization. To notionally "clear" a field (so it
won't be serialized), an API user would set it to the default value.

The default value for enum-typed fields under no presence is the corresponding
0-valued enumerator. Under proto3 syntax rules, all enum types are required to
have an enumerator value which maps to 0. By convention, this is an UNKNOWN or
similarly-named enumerator. If the zero value is notionally outside the domain
of valid values for the application, this behavior can be thought of as
tantamount to explicit presence.

Semantic differences

The no presence serialization discipline results in visible differences from
the explicit presence tracking discipline, when the default value is set. For
a singular field with numeric, enum, or string type:

  • No presence discipline:
    • Default values are not serialized.
    • Default values are not merged-from.
    • To "clear" a field, it is set to its default value.
    • The default value may mean:
      • the field was explicitly set to its default value, which is valid in
        the application-specific domain of values;
      • the field was notionally "cleared" by setting its default; or
      • the field was never set.
  • Explicit presence discipline:
    • Explicitly set values are always serialized, including default values.
    • Un-set fields are never merged-from.
    • Explicitly set fields -- including default values -- are merged-from.
    • A generated has_foo method indicates whether or not the field foo
      has been set (and not cleared).
    • A generated clear_foo method must be used to clear (i.e., un-set) the
      value.

Considerations for merging

Under the no presence rules, it is effectively impossible for a target field
to merge-from its default value (using the protobuf's API merging functions).
This is because default values are skipped, similar to the no presence
serialization discipline. Merging only updates the target (merged-to) message
using the non-skipped values from the update (merged-from) message.

The difference in merging behavior has further implications for protocols which
rely on partial "patch" updates. If field presence is not tracked, then an
update patch alone cannot represent an update to the default value, because only
non-default values are merged-from.

Updating to set a default value in this case requires some external mechanism,
such as FieldMask. However, if presence is tracked, then all explicitly-set
values -- even default values -- will be merged into the target.

Considerations for change-compatibility

Changing a field between explicit presence and no presence is a
binary-compatible change for serialized values in wire format. However, the
serialized representation of the message may differ, depending on which version
of the message definition was used for serialization. Specifically, when a
"sender" explicitly sets a field to its default value:

  • The serialized value following no presence discipline does not contain the
    default value, even though it was explicitly set.
  • The serialized value following explicit presence discipline contains every
    "present" field, even if it contains the default value.

This change may or may not be safe, depending on the application's semantics.
For example, consider two clients with different versions of a message
definition.

Client A uses this definition of the message, which follows the explicit
presence
serialization discipline for field foo:

protobuf
syntax = "proto3";
message Msg {
  optional int32 foo = 1;
}

Client B uses a definition of the same message, except that it follows the no
presence
discipline:

protobuf
syntax = "proto3";
message Msg {
  int32 foo = 1;
}

Now, consider a scenario where client A observes foo's presence as the clients
repeatedly exchange the "same" message by deserializing and reserializing:

protobuf
// Client A:
Msg m_a;
m_a.set_foo(1);                  // non-default value
assert(m_a.has_foo());           // OK
Send(m_a.SerializeAsString());   // to client B

// Client B:
Msg m_b;
m_b.ParseFromString(Receive());  // from client A
assert(m_b.foo() == 1);          // OK
Send(m_b.SerializeAsString());   // to client A

// Client A:
m_a.ParseFromString(Receive());  // from client B
assert(m_a.foo() == 1);          // OK
assert(m_a.has_foo());           // OK
m_a.set_foo(0);                  // default value
Send(m_a.SerializeAsString());   // to client B

// Client B:
Msg m_b;
m_b.ParseFromString(Receive());  // from client A
assert(m_b.foo() == 0);          // OK
Send(m_b.SerializeAsString());   // to client A

// Client A:
m_a.ParseFromString(Receive());  // from client B
assert(m_a.foo() == 0);          // OK
assert(m_a.has_foo());           // FAIL

If client A depends on explicit presence for foo, then a "round trip"
through client B will be lossy from the perspective of client A. In the example,
this is not a safe change: client A requires (by assert) that the field is
present; even without any modifications through the API, that requirement fails
in a value- and peer-dependent case.

How to enable *explicit presence* in proto3

These are the general steps to use field tracking support for proto3:

  1. Add an optional field to a .proto file.
  2. Run protoc (at least v3.15, or v3.12 using
    --experimental_allow_proto3_optional flag).
  3. Use the generated "hazzer" methods and "clear" methods in application code,
    instead of comparing or setting default values.

`.proto` file changes

This is an example of a proto3 message with fields which follow both no
presence
and explicit presence semantics:

protobuf
syntax = "proto3";
package example;

message MyMessage {
  // No presence:
  int32 not_tracked = 1;

  // Explicit presence:
  optional int32 tracked = 2;
}

`protoc` invocation

Presence tracking for proto3 messages is enabled by default
since v3.15.0
release, formerly up until
v3.12.0 the
--experimental_allow_proto3_optional flag was required when using presence
tracking with protoc.

Using the generated code

The generated code for proto3 fields with explicit presence (the optional
label) will be the same as it would be in a proto2 file.

This is the definition used in the "no presence" examples below:

protobuf
syntax = "proto3";
package example;
message Msg {
  int32 foo = 1;
}

This is the definition used in the "explicit presence" examples below:

protobuf
syntax = "proto3";
package example;
message Msg {
  optional int32 foo = 1;
}

In the examples, a function GetProto constructs and returns a message of type
Msg with unspecified contents.

C++ example

No presence:

c++
Msg m = GetProto();
if (m.foo() != 0) {
  // "Clear" the field:
  m.set_foo(0);
} else {
  // Default value: field may not have been present.
  m.set_foo(1);
}

Explicit presence:

c++
Msg m = GetProto();
if (m.has_foo()) {
  // Clear the field:
  m.clear_foo();
} else {
  // Field is not present, so set it.
  m.set_foo(1);
}

C# example

No presence:

c#
var m = GetProto();
if (m.Foo != 0) {
  // "Clear" the field:
  m.Foo = 0;
} else {
  // Default value: field may not have been present.
  m.Foo = 1;
}

Explicit presence:

c#
var m = GetProto();
if (m.HasFoo) {
  // Clear the field:
  m.ClearFoo();
} else {
  // Field is not present, so set it.
  m.Foo = 1;
}

Go example

No presence:

go
m := GetProto()
if m.Foo != 0 {
  // "Clear" the field:
  m.Foo = 0
} else {
  // Default value: field may not have been present.
  m.Foo = 1
}

Explicit presence:

go
m := GetProto()
if m.Foo != nil {
  // Clear the field:
  m.Foo = nil
} else {
  // Field is not present, so set it.
  m.Foo = proto.Int32(1)
}

Java example

These examples use a Builder to demonstrate clearing. Simply checking presence
and getting values from a Builder follows the same API as the message type.

No presence:

java
Msg.Builder m = GetProto().toBuilder();
if (m.getFoo() != 0) {
  // "Clear" the field:
  m.setFoo(0);
} else {
  // Default value: field may not have been present.
  m.setFoo(1);
}

Explicit presence:

java
Msg.Builder m = GetProto().toBuilder();
if (m.hasFoo()) {
  // Clear the field:
  m.clearFoo()
} else {
  // Field is not present, so set it.
  m.setFoo(1);
}

Python example

No presence:

python
m = example.Msg()
if m.foo != 0:
  # "Clear" the field:
  m.foo = 0
else:
  # Default value: field may not have been present.
  m.foo = 1

Explicit presence:

python
m = example.Msg()
if m.HasField('foo'):
  # Clear the field:
  m.ClearField('foo')
else:
  # Field is not present, so set it.
  m.foo = 1

Ruby example

No presence:

ruby
m = Msg.new
if m.foo != 0
  # "Clear" the field:
  m.foo = 0
else
  # Default value: field may not have been present.
  m.foo = 1
end

Explicit presence:

ruby
m = Msg.new
if m.has_foo?
  # Clear the field:
  m.clear_foo
else
  # Field is not present, so set it.
  m.foo = 1
end

Javascript example

No presence:

js
var m = new Msg();
if (m.getFoo() != 0) {
  // "Clear" the field:
  m.setFoo(0);
} else {
  // Default value: field may not have been present.
  m.setFoo(1);
}

Explicit presence:

js
var m = new Msg();
if (m.hasFoo()) {
  // Clear the field:
  m.clearFoo()
} else {
  // Field is not present, so set it.
  m.setFoo(1);
}

Objective C example

No presence:

objective-c
Msg *m = [[Msg alloc] init];
if (m.foo != 0) {
  // "Clear" the field:
  m.foo = 0;
} else {
  // Default value: field may not have been present.
  m.foo = 1;
}

Explicit presence:

objective-c
Msg *m = [[Msg alloc] init];
if (m.hasFoo()) {
  // Clear the field:
  [m clearFoo];
} else {
  // Field is not present, so set it.
  [m setFoo:1];
}

Implementing Proto3 Presence (`docs/implementing_proto3_presence.md`)

How To Implement Field Presence for Proto3

Protobuf release 3.12 adds experimental support for optional fields in proto3.
Proto3 optional fields track presence like in proto2. For background information
about what presence tracking means, please see
docs/field_presence.

Document Summary

This document is targeted at developers who own or maintain protobuf code
generators. All code generators will need to be updated to support proto3
optional fields. First-party code generators developed by Google are being
updated already. However third-party code generators will need to be updated
independently by their authors. This includes:

  • implementations of Protocol Buffers for other languages.
  • alternate implementations of Protocol Buffers that target specialized use
    cases.
  • RPC code generators that create generated APIs for service calls.
  • code generators that implement some utility code on top of protobuf
    generated classes.

While this document speaks in terms of "code generators", these same principles
apply to implementations that dynamically generate a protocol buffer API "on the
fly", directly from a descriptor, in languages that support this kind of usage.

Background

Presence tracking was added to proto3 in response to user feedback, both from
inside Google and
from open-source users.
The
proto3 wrapper types
were previously the only supported presence mechanism for proto3. Users have
pointed to both efficiency and usability issues with the wrapper types.

Presence in proto3 uses exactly the same syntax and semantics as in proto2.
Proto3 Fields marked optional will track presence like proto2, while fields
without any label (known as "singular fields"), will continue to omit presence
information. The optional keyword was chosen to minimize differences with
proto2.

Unfortunately, for the current descriptor protos and Descriptor API (as of
3.11.4) it is not possible to use the same representation as proto2. Proto3
descriptors already use LABEL_OPTIONAL for proto3 singular fields, which do
not track presence. There is a lot of existing code that reflects over proto3
protos and assumes that LABEL_OPTIONAL in proto3 means "no presence." Changing
the semantics now would be risky, since old software would likely drop proto3
presence information, which would be a data loss bug.

To minimize this risk we chose a descriptor representation that is semantically
compatible with existing proto3 reflection. Every proto3 optional field is
placed into a one-field oneof. We call this a "synthetic" oneof, as it was not
present in the source .proto file.

Since oneof fields in proto3 already track presence, existing proto3
reflection-based algorithms should correctly preserve presence for proto3
optional fields with no code changes. For example, the JSON and TextFormat
parsers/serializers in C++ and Java did not require any changes to support
proto3 presence. This is the major benefit of synthetic oneofs.

This design does leave some cruft in descriptors. Synthetic oneofs are a
compatibility measure that we can hopefully clean up in the future. For now
though, it is important to preserve them across different descriptor formats and
APIs. It is never safe to drop synthetic oneofs from a proto schema. Code
generators can (and should) skip synthetic oneofs when generating a user-facing
API or user-facing documentation. But for any schema representation that is
consumed programmatically, it is important to keep the synthetic oneofs around.

In APIs it can be helpful to offer separate accessors that refer to "real"
oneofs (see API Changes below). This is a convenient way to omit
synthetic oneofs in code generators.

Updating a Code Generator

When a user adds an optional field to proto3, this is internally rewritten as
a one-field oneof, for backward-compatibility with reflection-based algorithms:

protobuf
syntax = "proto3";

message Foo {
  // Experimental feature, not generally supported yet!
  optional int32 foo = 1;

  // Internally rewritten to:
  // oneof _foo {
  //   int32 foo = 1 [proto3_optional=true];
  // }
  //
  // We call _foo a "synthetic" oneof, since it was not created by the user.
}

As a result, the main two goals when updating a code generator are:

  1. Give optional fields like foo normal field presence, as described in
    docs/field_presence If your implementation already
    supports proto2, a proto3 optional field should use exactly the same API
    and internal implementation as proto2 optional.
  2. Avoid generating any oneof-based accessors for the synthetic oneof. Its only
    purpose is to make reflection-based algorithms work properly if they are not
    aware of proto3 presence. The synthetic oneof should not appear anywhere in
    the generated API.

Satisfying the Experimental Check

If you try to run protoc on a file with proto3 optional fields, you will get
an error because the feature is still experimental:

text
$ cat test.proto
syntax = "proto3";

message Foo {
  // Experimental feature, not generally supported yet!
  optional int32 a = 1;
}
$ protoc --cpp_out=. test.proto
test.proto: This file contains proto3 optional fields, but --experimental_allow_proto3_optional was not set.

There are two options for getting around this error:

  1. Pass --experimental_allow_proto3_optional to protoc.
  2. Make your filename (or a directory name) contain the string
    test_proto3_optional. This indicates that the proto file is specifically
    for testing proto3 optional support, so the check is suppressed.

These options are demonstrated below:

text
# One option:
$ protoc test.proto --cpp_out=. --experimental_allow_proto3_optional

# Another option:
$ cp test.proto test_proto3_optional.proto
$ protoc test_proto3_optional.proto --cpp_out=.
$

The experimental check will be removed in a future release, once we are ready to
make this feature generally available. Ideally this will happen for the 3.13
release of protobuf, sometime in mid-2020, but there is not a specific date set
for this yet. Some of the timing will depend on feedback we get from the
community, so if you have questions or concerns please get in touch via a GitHub
issue.

Signaling That Your Code Generator Supports Proto3 Optional

If you now try to invoke your own code generator with the test proto, you will
run into a different error:

text
$ protoc test_proto3_optional.proto --my_codegen_out=.
test_proto3_optional.proto: is a proto3 file that contains optional fields, but
code generator --my_codegen_out hasn't been updated to support optional fields in
proto3. Please ask the owner of this code generator to support proto3 optional.

This check exists to make sure that code generators get a chance to update
before they are used with proto3 optional fields. Without this check an old
code generator might emit obsolete generated APIs (like accessors for a
synthetic oneof) and users could start depending on these. That would create a
legacy migration burden once a code generator actually implements the feature.

To signal that your code generator supports optional fields in proto3, you
need to tell protoc what features you support. The method for doing this
depends on whether you are using the C++
google::protobuf::compiler::CodeGenerator framework or not.

If you are using the CodeGenerator framework:

c++
class MyCodeGenerator : public google::protobuf::compiler::CodeGenerator {
  // Add this method.
  uint64_t GetSupportedFeatures() const override {
    // Indicate that this code generator supports proto3 optional fields.
    // (Note: don't release your code generator with this flag set until you
    // have actually added and tested your proto3 support!)
    return FEATURE_PROTO3_OPTIONAL;
  }
}

If you are generating code using raw CodeGeneratorRequest and
CodeGeneratorResponse messages from plugin.proto, the change will be very
similar:

c++
void GenerateResponse() {
  CodeGeneratorResponse response;
  response.set_supported_features(CodeGeneratorResponse::FEATURE_PROTO3_OPTIONAL);

  // Generate code...
}

Once you have added this, you should now be able to successfully use your code
generator to generate a file containing proto3 optional fields:

text
$ protoc test_proto3_optional.proto --my_codegen_out=.

Updating Your Code Generator

Now to actually add support for proto3 optional to your code generator. The goal
is to recognize proto3 optional fields as optional, and suppress any output from
synthetic oneofs.

If your code generator does not currently support proto2, you will need to
design an API and implementation for supporting presence in scalar fields.
Generally this means:

  • allocating a bit inside the generated class to represent whether a given
    field is present or not.
  • exposing a has_foo() method for each field to return the value of this
    bit.
  • make the parser set this bit when a value is parsed from the wire.
  • make the serializer test this bit to decide whether to serialize.

If your code generator already supports proto2, then most of your work is
already done. All you need to do is make sure that proto3 optional fields have
exactly the same API and behave in exactly the same way as proto2 optional
fields.

From experience updating several of Google's code generators, most of the
updates that are required fall into one of several patterns. Here we will show
the patterns in terms of the C++ CodeGenerator framework. If you are using
CodeGeneratorRequest and CodeGeneratorReply directly, you can translate the
C++ examples to your own language, referencing the C++ implementation of these
methods where required.

To test whether a field should have presence

Old:

c++
bool MessageHasPresence(const google::protobuf::FieldDescriptor* field) {
  return field->has_presence();
}

New:

c++
// Presence is no longer a property of a message, it's a property of individual
// fields.
bool FieldHasPresence(const google::protobuf::FieldDescriptor* field) {
  return field->has_presence();
  // Note, the above will return true for fields in a oneof.
  // If you want to filter out oneof fields, write this instead:
  //   return field->has_presence && !field->real_containing_oneof()
}

To test whether a field is a member of a oneof

Old:

c++
bool FieldIsInOneof(const google::protobuf::FieldDescriptor* field) {
  return field->containing_oneof() != nullptr;
}

New:

c++
bool FieldIsInOneof(const google::protobuf::FieldDescriptor* field) {
  // real_containing_oneof() returns nullptr for synthetic oneofs.
  return field->real_containing_oneof() != nullptr;
}

To iterate over all oneofs

Old:

c++
bool IterateOverOneofs(const google::protobuf::Descriptor* message) {
  for (int i = 0; i < message->oneof_decl_count(); i++) {
    const google::protobuf::OneofDescriptor* oneof = message->oneof(i);
    // ...
  }
}

New:

c++
bool IterateOverOneofs(const google::protobuf::Descriptor* message) {
  // Real oneofs are always first, and real_oneof_decl_count() will return the
  // total number of oneofs, excluding synthetic oneofs.
  for (int i = 0; i < message->real_oneof_decl_count(); i++) {
    const google::protobuf::OneofDescriptor* oneof = message->oneof(i);
    // ...
  }
}

Updating Reflection

If your implementation offers reflection, there are a few other changes to make:

API Changes

The API for reflecting over fields and oneofs should make the following changes.
These match the changes implemented in C++ reflection.

  1. Add a FieldDescriptor::has_presence() method returning bool (adjusted to
    your language's naming convention). This should return true for all fields
    that have explicit presence, as documented in
    docs/field_presence. In particular, this includes
    fields in a oneof, proto2 scalar fields, and proto3 optional fields. This
    accessor will allow users to query what fields have presence without
    thinking about the difference between proto2 and proto3.
  2. As a corollary of (1), please do not expose an accessor for the
    FieldDescriptorProto.proto3_optional field. We want to avoid having users
    implement any proto2/proto3-specific logic. Users should use the
    has_presence() function instead.
  3. You may also wish to add a FieldDescriptor::has_optional_keyword() method
    returning bool, which indicates whether the optional keyword is present.
    Message fields will always return true for has_presence(), so this
    method can allow a user to know whether the user wrote optional or not. It
    can occasionally be useful to have this information, even though it does not
    change the presence semantics of the field.
  4. If your reflection API may be used for a code generator, you may wish to
    implement methods to help users tell the difference between real and
    synthetic oneofs. In particular:
    • OneofDescriptor::is_synthetic(): returns true if this is a synthetic
      oneof.
    • FieldDescriptor::real_containing_oneof(): like containing_oneof(),
      but returns nullptr if the oneof is synthetic.
    • Descriptor::real_oneof_decl_count(): like oneof_decl_count(), but
      returns the number of real oneofs only.

Implementation Changes

Proto3 optional fields and synthetic oneofs must work correctly when reflected
on. Specifically:

  1. Reflection for synthetic oneofs should work properly. Even though synthetic
    oneofs do not really exist in the message, you can still make reflection
    work as if they did. In particular, you can make a method like
    Reflection::HasOneof() or Reflection::GetOneofFieldDescriptor() look at
    the hasbit to determine if the oneof is present or not.
  2. Reflection for proto3 optional fields should work properly. For example, a
    method like Reflection::HasField() should know to look for the hasbit for
    a proto3 optional field. It should not be fooled by the synthetic oneof
    into thinking that there is a case member for the oneof.

Once you have updated reflection to work properly with proto3 optional and
synthetic oneofs, any code that uses your reflection interface should work
properly with no changes. This is the benefit of using synthetic oneofs.

In particular, if you have a reflection-based implementation of protobuf text
format or JSON, it should properly support proto3 optional fields without any
changes to the code. The fields will look like they all belong to a one-field
oneof, and existing proto3 reflection code should know how to test presence for
fields in a oneof.

So the best way to test your reflection changes is to try round-tripping a
message through text format, JSON, or some other reflection-based parser and
serializer, if you have one.

Validating Descriptors

If your reflection implementation supports loading descriptors at runtime, you
must verify that all synthetic oneofs are ordered after all "real" oneofs.

Here is the code that implements this validation step in C++, for inspiration:

c++
// Validation that runs for each message.
  // Synthetic oneofs must be last.
  int first_synthetic = -1;
  for (int i = 0; i < message->oneof_decl_count(); i++) {
    const OneofDescriptor* oneof = message->oneof_decl(i);
    if (oneof->is_synthetic()) {
      if (first_synthetic == -1) {
        first_synthetic = i;
      }
    } else {
      if (first_synthetic != -1) {
        AddError(message->full_name(), proto.oneof_decl(i),
                 DescriptorPool::ErrorCollector::OTHER,
                 "Synthetic oneofs must be after all other oneofs");
      }
    }
  }

  if (first_synthetic == -1) {
    message->real_oneof_decl_count_ = message->oneof_decl_count_;
  } else {
    message->real_oneof_decl_count_ = first_synthetic;
  }

Jvm Aot (`docs/jvm_aot.md`)

Ahead Of Time (AOT) compilation for the Java Virtual Machine (JVM)

Ahead Of Time (AOT) compilation build tools such as those provided by
GraalVM's native-image
can require some configuration when using protobuf. Protobuf for the JVM uses
reflection, and some of its target classes are not possible to determine in
advance. This situation is unlikely to change.

The Lite version of protobuf for the JVM
avoids reflection and may be better suited for use with AOT compilation tooling.
This Lite version was originally targeted for use on Android which has similar
AOT compilation goals as GraalVM's native-image tool.

GraalVM native-image

The GraalVM native-image tool can be configured with options for the
Java Native Interface
(JNI), http proxying, reflection, and other resources. While these configuration
options can be manually specified in JSON files, we recommend that you exercise
the application with
the assisted configuration agent.
The agent will generate native-image.properties files that you add to the
project's META-INF/native-image/ directory. The Native Image builder uses
configuration options provided in META-INF/native-image/ to construct
native-image command line arguments.


Options (`docs/options.md`)

Protobuf Global Extension Registry

This file contains a global registry of known extensions for descriptor.proto,
so that any developer who wishes to use multiple 3rd party projects, each with
their own extensions, can be confident that there won't be collisions in
extension numbers.

If you need an extension number for your custom option (see
custom options),
please
send us a pull request to
add an entry to this doc, or
create an issue with info
about your project (name and website) so we can add an entry for you.

Existing Registered Extensions

  1. C# port of protocol buffers

  2. Perl/XS port of protocol buffers

  3. Objective-C port of protocol buffers

  4. Google Wave Federation Protocol open-source release (FedOne)

  5. PHP code generator plugin

    • Website: ???
    • Extensions: 1004
  6. GWT code generator plugin (third-party!)

  7. Unix Domain RPC code generator plugin

  8. Object-C generator plugin (Plausible Labs)

  9. TBD (code42.com)

    • Website: ???
    • Extensions: 1008
  10. Goby Underwater Autonomy Project

  11. Nanopb

  12. Bluefin AUV Communication Extensions

  13. Dynamic Compact Control Language

  14. ScaleOut StateServer® Native C++ API

  15. FoundationDB SQL Layer

  16. Fender

  17. Vortex

  18. tresorit

  19. CRIU (Checkpoint Restore In Userspace)

  20. protobuf-c

  21. ScalaPB

  22. protoc-gen-bq-schema

  23. grpc-gateway

  24. Certificate Transparency

  25. JUNOS Router Telemetry

  26. Spine Event Engine

  27. Aruba cloud platform

    • Website: ???
    • Extensions: 1026 -> 1030
  28. Voltha

    • Website: ???
    • Extensions: 1031 -> 1033
  29. gator

    • Website: ???
    • Extensions: 1034
  30. protoc-gen-flowtypes

  31. ProfaneDB

  32. protobuf-net

  33. FICO / StreamEngine

  34. GopherJS

  35. ygot

  36. go-grpcmw

  37. grpc-gateway protoc-gen-openapiv2

  38. AN Message

    • Website: TBD
    • Extensions: 1043
  39. protofire

  40. Gravity

  41. SEMI Standards – I&C Technical Committee

  42. Elixir plugin

  43. API client generators

    • Website: ???
    • Extensions: 1048-1056
  44. Netifi Proteus

  45. CGSN Mooring Project

  46. Container Storage Interface

  47. TwirpQL Plugin

  48. Protoc-gen-validate

  49. Protokt (pre 1.0.0)

  50. Dart port of protocol buffers

  51. Ocaml-protoc-plugin

  52. Analyze Re Graphene

  53. Wire since and until

  54. Bazel, Failure Details

  55. grpc-graphql-gateway

  56. Cloudstate

  57. SummaFT protoc-plugins

  58. ADLINK EdgeSDK

  59. Wire wire_package

  60. Confluent Schema Registry

  61. ScalaPB Validate

  62. Astounding (Currently Private)

  63. Protoc-gen-psql

  64. Protoc-gen-sanitize

  65. Coach Client Connect (planned release in March 2021)

  66. Kratos API Errors

  67. Glitchdot (Currently Private)

  68. eigr/protocol

  69. Container Object Storage Interface (COSI)

  70. Protoc-gen-jsonschema

  71. Protoc-gen-checker

  72. Protoc-gen-go-svc

  73. Embedded Proto

  74. Protoc-gen-fieldmask

  75. Google Gnostic

  76. Protoc-gen-go-micro

  77. Protoc-gen-authz

  78. Protonium

  79. Protoc-gen-xo

  80. Ballerina gRPC

  81. Protoc-gen-referential-integrity

  82. Oclea Service Layer RPC

  83. mypy-protobuf

  84. Pigweed protobuf compiler

  85. Perfetto

  86. Buf

  87. Connect RPC

  88. protocel

  89. Cybozu

  90. EngFlow

  91. Proto-telemetry

  92. Digital Twins Definition Language (DTDL)

  93. RabbitMQ

  94. Wire use_array

  95. Protons

  96. gRPC Federation

  97. protoc-gen-openapi

  98. Wire enumMode

  99. Nebius AI

  100. Buf

  101. userver

  102. protoc-gen-default / protoc-gen-env (confpb)

  103. JaiaBot

  104. Protokt (1.0.0 and up)

  105. aep.dev Extensions

  106. Thermo Fisher Scientific TEM Extensions

  107. LiveKit

  108. grpc-gateway protoc-gen-openapiv3

  109. Protosource

  110. protosearch

  111. proto2type

  112. protoc-gen-openapi

  113. Motif

  114. Teleport

  115. proto2mcp

  116. ProtoWire

  117. Flyte

  118. durable

  119. firestore-proto-codec

  120. buffa


Third Party (`docs/third_party.md`)

Third-Party Add-ons for Protocol Buffers

This page lists code related to Protocol Buffers which is developed and
maintained by third parties. You may find this code useful, but note that
these projects are not affiliated with or endorsed by Google (unless
explicitly marked)
; try them at your own risk. Also note that many projects
here are in the early stages of development and not production-ready.

If you have a project that should be listed here, please
send us a pull request to
update this page.

Programming Languages

These are projects we know about implementing Protocol Buffers for other
programming languages:

RPC Implementations

GRPC (http://www.grpc.io/) is Google's RPC implementation for Protocol Buffers.
There are other third-party RPC implementations as well. Some of these actually
work with Protocol Buffers service definitions (defined using the service
keyword in .proto files) while others just use Protocol Buffers message
objects.

Inactive:

Build

IDE

Documentation

Other Utilities

There are miscellaneous other things you may find useful as a Protocol Buffers
developer.


Csharp Proto2 (`docs/csharp/proto2.md`)

As part of the 3.10 release of Google.Protobuf, experimental proto2 support has been released. This document outlines the new changes brought about to include proto2 support. This does not break existing proto3 support and users may continue to use proto3 features without changing their current code. Again the generated code and public API associated with proto2 is experimental and subject to change in the future. APIs for proto2 may be added, removed, or adjusted as feedback is received.
Generated code for proto2 may also be modified by adding, removing, or adjusting APIs as feedback is received.

Enabling proto2 features

For information about specific proto2 features, please read the proto2 language guide.

Much like other languages, proto2 features are used with proto2 files with the syntax declaration syntax = "proto2";. However, please note, proto3 is still the recommended version of protobuf and proto2 support is meant for legacy system interop and advanced uses.

Generated code

Messages

Messages in proto2 files are very similar to their proto3 counterparts. They expose the usual property for getting and setting, but they also include properties and methods to handle field presence.

For optional/required field XYZ, a HasXYZ property is included for checking presence and a ClearXYZ method is included for clearing the value.

proto
message Foo {
    optional Bar bar = 1;
    required Baz baz = 2;
}
cs
var foo = new Foo();
Assert.IsNull(foo.Bar);
Assert.False(foo.HasBar);
foo.Bar = new Bar();
Assert.True(foo.HasBar);
foo.ClearBar();

Messages with extension ranges

Messages which define extension ranges implement the IExtendableMessage interface as shown below.
See inline comments for more info.

cs
public interface IExtendableMessage<T> : IMessage<T> where T : IExtendableMessage<T>
{
    // Gets the value of a single value extension. If the extension isn't present, this returns the default value.
    TValue GetExtension<TValue>(Extension<T, TValue> extension);
    // Gets the value of a repeated extension. If the extension hasn't been set, this returns null to prevent unnecessary allocations.
    RepeatedField<TValue> GetExtension<TValue>(RepeatedExtension<T, TValue> extension);
    // Gets the value of a repeated extension. This will initialize the value of the repeated field and will never return null.
    RepeatedField<TValue> GetOrInitializeExtension<TValue>(RepeatedExtension<T, TValue> extension);
    // Sets the value of the extension
    void SetExtension<TValue>(Extension<T, TValue> extension, TValue value);
    // Returns whether the extension is present in the message
    bool HasExtension<TValue>(Extension<T, TValue> extension);
    // Clears the value of the extension, removing it from the message
    void ClearExtension<TValue>(Extension<T, TValue> extension);
    // Clears the value of the repeated extension, removing it from the message. Calling GetExtension after this will always return null.
    void ClearExtension<TValue>(RepeatedExtension<T, TValue> extension);
}

Extensions

Extensions are generated in static containers like reflection classes and type classes.
For example for a file called foo.proto containing extensions in the file scope, a
FooExtensions class is created containing the extensions defined in the file scope.
For easy access, this class can be used with using static to bring all extensions into scope.

proto
option csharp_namespace = "FooBar";
extend Foo {
    optional Baz foo_ext = 124;
}
message Baz {
    extend Foo {
        repeated Baz repeated_foo_ext = 125;
    }
}
cs
public static partial class FooExtensions {
    public static readonly Extension<Foo, Baz> FooExt = /* initialization */;
}

public partial class Baz {
    public partial static class Extensions {
        public static readonly RepeatedExtension<Foo, Baz> RepeatedFooExt = /* initialization */;
    }
}
cs
using static FooBar.FooExtensions;
using static FooBar.Baz.Extensions;

var foo = new Foo();
foo.SetExtension(FooExt, new Baz());
foo.GetOrInitializeExtension(RepeatedFooExt).Add(new Baz());

APIs

Message initialization

Initialization refers to checking the status of required fields in a proto2 message. If a message is uninitialized, not all required fields are set in either the message itself or any of its submessages. In other languages, missing required fields throw errors depending on the merge method used. This could cause unforeseen errors at runtime if the incorrect method is used.
However, in this implementation, parsers and input streams don't check messages for initialization on their own and throw errors. Instead it's up to you to handle messages with missing required fields in whatever way you see fit.
Checking message initialization can be done manually via the IsInitialized extension method in MessageExtensions.

Extension registries

Just like in Java, extension registries can be constructed to parse extensions when reading new messages
from input streams. The API is fairly similar to the Java API with some added bonuses with C# syntax sugars.

proto
message Baz {
    extend Foo {
        optional Baz foo_ext = 124;
    }
}
cs
var registry = new ExtensionRegistry() 
{ 
    Baz.Extensions.FooExt
};
var foo = Foo.Factory.WithExtensionRegistry(registry).ParseFrom(input);
Assert.True(foo.HasExtension(Bas.Extensions.FooExt));
var fooNoRegistry = Foo.Factory.ParseFrom(input);
Assert.False(foo.HasExtension(Bas.Extensions.FooExt));

Custom options

Due to their limited use and lack of type safety, the original CustomOptions APIs are now deprecated. Using the new generated extension identifiers, you can access extensions safely through the GetOption APIs. Note that cloneable values such as
repeated fields and messages will be deep cloned.

Example based on custom options usage example here.

cs
foreach (var service in input.Services)
{
    Console.WriteLine($"  {service.Name}");
    foreach (var method in service.Methods)
    {
        var rule = method.GetOption(AnnotationsExtensions.Http);
        if (rule != null)
        {
            Console.WriteLine($"    {method.Name}: {rule}");
        }
        else
        {
            Console.WriteLine($"    {method.Name}: no HTTP binding");
        }
    }
}

Reflection

Reflection APIs have been extended to enable accessing the new proto2 portions of the library and generated code.

  • FieldDescriptor.Extension
    • Gets the extension identifier behind an extension field, allowing it to be added to an ExtensionRegistry
  • FieldDescriptor.IsExtension
    • Returns whether a field is an extension of another type.
  • FieldDescriptor.ExtendeeType
    • Returns the extended type of an extension field
  • IFieldAccessor.HasValue
    • Returns whether a field's value is set. For proto3 fields, throws an InvalidOperationException.
  • FileDescriptor.Syntax
    • Gets the syntax of a file
  • FileDescriptor.Extensions
    • An immutable list of extensions defined in the file
  • MessageDescriptor.Extensions
    • An immutable list of extensions defined in the message
cs
var extensions = Baz.Descriptor.Extensions.GetExtensionsInDeclarationOrder(Foo.Descriptor);
var registry = new ExtensionRegistry();
registry.AddRange(extensions.Select(f => f.Extension));

var baz = Foo.Descriptor.Parser.WithExtensionRegistry(registry).ParseFrom(input);
foreach (var field in extensions)
{
    if (field.Accessor.HasValue(baz))
    {
        Console.WriteLine($"{field.Name}: {field.Accessor.GetValue(baz)}");
    }
}

Upb Arena Fusion (`docs/upb/arena_fusion.md`)

μpb Arena Fusion and References

μpb generally follows a thread-compatibility model where only operations on a
const pointer may occur concurrently from multiple threads; non-const
operations must not race with each other or operations on const pointers.

Fusion, via upb_Arena_Fuse, binds the lifetime of two arenas together, such
that none are freed until all of the transitively fused arenas reach refcount 0.
This is useful to avoid dangling pointers when a message in one arena references
a message in another - such as when a child message is constructed in isolation
then set onto a parent message - by fusing the parent's arena with the child's,
the child's lifetime is tied to the parent's without needing to copy it.

Both modifying the refcount and fusing are thread safe; if you want to
conceptually perform allocations concurrently from multiple threads with the
same arena lifetime, one way to achieve that would be to share a const upb_Arena* parent plus a dedicated arena per thread, and fuse the
thread-specific arena with the parent arena. By contrast, reference counting
cannot help to achieve concurrent allocations on multiple threads, it only helps
in synchronizing lifetimes from multiple things observing the same arena (which
may be a non-const pointer for multiple writers in a single threaded context,
or a const pointer for multiple readers in a multithreaded context).

To be usable everywhere, fusion has a lock-free implementation, documented
below.

Data Structure

Each arena has three pointer sized members used to track the relationship
between arenas. Together, they implement a hybrid disjoint set forest and
doubly-linked list.

c
// Tagged pointer - tracked as black arrows in diagrams
UPB_ATOMIC(uintptr_t) parent_or_count;
// Linked list - tracked as red arrows in diagrams
UPB_ATOMIC(struct upb_ArenaInternal*) next;
// Linked list - previous tracked as blue arrows in diagrams, tail as dashed
UPB_ATOMIC(uintptr_t) previous_or_tail;

The disjoint set is used to check whether two arenas are already fused and to
provide agreement on a single reference count across all fused arenas. The
linked list is used to implement freeing arenas once the refcount reaches 0, and
to implement upb_Arena_SpaceAllocated.

Two arenas are considered fused when the disjoint set says they are; the linked
list is guaranteed to converge before the refcount reaches 0, but may only track
a subset of fused arenas while racing with fuse calls.

Finding the root

Each set of fused arenas is uniquely identified by the root node of its tree. To
find the root for a given arena, traverse the parent_or_count pointer until
you reach a node with a count instead of a parent pointer - that's the root. To
avoid expensive lookups if frequently repeated, this data structure uses path
splitting to halve the distance from the root for every node traversed.
Repeatedly finding the root for a series of nodes that are part of the same
fused set will converge to O(1) quickly. Let's find the root of D, starting
with:

dot
digraph {
C [label="C (next)"]
D [label="D (current)"]
A -> B [dir=back]
B -> C [dir=back]
C -> D [dir=back]
}

We traverse our parents; each time we pass one we link it to its grandparent, so
that future lookups are faster.

dot
digraph {
B [label="B (next)"]
C [label="C (current)"]
A -> B [dir=back]
B -> C [dir=back]
B -> D [dir=back]
}

Then:

dot
digraph {
A [label="A (next)"]
B [label="B (current)"]
A -> B [dir=back]
A -> C [dir=back]
B -> D [dir=back]
}

D and C's paths got shorter; if we query D's root again, all subsequent lookups
will only require one step.

Fusing

Fusing starts by identifying the roots of each arena to fuse - if they already
share a root, there's nothing to be done. We'll work through the example of
fusing C and D - which is the same as fusing A and B, as they're the roots.

dot
digraph {
A [label="A (refs=2)"]
B [label="B (refs=3)"]
A -> C [dir=back]
A -> C [weight=0.001, color="red"]
C -> A [weight=0.001, color="blue"]
A -> C [style=dashed]
B -> D [dir=back]
B -> D [weight=0.001, color="red"]
D -> B [weight=0.001, color="blue"]
B -> D [style=dashed]
}

A and B are roots, so they store a refcount in their parent_or_count and the
start of the linked list in their next pointers; C and D are not, so they
store pointers to their parent node in parent_or_count. C and D store NULL
in next as they happen to be the last nodes in their set.

Pass refcount

As soon as the parent changes, all future refcount operations will switch to the
new root. To avoid decrementing to 0 while the old root still has active
references, we add the lower-addressed node's refcount to the new root.

dot
digraph {
A [label="A (refs=5)"]
B [label="B (refs=3)"]
A -> C [dir=back]
A -> C [weight=0.001, color="red"]
C -> A [weight=0.001, color="blue"]
A -> C [style=dashed]
B -> D [dir=back]
B -> D [weight=0.001, color="red"]
D -> B [weight=0.001, color="blue"]
B -> D [style=dashed]
}

The total transferred refcount is tracked throughout the operation so it can be
fixed up at the end if retries resulted in over-transfer.

Union

Now the arena with a lower address becomes the new root (A in this case), by
compare-and-exchange the higher address arena's parent_or_count pointer.

dot
digraph {
A [label="A (refs=5)"]
A -> C [dir=back]
A -> C [weight=0.001, color="red"]
C -> A [weight=0.001, color="blue"]
A -> C [style=dashed]
B -> D [dir=back]
B -> D [weight=0.001, color="red"]
D -> B [weight=0.001, color="blue"]
B -> D [style=dashed]
A -> B [dir=back]
}

This atomically eliminates B's refcount and makes it one of A's children. Any
change to B's refcount (or fusing it to a different arena with a lower address)
will make the swap fail, and the process starts again from the beginning.

Refcount fixups

If B's refcount changed while performing the union, we increase or decrease A's
refcount by the difference between the initial refcount we added and B's final
refcount, observed as part of the compare-and-exchange of B's parent_or_count.

Fuse linked lists

To fuse the linked lists, we need to make A's tail point to B. The root node is
always the start of the linked list, so that the list can't contain cycles. From
A's tail pointer, we traverse until we hit the end (C in this case), and CAS its
next to B.

dot
digraph {
A [label="A (refs=5)"]
A -> C [dir=back]
A -> C [weight=0.001, color="red"]
C -> A [weight=0.001, color="blue"]
A -> C [style=dashed]
B -> D [dir=back]
B -> D [weight=0.001, color="red"]
D -> B [weight=0.001, color="blue"]
B -> D [style=dashed]
A -> B [dir=back]
C -> B [weight=0.001, color="red"]
}

With this, B is now reachable from the root and the final free operation will be
able to see it, traversing A->C->B->D.

Update tail pointer

If we stopped at the previous step, it's easy to see how fusing many arenas
could result in O(n^2) behavior, as we'd spend all of our time traversing the
linked list from the root to find the tail. To avoid this, we update A's tail
pointer to point to B's tail - so the next fuse operation will not have to
traverse C or B.

dot
digraph {
A [label="A (refs=5)"]
A -> C [dir=back]
A -> C [weight=0.001, color="red"]
C -> A [weight=0.001, color="blue"]
A -> D [style=dashed]
B -> D [dir=back]
B -> D [weight=0.001, color="red"]
D -> B [weight=0.001, color="blue"]
B -> D [style=dashed]
A -> B [dir=back]
C -> B [weight=0.001, color="red"]
}

To make it possible to always traverse the list in both directions for
upb_Arena_SpaceAllocated, we need to make the matching back link for our
doubly-linked list. Since we linked C to B, we need to link B to C via B's
previous_or_tail pointer.

dot
digraph {
A [label="A (refs=5)"]
A -> C [dir=back]
A -> C [weight=0.001, color="red"]
C -> A [weight=0.001, color="blue"]
A -> D [style=dashed]
B -> D [dir=back]
B -> D [weight=0.001, color="red"]
D -> B [weight=0.001, color="blue"]
B -> C [weight=0.001, color="blue"]
A -> B [dir=back]
C -> B [weight=0.001, color="red"]
}

One-Way References

Fusion creates a bi-directional lifetime dependency between arenas: fused arenas
will have the same lifetime. In some cases, only a one-way dependency is needed.
If messages in arena A contain pointers to messages in arena B but not the
reverse, then arena B only needs to live at least as long as arena A.

upb_Arena_RefArena(A, B) establishes such a one-way dependency by having arena
A increment B's refcount. When arena A is freed, it releases its reference
on B. This is implemented by allocating a special block in A that holds a
pointer to B; this block is processed during upb_Arena_Free(A). Care is
taken to ensure that these blocks are processed before the memory blocks
containing them are freed from the underlying allocator.

Unlike fusion, upb_Arena_RefArena(A, B) is not thread-safe when called
concurrently with other operations on arena A, but it is thread-safe on B.

It is an error to create cycles of references (e.g., upb_Arena_RefArena(A, B); upb_Arena_RefArena(B, A)), or to create a reference between arenas that are
fused. Since fusion creates a bi-directional dependency, fusing arenas A and B
and calling upb_Arena_RefArena(B, A) would create a cycle A <-> B -> A. In
debug builds, upb checks for such cycles, with the implementation documented
below.

Counting allocated space

Traversing the linked list while the arenas are still live can be tricky, as we
can't necessarily reach our own node via the linked list from the root. Instead,
we scan the linked list in both directions starting from the caller-specified
node. This makes space counting weakly consistent - it's possible to see that A
and B are fused, but not see A's space reflected in SpaceAllocated(B) if the
fuse operation that joined them is still in progress. But counting allocated
space is always consistent with itself and any fully complete fuses - nodes are
only appended or prepended to the list, so starting the traversal at the same
point each time guarantees that future scans see a superset of previous ones.
Referenced arenas are not included in the count of allocated space.

Detecting reference cycles

To help prevent memory leaks from uncollectable arenas, upb checks for cycles in
debug builds when creating a reference or fusing arenas. A cycle can be formed
purely by references (eg. A->B->A) or by a combination of references and
fusions (eg. Fuse(A, B), then RefArena(B, A) creates the cycle A<->B->A).
If such cycles were permitted, the arenas involved could never be freed.

After performing a fusion or adding a reference, the cycle detector runs. This
check is performed after the operation because cycle detection cannot be
performed atomically; if the check was done prior to the fusion or reference, we
could miss a case where two concurrent operations resulted in a cycle. Both
would check for a possible cycle, find none, and then proceed.

Consider arenas with references A->B->C.

dot
digraph {
  rankdir=LR;
  A; B; C;
  A -> B [label="ref", color=blue];
  B -> C [label="ref", color=blue];
}

If we attempt RefArena(C, A), the call will add ref C->A, then check that
C is not reachable from A.

dot
digraph {
  rankdir=LR;
  A -> B [label="ref", color=blue];
  B -> C [label="ref", color=blue];
  C -> A [label="ref", color=blue];
}

The traversal will reveal A -> B -> C and trigger an assertion failure.

If instead attempt Fuse(A, C), fusion will occur:

dot
digraph {
  rankdir=LR;
  node [shape=box, style=rounded];
  A -> B [label="ref", color=blue];
  B -> C [label="ref", color=blue];
  C -> A [label="fuse", dir=both, color=red];
}

The traversal will find C <-> A -> B -> C, and trigger an assertion failure.

Cycle detection algorithm

Cycles are detected with a non-memoizing recursive depth-first search (DFS). A
path can traverse fusions bi-directionally and references uni-directionally to
find a cycle that contains at least one unidirectional edge. While this is not
asymptotically optimal as it may traverse the same set of nodes numerous times,
it doesn't require allocating memory, making it less intrusive as a debug-only
check. It also may result in infinite recursion if a cycle is created on one
thread while another thread is performing cycle checks, but that would have
caused an assertion failure anyway.

Fast check for fusion

If a directional ref was added and from and to are already fused
(upb_Arena_IsFused(from, to) returns true), they are mutually reachable, so a
path containing a directed edge exists and the assertion fails.

Traversing fusion group members

To check all references originating from from's fusion group, we must visit
every arena fused with from. Because fusion operations can race with this
check, we cannot rely on traversing from the fusion root, which might change.
Instead, like upb_Arena_SpaceAllocated, the algorithm finds members of
from's fusion group by first traversing backwards from from using
previous_or_tail, then iterates forwards.

Traverse fusion group and DFS on references

From the head of the list segment, we traverse forwards using next to visit
all nodes in from's fusion group. For each arena X in this group, we iterate
through all of its outgoing references (the list of arenas Y such that
RefArena(X, Y) was called). For each such reference X -> Y:

  • If Y == to, then to is directly referenced, so a path exists. We return
    true and eventually trigger an assertion failure.
  • If Y != to, we recurse to continue the depth-first search from Y,
    finding members of its fusion group and inspecting their outgoing
    references. If this recursive call returns true, then to is reachable
    via Y, and we return true to trigger an assertion failure.

If we explore all arenas in the fusion group and all their transitive references
without finding a path to to, no path exists, and we return false.


Upb Design (`docs/upb/design.md`)

upb Design

[TOC]

upb is a protobuf kernel written in C. It is a fast and conformant
implementation of protobuf, with a low-level C API that is designed to be
wrapped in other languages.

upb is not designed to be used by applications directly. The C API is very
low-level, unsafe, and changes frequently. It is important that upb is able to
make breaking API changes as necessary, to avoid taking on technical debt that
would compromise upb's goals of small code size and fast performance.

Design goals

Goals:

  • Full protobuf conformance
  • Small code size
  • Fast performance (without compromising code size)
  • Easy to wrap in language runtimes
  • Easy to adapt to different memory management schemes (refcounting, GC, etc)

Non-Goals:

  • Stable API
  • Safe API
  • Ergonomic API for applications

Parameters:

  • C99
  • 32 or 64-bit CPU (assumes 4 or 8 byte pointers)
  • Uses pointer tagging, but avoids other implementation-defined behavior
  • Aims to never invoke undefined behavior (tests with ASAN, UBSAN, etc)
  • No global state, fully re-entrant

Arenas

All memory management in upb uses arenas, using the type upb_Arena. Arenas are
an alternative to malloc() and free() that significantly reduces the costs
of memory allocation.

Arenas obtain blocks of memory using some underlying allocator (likely
malloc() and free()), and satisfy allocations using a simple bump allocator
that walks through each block in linear order. Allocations cannot be freed
individually: it is only possible to free the arena as a whole, which frees all
of the underlying blocks.

Here is an example of using the upb_Arena type:

c
upb_Arena* arena = upb_Arena_New();

  // Perform some allocations.
  int* x = upb_Arena_Malloc(arena, sizeof(*x));
  int* y = upb_Arena_Malloc(arena, sizeof(*y));

  // We cannot free `x` and `y` separately, we can only free the arena
  // as a whole.
  upb_Arena_Free(arena);

upb uses arenas for all memory management, and this fact is reflected in the API
for all upb data structures. All upb functions that allocate take a upb_Arena*
parameter and perform allocations using that arena rather than calling
malloc() or free().

c
// upb API to create a message.
UPB_API upb_Message* upb_Message_New(const upb_MiniTable* mini_table,
                                     upb_Arena* arena);

void MakeMessage(const upb_MiniTable* mini_table) {
  upb_Arena* arena = upb_Arena_New();

  // This message is allocated on our arena.
  upb_Message* msg = upb_Message_New(mini_table, arena);

  // We can free the arena whenever we want, but we cannot free the
  // message separately from the arena.
  upb_Arena_Free(arena);

  // msg is now deleted.
}

Arenas are a key part of upb's performance story. Parsing a large protobuf
payload usually involves rapidly creating a series of messages, arrays (repeated
fields), and maps. It is crucial for parsing performance that these allocations
are as fast as possible. Equally important, freeing the tree of messages should
be as fast as possible, and arenas can reduce this cost from O(n) to O(lg n).

Avoiding Dangling Pointers

Objects allocated on an arena will frequently contain pointers to other
arena-allocated objects. For example, a upb_Message will have pointers to
sub-messages that are also arena-allocated.

Unlike unique ownership schemes (such as unique_ptr<>), arenas cannot provide
automatic safety from dangling pointers. Instead, upb provides tools to help
bridge between higher-level memory management schemes (GC, refcounting, RAII,
borrow checkers) and arenas.

If there is only one arena, dangling pointers within the arena are impossible,
because all objects are freed at the same time. This is the simplest case. The
user must still be careful not to keep dangling pointers that point at arena
memory after it has been freed, but dangling pointers between the arena
objects will be impossible.

But what if there are multiple arenas? If we have a pointer from one arena to
another, how do we ensure that this will not become a dangling pointer?

To help with the multiple arena case, upb provides a primitive called "fuse".

c
// Fuses the lifetimes of `a` and `b`.  None of the blocks from `a` or `b`
// will be freed until both arenas are freed.
UPB_API bool upb_Arena_Fuse(const upb_Arena* a, const upb_Arena* b);

When two arenas are fused together, their lifetimes are irreversibly joined,
such that none of the arena blocks in either arena will be freed until both
arenas are freed with upb_Arena_Free(). This means that dangling pointers
between the two arenas will no longer be possible.

Fuse is useful when joining two messages from separate arenas (making one a
sub-message of the other). Fuse is a relatively cheap operation, on the order of
150ns, and is very nearly O(1) in the number of arenas being fused (the true
complexity is the inverse Ackermann function, which grows extremely slowly).

Each arena does consume some memory, so repeatedly creating and fusing an
additional arena is not free, but the CPU cost of fusing two arenas together is
modest.

Initial Block and Custom Allocators

upb_Arena normally uses malloc() and free() to allocate and return its
underlying blocks. But this default strategy can be customized to support the
needs of a particular language.

The lowest-level function for creating a upb_Arena is:

c
// Creates an arena from the given initial block (if any -- n may be 0).
// Additional blocks will be allocated from |alloc|.  If |alloc| is NULL,
// this is a fixed-size arena and cannot grow.
UPB_API upb_Arena* upb_Arena_Init(void* mem, size_t n, upb_alloc* alloc);

The buffer [mem, n] will be used as an "initial block", which is used to
satisfy allocations before calling any underlying allocation function. Note that
the upb_Arena itself will be allocated from the initial block if possible, so
the amount of memory available for allocation from the arena will be less than
n.

The alloc parameter specifies a custom memory allocation function which will
be used once the initial block is exhausted. The user can pass NULL as the
allocation function, in which case the initial block is the only memory
available in the arena. This can allow upb to be used even in situations where
there is no heap.

It follows that upb_Arena_Malloc() is a fallible operation, and all allocating
operations like upb_Message_New() should be checked for failure if there is
any possibility that a fixed size arena is in use.

Schemas

Nearly all operations in upb require that you have a schema. A protobuf schema
is a data structure that contains all of the message, field, enum, etc.
definitions that are specified in a .proto file. To create, parse, serialize,
or access a message you must have a schema. For this reason, loading a schema is
generally the first thing you must do when you use upb. [^0]

[^0]: This requirement comes from the protobuf wire format itself, which is a
deep insight about the nature of protobuf (or at least the existing wire
format). Unlike JSON, protobuf cannot be parsed or manipulated in a
schema-less way. This is because the binary wire format does not
distinguish between strings and sub-messages, so a generic parser that is
oblivious to the schema is not possible. If a future version of the wire
format did distinguish between these, it could be possible to have a
schema-agnostic data representation, parser, and serializer.

upb has two main data structures that represent a protobuf schema:

  • MiniTables are a compact, stripped down version of the schema that
    contains only the information necessary for parsing and serializing the
    binary wire format.
  • Reflection contains basically all of the data from a .proto file,
    including the original names of all messages/fields/etc., and all options.

The table below summarizes the main differences between these two:

MiniTables Reflection
Contains Field numbers and types All data in .proto file,
: : only : including names of :
: : : everything :
Used to parse binary format JSON / TextFormat
Wire representation MiniDescriptor Descriptor
Type names upb_MiniTable, upb_MessageDef,
: : upb_MiniTableField, ... : upb_FieldDef, ... :
Registry upb_ExtensionRegistry upb_DefPool
: : (for extensions) : :

MiniTables are useful if you only need the binary wire format, because they are
much lighter weight than full reflection.

Reflection is useful if you need to parse JSON or TextFormat, or you need access
to options that were specified in the proto file. Note that reflection also
includes MiniTables, so if you have reflection, you also have MiniTables
available.

MiniTables

MiniTables are represented by a set of data structures with names like
upb_MiniTable (representing a message), upb_MiniTableField,
upb_MiniTableFile, etc. Whenever you see one of these types in a function
signature, you know that this particular operation requires a MiniTable. For
example:

text
// Parses the wire format data in the given buffer `[buf, size]` and writes it
// to the message `msg`, which has the type `mt`.
UPB_API upb_DecodeStatus upb_Decode(const char* buf, size_t size,
                                    upb_Message* msg, const upb_MiniTable* mt,
                                    const upb_ExtensionRegistry* extreg,
                                    int options, upb_Arena* arena);

The subset of upb that requires only MiniTables can be thought of as "upb lite,"
because both the code size and the runtime memory overhead will be less than
"upb full" (the parts that use reflection).

Loading

There are three main ways of loading a MiniTable:

  1. From C generated code: The upb code generator can emit
    .upb_minitable.c files that contain the MiniTables as global constant
    variables. When the main program links against these, the MiniTable will be
    placed into .rodata (or .data.rel.ro) in the binary. The MiniTable can
    then be obtained from a generated function. In Blaze/Bazel these files can
    be generated and linked using the upb_minitable_proto_library() rule.
  2. From MiniDescriptors: The user can build MiniDescriptors into MiniTables
    at runtime. MiniDescriptors are a compact upb-specific wire format designed
    specially for this purpose. The user can call upb_MiniTable_Build() at
    runtime to convert MiniDescriptors to MiniTables.
  3. From reflection: If you have already built reflection data structures
    for your type, then you can obtain the upb_MiniTable corresponding to a
    upb_MessageDef using upb_MessageDef_MiniTable().

For languages that are already using reflection, (3) is an obvious choice.

For languages that are avoiding reflection, here is a general guideline for
choosing between (1) and (2): if the language being wrapped participates in the
standard binary linking model on a given platform (in particular, if it is
generally linked using ld), then it is better to use (1), which is also known
as "static loading".

Static loading of MiniTables has the benefit of requiring no runtime
initialization[^2], leading to faster startup. Static loading of MiniTables also
facilitates cross-language sharing of proto messages, because sharing generally
requires that both languages are using exactly the same MiniTables.

The main downside of static loading is that it requires the user to generate one
.upb.c file per .proto and link against the transitive closure of .upb.c
files. Blaze and Bazel make this reasonably easy, but for other build systems it
can be more of a challenge.

[^2]: aside from possible pointer relocations performed by the ELF/Mach-O loader
if the library or binary is position-independent

Loading from MiniDescriptors, as in option (2), has the advantage that it does
not require per-message linking of C code. For many language toolchains,
generating and linking some custom C code for every protobuf file or message
type would be a burdensome requirement. MiniDescriptors are a convenient way of
loading MiniTables without needing to cross the FFI boundary outside the core
runtime.

A common pattern when using dynamic loading is to embed strings containing
MiniDescriptors directly into generated code. For example, the generated code in
Dart for a message with only primitive fields currently looks something like:

dart
const desc = r'$(+),*-#$%&! /10';
  _accessor = $pb.instance.registry.newMessageAccessor(desc);

The implementation of newMessageAccessor() is mainly just a wrapper around
upb_MiniTable_Build(), which builds a MiniTable from a MiniDescriptor. In the
code generator, the MiniDescriptor can be obtained from the
upb_MessageDef_MiniDescriptorEncode() API; users should never need to encode a
MiniDescriptor manually.

Linking

When building MiniTables dynamically, it is the user's responsibility to link
each message to the to the appropriate sub-messages and or enums. Each message
must have its message and closed enum fields linked using
upb_MiniTable_SetSubMessage() and upb_MiniTable_SetSubEnum(), respectively.

A higher-level function that links all fields at the same time is also
available, as upb_MiniTable_Link(). This function pairs well with
upb_MiniTable_GetSubList() which can be used in a code generator to get a list
of all the messages and enums which must be passed to upb_MiniTable_Link().

A common pattern is to embed the link() calls directly into the generated
code. For example, here is an example from Dart of building a MiniTable that
contains sub-messages and enums:

dart
const desc = r'$3334';
  _accessor = $pb.instance.registry.newMessageAccessor(desc);
  _accessor!.link(
      [
        M2.$_accessor,
        M3.$_accessor,
        M4.$_accessor,
      ],
      [
        E.$_accessor,
      ],
  );

In this case, upb_MiniTable_GetSubList() was used in the code generator to
discover the 3 sub-message fields and 1 sub-enum field that require linking. At
runtime, these lists of MiniTables are passed to the link() function, which
will internally call upb_MiniTable_Link().

Note that in some cases, the application may choose to delay or even skip the
registration of sub-message types, as part of a tree shaking strategy.

When using static MiniTables, a manual link step is not necessary, as linking is
performed automatically by ld.

Enums

MiniTables primarily carry data about messages, fields, and extensions. However
for closed enums, we must also have a upb_MiniTableEnum structure that stores
the set of all numbers that are defined in the enum. This is because closed
enums have the unfortunate behavior of putting unknown enum values into the
unknown field set.

Over time, closed enums will hopefully be phased out via editions, and the
relevance and overhead of upb_MiniTableEnum will shrink and eventually
disappear.

Reflection

Reflection uses types like upb_MessageDef and upb_FieldDef to represent the
full contents of a .proto file at runtime. These types are upb's direct
equivalents of google::protobuf::Descriptor, google::protobuf::FieldDescriptor, etc. [^1]

[^1]: upb consistently uses Def where C++ would use Descriptor in type
names. This introduces divergence with C++; the rationale was to conserve
horizontal line length, as Def is less than 1/3 the length of
Descriptor. This is more relevant in C, where the type name is repeated
in every function, eg. upb_FieldDef_Name() vs.
upb_FieldDescriptor_Name().

Whenever you see one of these types in a function signature, you know that the
given operation requires reflection. For example:

c
// Parses JSON format into a message object, using reflection.
UPB_API bool upb_JsonDecode(const char* buf, size_t size, upb_Message* msg,
                            const upb_MessageDef* m, const upb_DefPool* symtab,
                            int options, upb_Arena* arena, upb_Status* status);

The part of upb that requires reflection can be thought of as "upb full." These
parts of the library cannot be used if a given application has only loaded
MiniTables. There is no way to convert a MiniTable into reflection.

The upb_DefPool type is the top-level container that builds and owns some set
of defs. This type is a close analogue of google::protobuf::DescriptorPool in C++. The
user must always ensure that the upb_DefPool outlives any def objects that it
owns.

Loading

As with MiniTable loading, we have multiple options for how to load full
reflection:

  1. From C generated code: The upb code generator can create foo.upbdefs.c
    files that embed the descriptors and exports generated C functions for
    adding them to a user-provided upb_DefPool.
  2. From descriptors: The user can make manual calls to
    upb_DefPool_AddFile(), using descriptors obtained at runtime. Defs for
    individual messages can then be obtained using
    upb_DefPool_FindMessageByName().

Unlike MiniTables, loading from generated code requires runtime initialization,
as reflection data structures like upb_MessageDef are not capable of being
emitted directly into .rodata like upb_MiniTable is. Instead, the generated
code embeds serialized descriptor protos into .rodata which are then built
into heap objects at runtime.

From this you might conclude that option (1) is nothing but a convenience
wrapper around option (2), but that is not quite correct either. Option (1)
does link against the static .upb.c structures for the MiniTables, whereas
option (2) will build the MiniTables from scratch on the heap. So option (1)
will use marginally less CPU and RAM when the descriptors are loaded into a
upb_DefPool. More importantly, the resulting descriptors will be capable of
reflecting over any messages built from the generated .upb.c MiniTables,
whereas descriptors built using option (2) will have distinct MiniTables that
cannot reflect over messages that use the generated MiniTables.

A common pattern for dynamic languages like PHP, Ruby, or Python, is to use
option (2) with descriptors that are embedded into the generated code. For
example, the generated code in Python currently looks something like:

python
from google.protobuf import descriptor_pool as _descriptor_pool
from google.protobuf.internal import builder as _builder

_desc = b'\n\x1aprotoc_explorer/main.proto\x12\x03pkg'

DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(_desc)
_globals = globals()
_builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals)
_builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'google3.protoc_explorer.main_pb2', _globals)

The AddSerializedFile() API above is mainly just a thin wrapper around
upb_DefPool_AddFile().


Upb Style Guide (`docs/upb/style-guide.md`)

C style guide

Since upb is written in pure C, we supplement the
Google C++ style guide with
some C-specific guidance.

Everything written here is intended to follow the spirit of the C++ style guide.

upb is currently inconsistent about following these conventions. It is intended
that all code will be updated to match these guidelines. The priority is
converting public interfaces as these are more difficult to change later.

Naming

Functions and Types

C does not have namespaces. Anywhere you would normally use a namespace
separator (::) in C++, we use an underscore (_) in C:

c++ {.good}
// C equivalent for upb::Arena::New()
upb_Arena* upb_Arena_New();

Since we rely on _ to be our namespace separator, we never use it to merely
separate words in function or type names:

c++ {.bad}
// BAD: this would be interpreted as upb::FieldDef::has::default().
bool upb_FieldDef_has_default(const upb_FieldDef* f);
c++ {.good}
// GOOD: this is equivalent to upb::FieldDef::HasDefault().
bool upb_FieldDef_HasDefault(const upb_FieldDef* f);

For multi-word namespaces, we use PascalCase:

c++ {.good}
// `PyUpb` is the namespace.
PyObject* PyUpb_CMessage_GetAttr(PyObject* _self, PyObject* attr);

Private Functions and Members

Since we do not have private in C, we use the UPB_PRIVATE() macro to mark
internal functions and variables that should only be accessed from upb:

c++ {.good}
// Internal-only function.
int64_t UPB_PRIVATE(upb_Int64_FromLL)();

// Internal-only members. Underscore prefixes are only necessary when the
// structure is defined in a header file.
typedef struct {
  const int32_t* UPB_PRIVATE(values);
  uint64_t UPB_PRIVATE(mask);
  int UPB_PRIVATE(value_count);
} upb_MiniTableEnum;

// Using these members in an internal function.
int upb_SomeFunction(const upb_MiniTableEnum* e) {
  return e->UPB_PRIVATE(value_count);
}

This prevents users from accessing these symbols, because UPB_PRIVATE()
is defined in port_def.inc and undefined in port_undef.inc, and these
textual headers are not accessible by users.

It is only necessary to use UPB_PRIVATE() for things defined in headers.
For symbols in .c files, it is sufficient to mark private functions with
static.


Upb Vs Cpp Protos (`docs/upb/vs-cpp-protos.md`)

upb vs. C++ Protobuf Design

upb is a small C
protobuf library. While some of the design follows in the footsteps of the C++
Protobuf Library, upb departs from C++'s design in several key ways. This
document compares and contrasts the two libraries on several design points.

Design Goals

Before we begin, it is worth calling out that upb and C++ have different design
goals, and this motivates some of the differences we will see.

C++ protobuf is a user-level library: it is designed to be used directly by C++
applications. These applications will expect a full-featured C++ API surface
that uses C++ idioms. The C++ library is also willing to add features to
increase server performance, even if these features would add size or complexity
to the library. Because C++ protobuf is a user-level library, API stability is
of utmost importance: breaking API changes are rare and carefully managed when
they do occur. The focus on C++ also means that ABI compatibility with C is not
a priority.

upb, on the other hand, is designed primarily to be wrapped by other languages.
It is a C protobuf kernel that forms the basis on which a user-level protobuf
library can be built. This means we prefer to keep the API surface as small and
orthogonal as possible. While upb supports all protobuf features required for
full conformance, upb prioritizes simplicity and small code size, and avoids
adding features like lazy fields that can accelerate some use cases but at great
cost in terms of complexity. As upb is not aimed directly at users, there is
much more freedom to make API-breaking changes when necessary, which helps the
core to stay small and simple. We want to be compatible with all FFI
interfaces, so C ABI compatibility is a must.

Despite these differences, C++ protos and upb offer roughly the same core set
of
features
.

Arenas

upb and C++ protos both offer arena allocation, but there are some key
differences.

C++

As a matter of history, when C++ protos were open-sourced in 2008, they did not
support arenas. Originally there was only unique ownership, whereby each
message uniquely owns all child messages and will free them when the parent is
freed.

Arena allocation was added as a feature in 2014 as a way of dramatically
reducing allocation and (especially) deallocation costs. But the library was
not at liberty to remove the unique ownership model, because it would break far
too many users. As a result, C++ has supported a hybrid allocation model
ever since, allowing users to allocate messages either directly from the
stack/heap or from an arena. The library attempts to ensure that there are
no dangling pointers by performing automatic copies in some cases (for example
a->set_allocated_b(b), where a and b are on different arenas).

C++'s arena object itself google::protobuf::Arena is thread-safe by
design, which allows users to allocate from multiple threads simultaneously
without external synchronization. The user can supply an initial block of
memory to the arena, and can choose some parameters to control the arena block
size. The user can also supply block alloc/dealloc functions, but the alloc
function is expected to always return some memory. The C++ library in general
does not attempt to handle out of memory conditions.

upb

upb uses arena allocation exclusively. All messages must be allocated from
an arena, and can only be freed by freeing the arena. It is entirely the user's
responsibility to ensure that there are no dangling pointers: when a user sets a
message field, this will always trivially overwrite the pointer and will never
perform an implicit copy.

upb's upb::Arena is thread-compatible, which means it cannot be used
concurrently without synchronization. The arena can be seeded with an initial
block of memory, but it does not explicitly support any parameters for choosing
block size. It supports a custom alloc/dealloc function, and this function is
allowed to return NULL if no dynamic memory is available. This allows upb
arenas to have a max/fixed size, and makes it possible in theory to write code
that is tolerant to out-of-memory errors.

upb's arena also supports a novel operation known as fuse, which joins two
arenas together into a single lifetime. Though both arenas must still be freed
separately, none of the memory will actually be freed until both arenas have
been freed. This is useful for avoiding dangling pointers when reparenting a
message with one that may be on a different arena.

Comparison

hybrid allocation vs. arena-only

  • The C++ hybrid allocation model introduces a great deal of complexity and
    unpredictability into the library. upb benefits from having a much simpler
    and more predictable design.
  • Some of the complexity in C++'s hybrid model arises from the fact that arenas
    were added after the fact. Designing for a hybrid model from the outset
    would likely yield a simpler result.
  • Unique ownership does support some usage patterns that arenas cannot directly
    accommodate. For example, you can reparent a message and the child will precisely
    follow the lifetime of its new parent. An arena would require you to either
    perform a deep copy or extend the lifetime.

thread-compatible vs. thread-safe arena

  • A thread-safe arena (as in C++) is safer and easier to use. A thread-compatible
    arena requires that the user prove that the arena cannot be used concurrently.
  • Thread Sanitizer
    is far more accessible than it was in 2014 (when C++ introduced a thread-safe
    arena). We now have more tools at our disposal to ensure that we do not trigger
    data races in a thread-compatible arena like upb.
  • Thread-compatible arenas are more performant.
  • Thread-compatible arenas have a far simpler implementation. The C++ thread-safe
    arena relies on thread-local variables, which introduce complications on some
    platforms. It also requires far more subtle reasoning for correctness and
    performance.

fuse vs. no fuse

  • The upb_Arena_Fuse() operation is a key part of how upb supports reparenting
    of messages when the parent may be on a different arena. Without this, upb has
    no way of supporting foo.bar = bar in dynamic languages without performing a
    deep copy.
  • A downside of upb_Arena_Fuse() is that passing an arena to a function can allow
    that function to extend the lifetime of the arena in potentially
    unpredictable ways. This can be prevented if necessary, as fuse can fail, eg. if
    one arena has an initial block. But this adds some complexity by requiring callers
    to handle the case where fuse fails.

Code Generation vs. Tables

The C++ protobuf library has always been built around code generation, while upb
generates only tables. In other words, foo.pb.cc files contain functions,
whereas foo.upb.c files emit only data structures.

C++

C++ generated code emits a large number of functions into foo.pb.cc files.
An incomplete list:

  • FooMsg::FooMsg() (constructor): initializes all fields to their default value.
  • FooMsg::~FooMsg() (destructor): frees any present child messages.
  • FooMsg::Clear(): clears all fields back to their default/empty value.
  • FooMsg::_InternalParse(): generated code for parsing a message.
  • FooMsg::_InternalSerialize(): generated code for serializing a message.
  • FooMsg::ByteSizeLong(): calculates serialized size, as a first pass before serializing.
  • FooMsg::MergeFrom(): copies/appends present fields from another message.
  • FooMsg::IsInitialized(): checks whether required fields are set.

This code lives in the .text section and contains function calls to the generated
classes for child messages.

upb

upb does not generate any code into foo.upb.c files, only data structures. upb uses a
compact data table known as a mini table to represent the schema and all fields.

upb uses mini tables to perform all of the operations that would traditionally be done
with generated code. Revisiting the list from the previous section:

  • FooMsg::FooMsg() (constructor): upb instead initializes all messages with memset(msg, 0, size).
    Non-zero defaults are injected in the accessors.
  • FooMsg::~FooMsg() (destructor): upb messages are freed by freeing the arena.
  • FooMsg::Clear(): can be performed with memset(msg, 0, size).
  • FooMsg::_InternalParse(): upb's parser uses mini tables as data, instead of generating code.
  • FooMsg::_InternalSerialize(): upb's serializer also uses mini-tables instead of generated code.
  • FooMsg::ByteSizeLong(): upb performs serialization in reverse so that an initial pass is not required.
  • FooMsg::MergeFrom(): upb supports this via serialize+parse from the other message.
  • FooMsg::IsInitialized(): upb's encoder and decoder have special flags to check for required fields.
    A util library upb/util/required_fields.h handles the corner cases.

Comparison

If we compare compiled code size, upb is far smaller. Here is a comparison of the code
size of a trivial binary that does nothing but a parse and serialize of descriptor.proto.
This means we are seeing both the overhead of the core library itself as well as the
generated code (or table) for descriptor.proto. (For extra clarity we should break this
down by generated code vs core library in the future).

Library .text .data .bss
upb 26Ki 0.6Ki 0.01Ki
C++ (lite) 187Ki 2.8Ki 1.25Ki
C++ (code size) 904Ki 6.1Ki 1.88Ki
C++ (full) 983Ki 6.1Ki 1.88Ki

"C++ (code size)" refers to protos compiled with optimize_for = CODE_SIZE, a mode
in which generated code contains reflection only, in an attempt to make the
generated code size smaller (however it requires the full runtime instead
of the lite runtime).

Bifurcated vs. Optional Reflection

upb and C++ protos both offer reflection without making it mandatory. However
the models for enabling/disabling reflection are very different.

C++

C++ messages offer full reflection by default. Messages in C++ generally
derive from Message, and the base class provides a member function
Reflection* Message::GetReflection() which returns the reflection object.

It follows that any message deriving from Message will always have reflection
linked into the binary, whether or not the reflection object is ever used.
Because GetReflection() is a function on the base class, it is not possible
to statically determine if a given message's reflection is used:

c++
Reflection* GetReflection(const Message& message) {
    // Can refer to any message in the whole binary.
    return message.GetReflection();
}

The C++ library does provide a way of omitting reflection: MessageLite. We can
cause a message to be lite in two different ways:

  • optimize_for = LITE_RUNTIME in a .proto file will cause all messages in that
    file to be lite.
  • lite as a codegen param: this will force all messages to lite, even if the
    .proto file does not have optimize_for = LITE_RUNTIME.

A lite message will derive from MessageLite instead of Message. Since
MessageLite has no GetReflection() function, this means no reflection is
available, so we can avoid taking the code size hit.

upb

upb does not have the Message vs. MessageLite bifurcation. There is only one
kind of message type upb_Message, which means there is no need to configure in
a .proto file which messages will need reflection and which will not.
Every message has the option to link in reflection from a separate foo.upbdefs.o
file, without needing to change the message itself in any way.

upb does not provide the equivalent of Message::GetReflection(): there is no
facility for retrieving the reflection of a message whose type is not known statically.
It would be possible to layer such a facility on top of the upb core, though this
would probably require some kind of code generation.

Comparison

  • Most messages in C++ will not bother to declare themselves as "lite". This means
    that many C++ messages will link in reflection even when it is never used, bloating
    binaries unnecessarily.
  • optimize_for = LITE_RUNTIME is difficult to use in practice, because it prevents
    any non-lite protos from importing that file.
  • Forcing all protos to lite via a codegen parameter (for example, when building for
    mobile) is more practical than optimize_for = LITE_RUNTIME. But this will break
    the compile for any code that tries to upcast to Message, or tries to use a
    non-lite method.
  • The one major advantage of the C++ model is that it can support msg.DebugString()
    on a type-erased proto. For upb you have to explicitly pass the upb_MessageDef*
    separately if you want to perform an operation like printing a proto to text format.

Explicit Registration vs. Globals

TODO


Upb Wrapping Upb (`docs/upb/wrapping-upb.md`)

Building a protobuf library on upb

This is a guide for creating a new protobuf implementation based on upb. It
starts from the beginning and walks you through the process, highlighting
some important design choices you will need to make.

Overview

A protobuf implementation consists of two main pieces:

  1. a code generator, run at compile time, to turn .proto files into source
    files in your language (we will call this "zlang", assuming an extension of ".z").
  2. a runtime component, which implements the wire format and provides the data
    structures for representing protobuf data and metadata.
dot {align="center"}
digraph {
    rankdir=LR; 
    newrank=true;
    node [style="rounded,filled" shape=box]
    "foo.proto" -> protoc;
    "foo.proto" [shape=folder];
    protoc [fillcolor=lightgrey];
    protoc -> "protoc-gen-zlang";
    "protoc-gen-zlang" -> "foo.z";
    "protoc-gen-zlang" [fillcolor=palegreen3];
    "foo.z" [shape=folder];
    labelloc="b";
    label="Compile Time";
}
dot {align="center"}
digraph {
    newrank=true;
    node [style="rounded,filled" shape=box fillcolor=lightgrey]
    "foo.z" -> "zlang/upb glue (FFI)";
    "zlang/upb glue (FFI)" -> "upb (C)";
    "zlang/upb glue (FFI)" [fillcolor=palegreen3];
    labelloc="b";
    label="Runtime";
}

The parts in green are what you will need to implement.

Note that your code generator (protoc-gen-zlang) does not need to generate
any C code (eg. foo.c). While upb itself is written in C, upb's parsers and
serializers are fully table-driven, which means there is never any need or even
benefit to generating C code for each proto. upb is capable of full-speed
parsing even when schema data is loaded at runtime from strings embedded into
foo.z. This is a key benefit of upb compared with C++ protos, which have
traditionally relied on generated parsers in foo.pb.cc files to achieve full
parsing speed, and suffered a ~10x speed penalty in the parser when the schema
data was loaded at runtime.

Prerequisites

There are a few things that the language runtime must provide in order to wrap
upb.

  1. FFI: To wrap upb, your language must be able to call into a C API
    through a Foreign Function Interface (FFI). Most languages support FFI in
    some form, either through "native extensions" (in which you write some C
    code to implement new methods in your language) or through a direct FFI (in
    which you can call into regular C functions directly from your language
    using a special library).
  2. Finalizers, Destructors, or Cleaners: The runtime must provide
    finalizers or destructors of some sort. There must be a way of triggering a
    call to a C function when the language garbage collects or otherwise
    destroys an object. We don't care much whether it is a finalizer, a
    destructor, or a cleaner, as long as it gets called eventually when the
    object is destroyed. upb allocates memory in C space, and a finalizer is our
    only way of making sure that memory is freed and does not leak.
  3. HashMap with weak values: (optional) This is not a strong requirement,
    but it is sometimes helpful to have a global hashmap with weak values to act
    as a upb_msg* -> wrapper object cache. We want the values to be weak (not
    the keys). There is some question about whether we want to continue to use
    this pattern going forward.

Reflection vs. MiniTables

The first key design decision you will need to make is whether your generated
code will access message data via reflection or minitables. Generally more
dynamic languages will want to use reflection and more static languages will
want to use minitables.

Reflection

Reflection-based data access makes the most sense in highly dynamic language
interpreters, where method dispatch is generally resolved via strings and hash
table lookups.

In such languages, you can often implement a special method like __getattr__
(Python) or method_missing (Ruby) that receives the method name as a string.
Using upb's reflection, you can look up a field name using the method name,
thereby using a hash table belonging to upb instead of one provided by the
language.

python
class FooMessage:
  # Written in Python for illustration, but in practice we will want to
  # implement this in C for speed.
  def __getattr__(self, name):
    field = FooMessage.descriptor.fields_by_name[name]
    return field.get_value(self)

Using this design, we only need to attach a single __getattr__ method to each
message class, instead of defining a getter/setter for each field. In this way
we can avoid duplicating hash tables between upb and the language interpreter,
reducing memory usage.

Reflection-based access requires loading full reflection at runtime. Your
generated code will need to embed serialized descriptors (ie. a serialized
message of descriptor.proto), which has some amount of size overhead and
exposes all message/field names to the binary. It also forces a hash table
lookup in the critical path of field access. If method calls in your language
already have this overhead, then this is no added burden, but for statically
dispatched languages it would cause extra overhead.

If we take this path to its logical conclusion, all class creation can be
performed fully dynamically, using only a binary descriptor as input. The
"generated code" becomes little more than an embedded descriptor plus a
library call to load it. Python has recently gone down this path. Generated
code now looks something like this:

python
# main_pb2.py
from google3.net.proto2.python.internal import builder as _builder
from google3.net.proto2.python.public import descriptor_pool as _descriptor_pool

DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile("<...>")
_builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, globals())
_builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'google3.main_pb2', globals())

This is all the runtime needs to create all of the classes for messages defined
in that serialized descriptor. This code has no pretense of readability, but
a separate .pyi stub file provides a fully expanded and readable list of all
methods a user can expect to be available:

python
# main_pb2.pyi
from google3.net.proto2.python.public import descriptor as _descriptor
from google3.net.proto2.python.public import message as _message
from typing import ClassVar as _ClassVar, Optional as _Optional

DESCRIPTOR: _descriptor.FileDescriptor

class MyMessage(_message.Message):
    __slots__ = ["my_field"]
    MY_FIELD_FIELD_NUMBER: _ClassVar[int]
    my_field: str
    def __init__(self, my_field: _Optional[str] = ...) -> None: ...

To use reflection-based access:

  1. Load and access descriptor data using the interfaces in upb/reflection/def.h.
  2. Access message data using the interfaces in upb/reflection/message.h.

MiniTables

MiniTables are a "lite" schema representation that are much smaller than
reflection. MiniTables omit names, options, and almost everything else from the
.proto file, retaining only enough information to parse and serialize binary
format.

MiniTables can be loaded into upb through MiniDescriptors. MiniDescriptors are
a byte-oriented format that can be embedded into your generated code and passed
to upb to construct MiniTables. MiniDescriptors only use printable characters,
and therefore do not require escaping when embedding them into generated code
strings. Overall the size savings of MiniDescriptors are ~60x compared with
regular descriptors.

MiniTables and MiniDescriptors are a natural choice for compiled languages that
resolve method calls at compile time. For languages that are sometimes compiled
and sometimes interpreted, there might not be an obvious choice. When a method
call is statically bound, we want to remove as much overhead as possible,
especially from accessors. In the extreme case, we can use unsafe APIs to read
raw memory at a known offset:

java
// Example of a maximally-optimized generated accessor.
class FooMessage {
    public long getBarField() {
        // Using Unsafe should give us performance that is comparable to a
        // native member access.
        //
        // The constant "24" is obtained from upb at compile time.
        sun.misc.Unsafe.getLong(this.ptr, 24);
    }
}

This design is very low-level, and tightly couples the generated code to one
specific version of the schema and compiler. A slower but safer version would
look up a field by field number:

java
// Example of a more loosely-coupled accessor.
class FooMessage {
    public long getBarField() {
        // The constant "2" is the field number.  Internally this will look
        // up the number "2" in the MiniTable and use that to read the value
        // from the message.
        upb.glue.getLong(this.ptr, 2);
    }
}

One downside of MiniTables is that they cannot support parsing or serializing
to JSON or TextFormat, because they do not know the field names. It should be
possible to generate reflection data "on the side", into separate generated
code files, so that reflection is only pulled in if it is being used. However
APIs to do this do not exist yet.

To use MiniTable-based access:

  1. Load and access MiniDescriptors data using the interfaces in upb/mini_descriptor/decode.h.
  2. Access message data using the interfaces in upb/message/accessors.h.

Memory Management

One of the core design challenges when wrapping upb is memory management. Every
language runtime will have some memory management system, whether it is
garbage collection, reference counting, manual memory management, or some hybrid
of these. upb is written in C and uses arenas for memory management, but upb is
designed to integrate with a wide variety of memory management schemes, and it
provides a number of tools for making this integration as smooth as possible.

Arenas

upb defines data structures in C to represent messages, arrays (repeated
fields), and maps. A protobuf message is a hierarchical tree of these objects.
For example, a relatively simple protobuf tree might look something like this:

dot {align="center"}
digraph G {
  rankdir=LR;
  newrank=true;
  node [style="rounded,filled" shape=box colorscheme=accent8 fillcolor=1, ordering=out]
  upb_msg -> upb_msg2;
  upb_msg -> upb_array;
  upb_msg [label="upb Message" fillcolor=1]
  upb_msg2 [label="upb Message"];
  upb_array [label="upb Array"]
}

All upb objects are allocated from an arena. An arena lets you allocate objects
individually, but you cannot free individual objects; you can only free the arena
as a whole. When the arena is freed, all of the individual objects allocated
from that arena are freed together.

dot {align="center"}
digraph G {
  rankdir=LR;
  newrank=true;
  subgraph cluster_0 {
    label = "upb Arena"
    graph[style="rounded,filled" fillcolor=gray]
    node [style="rounded,filled" shape=box colorscheme=accent8 fillcolor=1, ordering=out]
    upb_msg -> upb_array;
    upb_msg -> upb_msg2;
    upb_msg [label="upb Message" fillcolor=1]
    upb_msg2 [label="upb Message"];
    upb_array [label="upb Array"];
  }
}

In simple cases, the entire tree of objects will all live in a single arena.
This has the nice property that there cannot be any dangling pointers between
objects, since all objects are freed at the same time.

However upb allows you to create links between any two objects, whether or
not they are in the same arena. The library does not know or care what arenas
the objects are in when you create links between them.

dot {align="center"}
digraph G {
  rankdir=LR;
  newrank=true;
  subgraph cluster_0 {
    label = "upb Arena 1"
    graph[style="rounded,filled" fillcolor=gray]
    node [style="rounded,filled" shape=box colorscheme=accent8 fillcolor=1, ordering=out]
    upb_msg -> upb_array;
    upb_msg -> upb_msg2;
    upb_msg [label="upb Message 1" fillcolor=1]
    upb_msg2 [label="upb Message 2"];
    upb_array [label="upb Array"];
  }
  subgraph cluster_1 {
    label = "upb Arena 2"
    graph[style="rounded,filled" fillcolor=gray]
    node [style="rounded,filled" shape=box colorscheme=accent8 fillcolor=1]
    upb_msg3;
  }
  upb_msg2 -> upb_msg3;
  upb_msg3 [label="upb Message 3"];
}

When objects are on separate arenas, it is the user's responsibility to ensure
that there are no dangling pointers. In the example above, this means Arena 2
must outlive Message 1 and Message 2.

Integrating GC with upb

In languages with automatic memory management, the goal is to handle all of the
arenas behind the scenes, so that the user does not have to manage them manually
or even know that they exist.

We can achieve this goal if we set up the object graph in a particular way. The
general strategy is to create wrapper objects around all of the C objects,
including the arena. Our key goal is to make sure the arena wrapper is not
GC'd until all of the C objects in that arena have become unreachable.

For this example, we will assume we are wrapping upb in Python:

text
/* Detailed source-code truncated for AI context efficiency. */

In this example we have three different kinds of pointers:

  • raw ptr: This is a pointer that carries no ownership.
  • unique ptr: This is a pointer has unique ownership of the target. The owner
    will free the target in its destructor (or finalizer, or cleaner). There can
    only be a single unique pointer to a given object.
  • shared (GC) ptr: This is a pointer that has shared ownership of the
    target. Many objects can point to the target, and the target will be deleted
    only when all such references are gone. In a runtime with automatic memory
    management (GC), this is a reference that participates in GC. In Python such
    references use reference counting, but in other VMs they may use mark and
    sweep or some other form of GC instead.

The Python Message wrappers have only raw pointers to the underlying message,
but they contain a shared pointer to the arena that will ensure that the raw
pointer remains valid. Only when all message wrapper objects are destroyed
will the Python Arena become unreachable, and the upb arena ultimately freed.

The design given above works well for objects that live in a single arena. But
what if a user wants to create a link between two objects in different arenas?

TODO

UTF-8 vs. UTF-16

TODO

Object Cache

TODO


Python Docs Environment.Yml (`python/docs/environment.yml`)

name: protobuf
channels:

  • defaults
    dependencies:
  • libprotobuf=3.11.4
  • make=4.2.1
  • pip=19.3.1
  • python=3.7.6
  • sphinx=2.4.0
  • sphinx_rtd_theme=0.4.3

Python Docs Index (`python/docs/index.rst`)

.. Protocol Buffers documentation master file, created by
sphinx-quickstart on Thu Aug 15 13:56:43 2019.
You can adapt this file completely to your liking, but it should at least
contain the root toctree directive.

.. ifconfig:: build_env == 'readthedocs'

.. warning::

text
You are reading the documentation for the `latest committed changes
  <https://github.com/protocolbuffers/protobuf/tree/master/python>`_ of
  the `Protocol Buffers package for Python
  <https://developers.google.com/protocol-buffers/docs/pythontutorial>`_.
  Some features may not yet be released. Read the documentation for the
  latest released package at `googleapis.dev
  <https://googleapis.dev/python/protobuf/latest/>`_.

Protocol Buffers Python API Reference

The complete documentation for Protocol Buffers is available via the web at:

https://developers.google.com/protocol-buffers/

Modules and Packages

.. START REFTOC, generated by generate_docs.py.
.. toctree::

google/protobuf
google/protobuf/any_pb2
google/protobuf/descriptor
google/protobuf/descriptor_database
google/protobuf/descriptor_pb2
google/protobuf/descriptor_pool
google/protobuf/duration_pb2
google/protobuf/empty_pb2
google/protobuf/field_mask_pb2
google/protobuf/internal/containers
google/protobuf/json_format
google/protobuf/message
google/protobuf/message_factory
google/protobuf/proto_builder
google/protobuf/reflection
google/protobuf/service
google/protobuf/service_reflection
google/protobuf/struct_pb2
google/protobuf/symbol_database
google/protobuf/text_encoding
google/protobuf/text_format
google/protobuf/timestamp_pb2
google/protobuf/type_pb2
google/protobuf/unknown_fields
google/protobuf/wrappers_pb2

.. END REFTOC.

Indices and tables

  • :ref:genindex
  • :ref:modindex

Python Docs Requirements (`python/docs/requirements.txt`)

googleapis-common-protos==1.56.1
jinja2==3.1.6
sphinx==3.0.4
sphinx_rtd_theme==0.4.3
sphinxcontrib-napoleon==0.7


Design Editions Cpp Apis For Edition Zero (`docs/design/editions/cpp-apis-for-edition-zero.md`)

C++ APIs for Edition Zero

Author: @mcy

Approved: 2022-06-27

Overview

As recorded in FileDescriptor::syntaxAudit Report (not available externally),
there are significant uses of FileDescriptor::syntax() in internal Google
repositories that Edition Zero will break; for example, existing usage checks
syntax() == PROTO3 to determine if enums are open.

Because we expect to make everything that callers are querying be controlled by
individual edition-gated features, the cleanest way forward is to enrich the
Descriptor types with focused APIs that serve current usages, followed by a
migration to them.

Tier 1 proposal:

Proposed API

This proposal adds the following code to descriptor.h:

text
class FileDescriptor {
  // Copies package, syntax, edition, dependencies, and file-level options.
  void CopyHeadingTo(FileDescriptorProto*) const;
};
class FieldDescriptor {
  // Returns whether this field has a proto3-like zero default value.
  bool has_zero_default_value() const;
  // Returns whether this is a string field that enforces UTF-8 in the codec.
  bool enforces_utf8() const;
};
class EnumDescriptor {
  // Returns whether this enum is a proto2-style closed enum.
  bool is_closed() const;
};

This covers everything not already covered by existing accessors.
CopyHeadingTo is intended to simplify the common, easy-to-get-wrong pattern of
copying the heading of a proto file before doing custom manipulation of
descriptors within.

Additionally, we propose generating unknown_fields() and
mutable_unknown_fields() for all protos unconditionally.

Migration

In addition to the above functions, we'll use FieldDescriptor::has_presence()
and FieldDescriptor::is_packed() to migrate callers performing comparisons to
syntax(). Users can migrate to the new functions by:

  1. Searching for calls to syntax().
  2. Identifying what proto2/proto3 distinction is being picked up on, among the
    following:
    1. UTF-8 verification on parse (use new API).
    2. Closed/open enum (use new API).
    3. Packed-ness (use existing API instead of guessing from syntax).
    4. Whether fields have hasbits (use has_presence()).
  3. Migrate to the new API.

The volume of such changes in google3 is small enough that it's probably easiest
to create a giant CL that fixes every instance of a particular misuse and then
hand it to Rosie for splitting up.

Once all the "easy" usages are migrated, we will mark syntax() as
ABSL_DEPRECATED. syntax() will return a new special value,
Syntax::EDITIONS, intended to explicitly break caller expectations about what
this function returns. Virtually all cases not covered by this proposal are
using syntax() to reject one of the two existing syntaxes, or produce an error
when encountering an unknown Syntax value, so they will continue to work as
expected (i.e. fail on editions protos).

A number of existing tools are hostile to proto2 or proto3. Once the migration
plan is approved, we should reach out to them individually to coordinate either
updating or deprecating their tool.


Design Editions Edition Evolution (`docs/design/editions/edition-evolution.md`)

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 &lt; b.len && a &lt; b. This ensures that 9 &lt; 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:

text
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.


Design Editions Edition Lifetimes (`docs/design/editions/edition-lifetimes.md`)

Edition Lifetimes

Now that Edition Zero is complete, we need to re-evaluate what the lifetimes of
features and editions look like going forward.

Background

The implementation of editions today was based largely on
Protobuf Editions Design: Features and
Life of an Edition (among other less-relevant docs).
Specifically, the latter one takes a strong stance on the lifetimes of both
editions and features. Many of the ideas around editions have since been
simplified in Edition Naming, where we opted for a stricter
naming scheme owned and defined by us. In the process of rolling out editions to
various protoc plugins and planning for edition 2024, it's become clear that we
may need to re-evaluate the feature lifetimes as well.

Editions: Life of a Feature (not available externally) is an alternate vision
to Life of an Edition, which tries to put tighter constraints on how features
and editions interact. It also predicted many of the problems we face now, and
proposes a possible solution.

Overview

Today, features and editions are largely disconnected from each other. We have a
set of features that govern various behaviors, and they can all be used in any
edition. Each edition is simply a distinct set of defaults for every
feature. Users can override the value of any released feature in any edition.
Each generator and protoc itself all advertise a range of editions they support,
and will reject any protos encountered outside that range.

This system does have the nice consequence that behavior-preserving editions
upgrades can always be performed (with respect to the most current proto
language). It separates editions from breaking changes, and means that we only
need to worry about one versioning scheme (our OSS release).

While this all works fine in Edition 2023 where we only have a single edition,
it poses a number of problems going forward. There are three relevant events in
the lifetime of both editions and features, introduction, deprecation, and
removal. Deprecation is essentially just a soft removal to give users adequate
warning, and the same problems apply to both.

Introducing an Edition

Because every generator plugin (and protoc) advertises its edition support
window, introducing a new Edition is well-handled today. We get to enjoy all the
same benefits we saw rolling out Edition 2023 in every subsequent edition (e.g.
we can make radical language changes gated on edition).

Dropping an Edition

Dropping support for an edition doesn't really mean that much today. We could
do it simply by bumping up the minimum supported edition of a binary in a
breaking release. However, that would have no relation to our feature support,
and at best would allow us to clean up some parser code branching on editions.
This code would always be tied to language changes we made in the introduction
of a new edition, where we could finalize them with the removal of an edition.

Introducing a Feature

Whenever we introduce a new feature, we need to make sure to specify its
defaults for every edition (protoc enforces that every edition has a known
set of defaults). It will also immediately be overridable in every edition. This
means that pre-existing binaries that have declared support for an old edition
may suddenly be presented with protos that override a feature they can't
possibly know about.

We faced this problem when introducing the new string_type feature as part of
New String APIs (not available externally). Our solution at the time was to
create some ad hoc validation that prohibited overriding this feature in Edition
2023 until we were ready to release it. This solution doesn't work in general
though, where in OSS we can have arbitrarily old binaries floating around (and
even in google3 we can easily have up to six-month-old binaries within the build
horizon). These old binaries wouldn't have that validation layer, and would
happily process Edition 2023 files with string_type overrides, despite not
knowing how to properly treat that feature.

Dropping a Feature

On the other end of the spectrum, we need a way to deprecate and then remove
support for features. Given that we expect most features to remain for many
years, we haven't been forced to consider this situation too much. The current
plan of record though, is that we would do this by first marking the feature
field definition deprecated, and then remove it entirely in a breaking release
(and total burndown in google3).

The problem with this plan is that it creates a lot of complexity for users
trying to understand our support guarantees. They'll need to track the lifetime
of every feature they use, and also difficult-to-predict interactions
between different versions of protoc and its plugins. If we drop a global
feature in protoc, some plugins may still expect to see that feature and become
broken, while others may not care and still work.

Recommendation

Feature Lifetimes

We recommend adding four new field options to be used in feature specifications:
edition_introduced, edition_deprecated, deprecation_warning, and
edition_removed. This will allow every feature to specify the edition it was
introduced in, the edition it became deprecated in, when we expect to remove it
(deprecation warnings), and the edition it actually becomes removed in.

We will also add a new special edition EDITION_LEGACY, to act as a placeholder
for "infinite past". For editions earlier than edition_introduced, the default
assigned to EDITION_LEGACY will be assigned and should always signal the noop
behavior that predated the feature
. Proto files will not be allowed to override
this feature without upgrading to a newer edition. Deprecated features can get
special treatment beyond the regular deprecated option, and a custom warning
signaling that they should be migrated off of. For editions later than
edition_removed, the last edition default will continue to stay in place, but
overrides will be disallowed in proto files.

For example, a hypothetical feature might look like:

text
optional FeatureType do_something = 2 [
    retention = RETENTION_RUNTIME,
    targets = TARGET_TYPE_FIELD,
    targets = TARGET_TYPE_FILE,
    feature_support {
      edition_introduced = EDITION_2023,
      edition_deprecated = EDITION_2025,
      deprecation_warning = "Feature do_something will be removed in edition 2027",
      edition_removed = EDITION_2027,
    }
    edition_defaults = { edition: EDITION_LEGACY, value: "LEGACY" }
    edition_defaults = { edition: EDITION_2023, value: "INTERMEDIATE" }
    edition_defaults = { edition: EDITION_2024, value: "FUTURE" }
];

Before edition 2023, this feature would always get a default of LEGACY, and
proto files would be prohibited from overriding it. In edition 2023, the default
would change to INTERMEDIATE and users could override it to the old default or
the future behavior. In edition 2024 the default would change again to FUTURE,
and in edition 2025 any overrides of that would start emitting warnings. In
edition 2027 we would prohibit overriding this feature, and the behavior would
always be FUTURE.

Edition Lifetimes

By tying feature lifetimes to specific editions, it gives editions a lot more
meaning. We will still limit this to breaking releases, but it means that
all of the editions-related breaking changes come from this process. When we
drop an edition, breaking changes will always come from the removal of
previously deprecated features. By regularly dropping support for editions, we
will be able to gradually clean up our codebase.

Edition Upgrades

A consequence of this design is that edition upgrades could now become
potentially breaking. Any proto files using deprecated features could be broken
by bumping its edition to one where the feature has been removed. Within
google3, we would need to completely burn down all deprecated uses before we can
remove the feature.

This is not a substantial change on our end from the existing situation though,
where we'd still need to remove all uses before removing it. The key difference
is that we have the option to allowlist some people to stay on an older
edition while still moving the rest of google3 forward. We would also be able to
continue testing removed features by allow-listing dedicated tests to stay on
old editions.

Garbage Collection

Another consequence of this is that we can't actually clean up feature-related
code until every edition before its edition_removed declaration has been
dropped. This ties feature support directly to edition support, especially in
OSS where we can't forcibly upgrade protos to the latest edition.

Predictability

The main win with this strategy is that it clarifies our guarantees and makes
our library more predictable. We can guarantee that a proto file at a specific
edition will not see any behavioral changes unless we:

  1. Make a breaking change outside the editions framework.
  2. Drop the edition the proto file uses.

We can also guarantee that as long as users stay away from deprecated features,
they will still be able to upgrade to the next edition without any changes.

Implementation

Fortunately, this design would be very easy to implement right now. We
simply need to add the new field options and the new placeholder edition, and
then implement new validation in protoc. Because the two error conditions (using
a feature outside its existence window) and the warning (using a deprecated
feature) only trigger on overridden features, protoc already has all the
information it needs. Generator feature extensions must be imported to be
overridden, so the problem of protoc not knowing feature defaults doesn't come
into play at all.

If we wait until edition 2024 has been released, the situation would be a bit
more difficult to unravel. Any new features added in 2024 would be usable from
2023, so we'd have to either intentionally backport support or remove all of
those uses before enabling the validation layer. Therefore, the recommendation
is to implement this ASAP, before we start rolling out 2024.

Runtimes with Dynamic Messages

None of the generators where editions have already been rolled out require any
changes. We likely will want to add validation layers to runtimes that support
dynamic messages though, to make sure there are no invalid descriptors floating
around. Since they all have access to protoc's compiled defaults IR, we can pack
as much information in there as possible to minimize duplication. Specifically,
we will add two new FeatureSet fields to FeatureSetEditionDefault in
addition to the existing features field.

  • overridable_features - The default values that users are allowed to
    override in a given edition
  • fixed_features - The default values that users are not allowed to
    override in a given edition

We will keep the existing features field as a migration tool, to avoid
breaking plugins and runtimes that already use it to calculate defaults. We can
strip it from OSS prior to the 27.0 release though, and remove it once everyone
has been migrated.

In order to calculate the full defaults of any edition, each language will
simply need to merge the two FeatureSet objects. The advantage to splitting
them means that we can fairly easily implement validation checks in every
language that needs it for dynamic messages. The algorithm is as follows, for
some incoming unresolved FeatureSet user_features:

  1. Strip all unknown fields from user_features
  2. Strip all extensions from user_features that the runtime doesn't handle
  3. merged_features := user_features.Merge(overridable_defaults)
  4. assert merged_features == overridable_defaults

This will work as long as every feature is a scalar value (making merge a simple
override). We already ban oneof and repeated features, and we plan to ban
message features before the OSS release.

Note, that there is a slight gap here in that we perform no validation for
features owned by other languages. Dynamic messages in language A will naively
be allowed to specify whatever language B features they want. This isn't
optimal, but it is in line with our current situation where validation of
dynamic messages is substantially more permissive than descriptors processed by
protoc.

On the other hand, owners of language A will have the option of easily adding
validation for language B's features, without having to reimplement the
reflective inspection of imports that protoc does. This can be done by simply
adding those features to the compilation of the defaults IR, and then not
stripping those extensions during validation. This will have the effect of tying
the edition support window of A to that of B though, and A won't be able to
extend its maximum edition until B does (at least for dynamic messages). For
generators in a monorepo like Protobuf's this seems fine, but may not be
desirable elsewhere.

Patching Old Editions

In Edition Naming we decided to drop the idea of "patch"
editions, because editions were always forward and backward compatible. We would
only ever need multiple editions in a year if somehow we managed to speed up the
rollout process and wanted faster turnaround. This changes those assumptions
though, since now editions are neither forward-compatible (new features don't
work in old editions) or backward-compatible (old features may not work in new
editions).

Hypothetically, if there were a bug in the editions layer itself we may require
a "patch" edition to safely roll out a fix. For example, imagine we discover
that our calculation of edition defaults is broken in edition 2023 and we had
accidentally released it. If we've already fixed the issue and released edition
2024 as well, we can't just create a 2023A "patch" to fix the issue because
editions are represented as integers (and 2023 and 2024 are adjacent). We would
want to release some kind of fix for people still on edition 2023 though, so
that they can minimally upgrade before 2024 (which may be a breaking edition).

What we could do in this situation (if it ever arises) is introduce a new
integer field in FileDescriptorProto called edition_patch. It would take
some work to fit this into feature resolution and roll it out to every plugin,
but given that we've hidden the edition from most users
(Editions Feature Visibility) it shouldn't be
too bad. As long as patches never introduce or remove features or change their
defaults, protoc and plugins can always use the latest patch they know about to
represent that edition.

Documentation

As part of this change, we need to document all of this publicly for
plugin/runtime owners. We should create a new topic in
https://protobuf.dev/editions/ to cover all of this, along with other relevant
details they'd need to know.

Alternatives

Continue as usual

The only real alternative here is to make no change, which has all of the
problems listed in the overview of this topic.

Pros

  • Requires no effort short-term
  • Editions upgrades will never be breaking changes

Cons

  • Likely to cause problems as soon as edition 2024
  • Introducing new features is dangerous and unpredictable
  • Dropping features affects all editions simultaneously
  • The features supported in each edition can vary between protobuf releases
  • High cognitive overhead for our users. They'd need to track the progress of
    every feature individually across releases.

Full Validation for Dynamic Messages

None of the generators where editions have already been rolled out require any
changes. We will need to add validation layers to runtimes that support dynamic
messages though, to make sure there are no invalid descriptors floating around.
Any runtime that supports dynamic messages should have reflection, and the same
reflection-based algorithm will need to be duplicated everywhere. For each
FeatureSet specified on a descriptor:

text
/* Detailed source-code truncated for AI context efficiency. */

Pros

  • Prevents any feature lifetime violations for any language, in any language
  • Easier to understand
  • Less error-prone
  • Easy to test with fake features

Cons

  • Only works post-build, which requires a huge amount of code in every
    language to walk the descriptor tree applying these checks
  • Performance concerns, especially in upb
  • Duplicates protoc validation, even though most languages perform
    significantly looser checks on dynamic messages

Pros

  • Minimizes the amount of reflection needed

Cons

  • Can't validate extensions for languages we don't know about, since they're
    not built into the binary
  • Potential version skew between pool and runtime features
  • Requires reflection stripping unexpected fields
  • Difficult to understand the algorithm from the code

Design Editions Edition Naming (`docs/design/editions/edition-naming.md`)

Edition Naming

Author: @mkruskal-google

Approved: 2023-08-25

Background

One of the things Life of an Edition lays out is a very
loose scheme for naming editions. It defines an ordering rule and "." delimiter,
but otherwise imposes no constraints on the edition name. By convention, we
decided to use the year followed by an optional revision number.

One of the decisions in
Editions: Life of a FeatureSet was that
feature resolution would, at least partially, need to be duplicated across every
supported language.

Problem Description

As discussed in Life of a FeatureSet, the minimal operations we need to
duplicate are edition comparison and proto merging. While edition comparison
isn't very complicated today, it has a lot of edge cases we may miss due to
the loose constraints on edition names. It would also be really nice if we could
reduce it down to a lexicographical string comparison, which can be easily done
in any language.

There's also a very real Hyrum's law risk when we permit an infinite number of
edition names that we never expect to exist in practice. For example, 2023.a
is currently valid, and its relative ordering to 2023.10 (for example) is not
obvious. Notably, our initial editions tests used an edition very-cool, which
doesn't seem like something we need to support. Ideally, our edition names would
be as simple as possible, with enforceable and documented constraints.

There has been a lot of confusion when looking at the interaction between
editions and releases. Specifically, the fact that editions look like calver
numbers has led to us calling revisions "patch editions", which suggests that
they're bug fixes to an earlier one. This was not the original intention though,
which to summarize was:

  • Editions are strictly time-ordered. Revisions were simply a mechanism to
    release more than one edition per calendar year, but new editions can't be
    inserted into earlier slots.

  • New editions can be added at any time. As long as they're ordered
    after the pre-existing ones, this is a non-breaking change and can be done
    in a patch release.

  • New features can be added at any time without changing the edition.
    Since they're a non-breaking change by definition, this can be done in a
    patch release.

  • Features can only be deleted in a breaking release. The editions model
    doesn't support the deletion of features, which is always a breaking change.
    This will only be done in a major version bump of protobuf.

  • Feature defaults can only be changed in a new edition. Once the default
    is chosen for a feature, we can only change it by releasing a new edition
    with an updated default. This is a non-breaking change though, and can be
    done in patch releases.

We want the following properties out of editions:

  • Discrete allowable values controlled by us
  • Easily comparable
  • Cross-language support
  • Relatively small set (<100 for the next century)
  • Slow growth (~once per year)

The simplest solution here is to just make an Edition enum for specifying the
edition. We will continue to use strings in proto files, but the parser will
quickly convert them and all code from then on will treat them as enums. This
way, we will have a centralized cross-language list of all valid editions:

text
enum Edition {
  EDITION_UNKNOWN = 0;
  EDITION_2023 = 1;
  EDITION_2024 = 2;
  // ...
}

We will document that these are intended to be comparable numerically for
finding the time ordering of editions. Proto files will specify the edition in
exactly the same way as in the original decision:

text
edition = "2023";

Ideally, this would be an open enum to avoid ever having the edition thrown into
the unknown field set. However, since it needs to exist in descriptor.proto,
we won't be able to make it open until the end of our edition zero migration.
Until then, we can take advantage of the fact that syntax gets set to
editions by the parser when an edition is found. In this case, an unset
edition should be treated as an error. Once we migrate to an open enum we can
replace this with a simpler validity check.

Additionally, we've decided to punt on the question of revisions and just not
allow them for now. If we ever need more than that it means we've made a huge
mistake, and we can bikeshed on naming at that time (e.g. EDITION_2023_OOPS).
We will plan to have exactly one edition every year.

Pros

  • Edition comparison becomes even simpler, as it's just an integer
    comparison

    • Feature resolution becomes trivial in every language
  • Automatic rejection of unknown editions.

    • This includes both future and dropped editions, and also unknown
      revisions
    • Other solutions would need custom logic in protoc to enforce these
  • Doesn't look like calver, avoiding that confusion

  • Lack of revisions simplifies our documentation and makes editions easier to
    understand/maintain

Cons

  • Might prove to be a challenge when we go to migrate descriptor.proto to
    editions

  • Might be a bit tricky to implement in the parser (but Prototiller does this
    just fine)

Considered Alternatives

Edition Enums

Instead of using a string, editions could be represented as an enum. This would
give us a lot of enforcement for free and be maximally constraining. For
example:

text
enum Edition {
  E2023  = 1;
  E2023A = 2;
  E2024  = 3;
}

Pros

  • Edition comparison becomes even simpler, as it's just an integer
    comparison

  • Arbitrary number of revisions within a year

  • Automatic rejection of unknown editions (with closed enums).

    • This includes both future and dropped editions, and also unknown
      revisions
    • Other solutions would need custom logic in protoc to enforce these
  • Doesn't look like calver, avoiding that confusion

Cons

  • Edition specification in every proto file becomes arguably less readable.
    edition = "2023.1" is easier to read than edition = E2023A.

  • Might require parser changes to get descriptor.proto onto editions

  • This is a pretty big change and will require documentation/comms updates

  • Plugin owners can't pre-release upcoming features

    • Do we even want to allow this? It could help coordinating editions
      releases

Neutral

  • Edition must be strictly time-ordered. We can't go back and add a revision
    to an older revision, but the original plan didn't really allow for this
    anyway.

  • Edition ordering is not directly linked to the name at all. We'd have to
    write a reflection test that enforces that they're strictly increasing.

Truncated Revisions

The simplest solution that's consistent with the original plan would be to limit
ourselves to no more than 9 intermediate editions per year. This means editions
would either be a simple year (e.g. 2023), or have a single digit revision
number (e.g. 2024.3). The revision number would be constrained to (0,9] and
the year would have to be an integer >=2023.

With these constraints in place, edition ordering becomes a simple
lexicographical string ordering.

Pros

  • Tightly constrains edition names and avoids unexpected edge cases
  • Edition comparison code is easily duplicated (basic string comparison)
  • Removes ambiguity around revision 0 (e.g. 2023.0 isn't allowed)

Cons

  • Limits us to no more than 10 editions per year. This seems reasonable
    today, and since protoc is the one enforcing this we can always revisit it
    later. As long as the ordering stays lexicographical we can expand it
    arbitrarily.

Fixed length editions

This is similar to the recommended solution, but instead of allowing year
editions we would always start with a .0 release. This means that Edition Zero
would become 2023.0. This has the same pros/cons as the solution above, with
the addition of:

Pros

  • Makes revisions less jarring. Users won't be confused by 2023.1 if they're
    used to 2023.0.

Cons

  • We've already published comms and documentation calling the first edition
    2023. We'd have to go update those and communicate the change.
  • We have no real use-case for revisions yet, and this adds complexity to the
    "typical" case.

Edition Messages

Instead of using a string, editions could be represented as a message. This
would allow us to model the constraints better. For example:

text
message Edition {
  uint32 year = 1;
  uint32 revision = 2;
}

Pros

  • The schema itself would automatically enforce a lot of our constraints
  • Arbitrary number of revisions would be allowed
  • Can't accidentally use string comparison

Cons

  • Custom comparison code would still be needed in every language (marginally
    more expensive than string comparison)
  • We would either need to convert the edition string into this message during
    parsing or radically change the edition specification

Do Nothing

Pros

  • Easier today, since we don't have to do anything

Cons

  • Harder tomorrow when we have to duplicate this comparison code into every
    language
  • Leaves us open to Hyrum's law and unexpected abuse

Design Editions Edition Zero Converged Semantics (`docs/design/editions/edition-zero-converged-semantics.md`)

Edition Zero: Converged Semantics

Authors: @perezd,
@haberman

Approved: 2021-10-07

Background

The Protobuf Team has been exploring potential facilities for introducing
breaking API and semantic changes. This document is an attempt to make use of
these facilities to unify proto semantics from this point forward, while giving
customers the ability to more-granularly manage their project's specific needs.

Objective

We want to reduce complications of API semantics that are coarsely managed
through the syntax keyword, and instead default to converged proto2/proto3 when
opting into editions. Where needed, customers will be able to opt out of
specific semantics that are incompatible with their existing usages, at a
fine-grained level, using the new capabilities provided by editions + features.

Why Now?

As we introduce new facilities for managing breaking changes, we have an
additional opportunity to cutover and rectify a long-standing vision of
converging proto 2/3 semantics as a natural extension of this.

Doing this in lockstep with the introduction of editions provides the protobuf
team with a few valuable outcomes:

  • Editions provides us with more granular specification of intent than the
    existing coarse knob of "proto2" or "proto3." By opting into our first
    edition, customers are upgrading to what we've referred to in the past as
    "converged semantics," and if needed can reversibly downgrade back to proto2
    or proto3 semantics respectively by opt-ing out of the specific features
    that are incompatible with their existing needs.

  • The protobuf team can avoid the n^2 complexity of considering how an
    edition/feature will interplay with an explicit syntax designation of
    "proto2" vs "proto3" for all impacted runtimes. This allows us to transition
    our thinking/support model to be explicitly feature-centric.

  • The introduction of editions will almost certainly cause a major version
    bump and gives us ample justification to make breaking changes as we
    transition to this granular specification.

Introduction of the `edition` keyword to proto IDL

The edition keyword is used to define which semantic version a particular file
and all of its contents will adhere to as a baseline. Whenever a proto file
declares an edition keyword, it automatically defaults to converged proto2/3
semantics.

An edition's value is represented as a string, encoded by convention as a year.

Introduction of `features` option to `descriptor.proto`

This option will be uniformly defined as a repeated set of strings which can be
used to encode the ability to opt-out of a specific feature (eg:
"-string_view"), or to potentially opt-in to a future/experimental feature
(eg: "string_view"). The features option will be added to descriptor.proto
for the following descriptor options:

  • File
  • Message
  • Field
  • Enum
  • Enum Value
  • Oneof
  • Service
  • Method
  • Stream (internal repositories only)

Features are only respected when used in conjunction with the edition keyword.
They are not validated for correctness to ensure they are forward/backward
compatible with releases.

Features may be declared at any descriptor level, however, a feature definition
may influence descendant types at the discretion of the protobuf team. (e.g., a
file-level feature opt-out could impact all fields within the file, if it was
desired).

A taxonomy of features

Features can be broken down into two main categories: language-specific and
semantic.

Language-specific features

Language-specific features pertain to the generated API for a given language.
Referring to the protobuf breaking changes backlog we can see some examples:

  • (C++) Changing string fields to return string_view.
  • (Java) Removing the confusing Enum#valueOf(int) API.
  • (Java) Rename oneof enums to do appropriate camel casing.

Language-specific features have no meaning for any other language: they can be
ignored entirely. They are, in essence, a private (tunneled) interface between
protobuf IDL and the respective code generator. Each language's code generator
can independently decide what the "base" set of features is for any given
edition. Each language defines the migration path between editions
independently.

Semantic Features

Semantic features define behavior changes that apply to the protobuf data model,
independent of language. These can also have API implications, but their meaning
goes deeper than just a surface-level API. Some examples of semantic features:

  • Open enums (enums are placed directly into the field instead of the
    UnknownFieldSet).
  • Packed (whether repeated fields are packed on the wire)

Semantic features have significantly broader scope, since they must be respected
across languages, and each language must implement the semantic correctly. This
also implies that every language must either (1) know the canonical set of "base
features" for each edition, or (2) that the set of "default" features for the
edition must be resolved in protoc itself and propagated explicitly into the
descriptor.

Rev'ing the protobuf IDL vs. descriptor.proto

Changing descriptor.proto to reflect editions is a much more intrusive change
than changing just the protobuf IDL. The protobuf IDL is parsed and resolved in
protoc, and we have only a single implementation of that parser. Any change that
can be resolved in the parser alone is relatively unintrusive (though there are
build horizon issues since GCL parses protos in prod).

Rev'ing descriptor.proto is a far more intrusive change that affects many
downstream systems. Many systems access descriptors through either a descriptor
API (for example, google::protobuf::Descriptor in C++) or by directly accessing a proto
from descriptor.proto (eg. google.protobuf.DescriptorProto). Any changes
here need to be managed much more delicately.

Deprecation of the `syntax` keyword from proto IDL

The syntax keyword shall no longer be required/observed when an edition
keyword is present, as it is now considered redundant. If edition and syntax
are both present, edition takes precedence and syntax is ignored.

Migrating from `proto2` and `proto3` to Editions + Features

Today's usage of syntax opaquely bundles a collection of implied feature flags
that are set based on the presence of proto2 or proto3. This is often a
source of confusion for customers (eg: what am I gaining by moving to proto3?
What am I losing?).

By deciding that editions/features exist in a state of proto2/3 convergence,
this enables customers to decide for themselves what features are important to
their usage of protos.

Migrating existing users of proto2 and proto3 to editions w/converged semantics
would mean we'd need to execute a large-scale change to make their
implicit/implied behavior explicit. Here are examples of implied behavior.
today:

Feature proto2 implied behavior proto3 implied behavior
packed_repeated_primitives 🚫 ✅
extensions ✅ 🚫
required ✅ 🚫
groups ✅ 🚫
cpp_string_view 🚫 🚫
java_enum_no_value_of 🚫 🚫
open_enums 🚫 ✅
MORE STUFF ...

Managing Complexity of `features` for Large Deployments

A separate concept has been established to help mitigate the complexity of
editions and progressive feature rollouts and synchronizations for larger proto
projects.

This facility could be used to migrate existing usages of the syntax keyword
to use Editions + Features across google3, for example.

Prior Work


Design Editions Edition Zero Feature Enum Field Closedness (`docs/design/editions/edition-zero-feature-enum-field-closedness.md`)

Edition Zero Feature: Enum Field Closedness

Author: @mcy

Approved: 2023-02-13

Background

On 2023-02-10, a CL @mcy submitted to delete
google::protobuf::Reflection::SupportsUnknownEnumValue(). Oddly, this function used the
containing message's syntax, rather than the enum field's, to determine
whether the enum was open.

It turns out we misunderstood a critical corner-case of proto3 enums. Consider
the following proto files:

text
// enum.proto
syntax = "proto3";
package oh.no;

enum Enum {
  A = 0;
  B = 1;
}

// message.proto
syntax = "proto2";
package oh.no;
import "enum.proto";

message Msg {
  optional Enum enum = 1;
}

If we parse the Protoscope
value 1: 2 as an oh.no.Msg, and look at the value of oh.no.Msg.enum, we
will find that it is not present, and that there is a VARINT of value 2 in the
UnknownFieldSet.

This is because Protobuf sometimes implements the openness of an enum by its
usage, not its definition.

This case is actually quite difficult to observe, because the converse doesn't
work: the proto compiler rejects proto2-enum-valued fields in proto3 messages,
because such enums can have nonzero defaults, which proto3 does not support due
to implicit presence.

Languages

Language Open/Closed handling
C++ Determined by the field using the enum's file
Java Determined by the field using the enum's file
UPB (non-ruby) Determined by the enum's definition file
UPB (ruby) All enums treated as open
C# All enums treated as open
Obj-C by usage (< 22.x)

by definition (>= 22.x)

It looks like this was handled by the field's usage, but in Nov as part of the syntax cleanup, we stopped looking at syntax and captured things on the enum definition, so it's now defined by the enum. |
| Swift | Determined by the enum's definition file

Swift uses the ability for enums to have associated values, so an enum defined in a proto3 syntax file gets a value that holds all unknown values. So a proto2 syntax defined message will still end up with the enum using that to hold unknown values. |
| Go | All enums treated as open |
| Apps JSPB | All enums treated as open |
| ImmutableJs | All enums treated as open |
| JsProto | All enums treated as open |

Impact

Approximately 2.99% of enum fields import enums across syntaxes and 1.77% of
enums are imported across syntaxes.

6.14% of fields being enum fields, meaning 0.18% of fields are affected when
used by affected languages.

Overview

This document proposes adding an additional feature to
Edition Zero Features, specified as the following
.proto fragment:

text
message Features {
  // ...
  optional bool legacy_treat_enum_as_closed = ??? [
      retention = RUNTIME,
      target = FILE,
      target = FIELD
  ];
}

The name of this field captures the desired intent: this is a bad legacy
behavior that we believe is rare and want to stamp out. Edition 2023 would set
this to false by default, and proto2 would treat it as implicitly true. It
also does not permit the converse: you cannot force a field to be open, because
that is currently not possible and we don't want to add more special cases.

Additionally, we would like to make special dispensation in migration tooling
for this field: it should not be set unconditionally when migrating from proto2
-> editions, but only on proto2 fields that are of proto3 enum type. We should
also want to build an allowlist for this, like we do for required.

This option can also help in migrating enums from closed to open, since we can
use it to migrate individual use-sites by marking the enum as open and all of
its uses as treat-as-closed in one CL, and then deleting the treat-as-closed
annotations one by one.

An open (lol) question is whether we should move is_closed from
EnumDescriptor to FieldDescriptor.

Recommendation

Use the "define official behavior" alternative below. Given the wide variety of
behavior in different languages, a singular global setting will always leave
some of our languages in the lurch. As such, we will use per language features
to allow each language to control its own evolution while we define the
"correct" behavior.

For example, in C++ we will define:

text
// Determines if the given enum field is treated as closed based on legacy
// non-conformant behavior.
//
// Conformant behavior determines closedness based on the enum and
// can be queried using EnumDescriptor::is_closed().
//
// Some runtimes currently have a quirk where non-closed enums are
// treated as closed when used as the type of fields defined in a
// `syntax = proto2;` file. This quirk is not present in all runtimes; as of
// writing, we know that:
//
// - C++, Java, and C++-based Python share this quirk.
// - UPB and UPB-based Python do not.
// - PHP and Ruby treat all enums as open regardless of declaration.
//
// Care should be taken when using this function to respect the target
// runtime's enum handling quirks.

bool FieldDescriptor::legacy_enum_field_treated_as_closed() const {
  return type() == TYPE_ENUM && file().syntax() == FileDescriptor::SYNTAX_PROTO2;
}

In Java, FileDescriptor.supportsUnknownEnumValue() will need to be deprecated
and replaced with the above.

Alternatives

Define official behavior

Define the official behavior to be "Enums open-ness should be defined by the
definition of the enum." Add a conformance test for this behavior. Use per
language features to eventually converge implementations that are out of
conformance. We choose to define this as "enum openness is defined by the
definition" because that matches the model for almost all other proto3/proto2
properties.

Pros

  • Clarifies desired behavior
  • Existing implementations can change incrementally using editions
  • Avoids complicating global features for something that is a per-language
    issue

Cons

  • When migrating from syntax to edition zero, Prototiller will need to know
    all used languages to make the upgrade a trivial change (this is already the
    case for other edition upgrades).

Make `Features.enum` a field-level feature

Here, we don't add legacy_treat_enum_as_closed and instead make closeness a
bona fide property of fields, not enums.

Pros

  • Reflects the current behavior of Protobuf for our largest languages
    (C++/Java).
  • Removes the possibility of making a mistake in reflective code that checks
    is_closed() on EnumDescriptor rather than FieldDescriptor.

Cons

  • Doesn't handle the case for languages other than C++/Java
  • Harder to migrate individual enums to open, since the property is not in
    control of the owner of the type.
  • Conceptually unpleasant, since it gives locality to the meaning of
    IsValid, unless we want to believe that IsValid merely states whether
    the value has a name we know of.

Allow `Features.enum` on both enums and fields

This allows enum owners some more control without needing to introduce a
strictly "legacy do not use" feature.

Pros

  • We don't introduce a "legacy do not use" option, and don't need to play the
    allowlist game.

Cons

  • We need to support closed-enums-treated-as-open, which is a functionality
    Protobuf does not offer today.

Name the feature `Features.treat_as_closed_for_migration`

This is an aesthetic choice if we feel this is a useful knob for migration, that
still highlights its temporary nature.

Pros

  • We don't introduce a "legacy do not use" option, and don't need to play the
    allowlist game.
  • Clearly underscores that this is for migration in a specific desirable
    direction (closed -> open).

Cons

  • People may use it because they like closed enums for some reason and don't
    fully appreciate the ramifications.

Do Nothing

We can simply keep the current editions enum semantics.

Pros

  • No extra work.

Cons

  • proto2 -> editions is not a no-op in some cases. This breaks a lot of the
    draw of moving to editions, even though it is possible to detect the no-ops
    in advance.
  • This would immediately add a blocker to our syntax reflection large-scale
    change

Design Editions Edition Zero Features (`docs/design/editions/edition-zero-features.md`)

Edition Zero Features

Authors: @mcy,
@zhangskz,
@mkruskal-google

Approved: 2022-07-22

Feature flags, and their defaults, that we will introduce to define the
converged semantics of Edition Zero.

NOTE: This document is largely replaced by the topic,
Feature Settings for Editions (to be
released soon).

Overview

Edition Zero Features defines the "first edition" of the brave new world of
no-syntax Protobuf. This document defines the actual mechanics of the features
(in the narrow sense of editions) we need to implement in protoc, as well as the
chosen defaults.

This document will require careful review from various stakeholders, because it
is essentially defining a new Protobuf syntax, even if it isn't spelled that
way. In particular, we need to ensure that there is a way to rewrite existing
proto2 and proto3 files as editions files, and the behavior of "mixed
syntax" messages, without any nasty surprises.

Note that it is an explicit goal that it be possible to take an arbitrary
proto2/proto3 file and convert it to editions without semantic changes, via
appropriate application of features.

Existing Non-Conformance

We must keep in mind that the status quo is messy. Many languages have some
areas where they currently diverge from the correct proto2/proto3 semantics. For
edition zero, we must preserve these idiosyncratic behaviors, because that is
the only way for a proto2/proto3 -> editions LSC to be a no-op.

For example, in this document we define a feature features.enum = {CLOSED,OPEN}. But currently Go does not implement closed enum semantics for
syntax=proto2 as it should. This behavior is out of conformance, but we must
preserve this out-of-conformance behavior for edition zero.

In other words, defining features and their semantics is in scope for edition
zero, but fixing code generators to perfectly match those semantics is
explicitly out-of-scope.

Glossary

Because we need to speak of two proto syntaxes, proto2 and proto3, that have
disagreeing terminology in some places, we'll define the following terms to aid
discussion. When a term appears in code font, it refers to the Protobuf
language keyword.

  • A presence discipline is a handling for the presence (or hasbit) of a
    field. Every field notionally has a hasbit: whether it has been explicitly
    set via the API or whether a record for it was present on deserialization.
    See
    Application Note: Field Presence
    for more on this topic. The discipline specifies how this bit is surfaced to
    the user:
    • No presence means that the API does not expose the hasbit. The
      default value for the field behaves somewhat like a special sentinel
      value, which is not serialized and not merged-from. The hasbit may still
      exist in the implementation (C++ accidentally leaks this via HasField,
      for example). Note that repeated fields sort-of behave like no presence
      fields.
    • Explicit presence means that the API exposes the hasbit through a
      has method and a Clear method; default values are always serialized
      if the hasbit is set.
  • A closed enum is an enum where parsing requires validating that a parsed
    int32 representing a field of this type matches one of the known set of
    valid values.
  • An open enum does not have this restriction, and is just an int32
    field with well-known values.

For the purposes of this document, we will use the syntax described in Features
as Custom Options
, since it is the prevailing consensus among those working on
editions, and allows us to have enum-typed features. The exact names for the
features are a matter of bikeshedding.

Proposed Converged Semantics

There are two kinds of syntax behaviors we need to capture: those that are
turned on by a keyword, like required, and those that are implicit, like open
enums. The differences between proto2 and proto3 today are:

  • Required. Proto2 has required but not defaulted; Proto3 has defaulted
    but not required. Proto3 also does not allow custom defaults on
    defaulted fields, and on message-typed fields, defaulted is a synonym
    for optional.
  • Groups. Proto2 has groups, proto3 does not.
  • Enums. In Proto2, enums are closed: messages that have an enum not in
    the known set are stored in the unknown field set. In Proto3, enums are
    open.
  • String validation. Proto2 is a bit wobbly on whether strings must be UTF-8
    when serialized; Proto3 enforces this (sometimes).
  • Extensions. Proto2 has extensions, while Proto3 does not (Any is the
    canonical workaround).

We propose defining the following features as part of edition zero:

features.field_presence

This feature is enum-typed and controls the presence discipline of a singular
field:

  • EXPLICIT (default) - the field has explicit presence discipline. Any
    explicitly set value will be serialized onto the wire (even if it is the
    same as the default value).
  • IMPLICIT - the field has no presence discipline. The default value is
    not serialized onto the wire (even if it is explicitly set).
  • LEGACY_REQUIRED - the field is wire-required and API-optional. Setting
    this will require being in the required allowlist. Any explicitly set
    value will be serialized onto the wire (even if it is the same as the
    default value).

The syntax for singular fields is a much debated question. After discussing the
tradeoffs, we have chosen to eliminate both the optional and required
keywords, making them parse errors
. Singular fields are spelled as in proto3
(no label), and will take on the presence discipline given by
features.:presence. Migration will require deleting every instance of
optional in proto files in google3, of which there are 385,236.

It is important to observe that proto2 users are much likelier to care about
presence than proto3 users, since the design of proto3 discourages thinking
about presence as an interesting feature of protos, so arguably introducing
proto2-style presence will not register on most users' mental radars. This is
difficult to prove concretely.

IMPLICIT fields behave much like proto3 implicit fields: they cannot have
custom defaults and are ignored on submessage fields. Also, if it is an
enum-typed field, that enum must be open (i.e., it is either defined in a
syntax = proto3; file or it specifies option features.enum = OPEN;
transitively).

We also make some semantic changes:

  • IMPLICIT``fields may have a custom default value, unlike inproto3. Whether anIMPLICIT` field containing its default value is serialized
    becomes an implementation choice (implementations are encouraged to try to
    avoid serializing too much, though).
  • has_optional_keyword() and has_presence() now check for EXPLICIT, and
    are effectively synonyms.
  • proto3_optional is rejected as a parse error (use the feature instead).

Migrating from proto2/3 involves deleting all optional/required labels and
adding IMPLICIT and LEGACY_REQUIURED annotations where necessary.

Alternatives

  • For syntax:
    • Require optional. This may confuse proto3 users who are used to
      optional not being a default they reach for. Will result in
      significant (trivial, but noisy) churn in proto3 files. The keyword is
      effectively line noise, since it does not indicate anything other than
      "this is a singular field".
    • Invent a new label, like singular. This results in more churn but
      avoids breaking peoples' priors.
    • Allow optional and no label to coexist in a file, which take on their
      original meanings unless overridden by features.field_presence. The
      fact that a top-level features.field_presence = IMPLICIT breaks the
      proto3 expectation that optional means EXPLICIT may be a source of
      confusion.
  • proto:allow_required, which must be present for required to not be a
    syntax error.
  • Allow required/optional and introduce defaulted as a real keyword. We
    will not have another easy chance to introduce such syntax (which we do,
    because edition = ... is a breaking change).
  • Reject custom defaults for IMPLICIT fields. This is technically not really
    needed for converged semantics, but trying to remove the Proto3-ness from
    IMPLICIT fields seems useful for consistency.

Future Work

In the future, we can introduce something like features.always_serialize or a
similar new enumerator (ALWAYS_SERIALIZE) to the when_missing enum, which
makes EXPLICIT_PRESENCE fields unconditionally serialized, allowing
LEGACY_REQUIRED fields to become EXPLICIT_PRESENCE in a future large-scale
change. The details of such a migration are out-of-scope for this document.

Migration Examples

Given the following files:

text
// foo.proto
syntax = "proto2"

message Foo {
  required int32 x = 1;
  optional int32 y = 2;
  repeated int32 z = 3;
}

// bar.proto
syntax = "proto3"

message Bar {
  int32 x = 1;
  optional int32 y = 2;
  repeated int32 z = 3;
}

post-editions, they will look like this:

text
// foo.proto
edition = "tbd"

message Foo {
  int32 x = 1 [features.field_presence = LEGACY_REQUIRED];
  int32 y = 2;
  repeated int32 z = 3;
}

// bar.proto
edition = "tbd"
option features.field_presence = NO_PRESENCE;

message Bar {
  int32 x = 1;
  int32 y = 2 [features.field_presence = EXPLICIT_PRESENCE];
  repeated int32 z = 3;
}

features.enum_type

Enum types come in two distinct flavors: closed and open.

  • closed enums will store enum values that are out of range in the unknown
    field set.

  • open enums will parse out of range values into their fields directly.

    NOTE: Closed enums can cause confusion for parallel arrays (two repeated
    fields that expect to have index i refer to the same logical concept in both
    fields) because an unknown enum value from a parallel array will be placed
    in the unknown field set and the arrays will cease being parallel. Similarly
    parsing and serializing can change the order of a repeated closed enum by
    moving unknown values to the end.

    NOTE: Some runtimes (C++ and Java, in particular) currently do not use
    the declaration site of enums to determine whether an enum field is treated
    as open; rather, they use the syntax of the message the field is defined in,
    instead. To preserve this proto2 quirk until we can migrate users off of it,
    Java and C++ (and runtimes with the same quirk) will use the value of
    features.enum as set at the file level of messages (so, if a file sets
    features.enum = CLOSED at the file level, enum fields defined in it behave
    as if the enum was closed, regardless of declaration). IMPLICIT singular
    fields in Java and C++ ignore this and are always treated as open, because
    they used to only be possible to define in proto3 files, which can't use
    proto2 enums.

In proto2, enum values are closed and no requirements are placed upon the
first enum value. The first enum value will be used as the default value.

In proto3, enum values are open and the first enum value must be zero. The
first enum value is used as the default value, but that value is required to
be zero.

In edition zero, We will add a feature features.enum_type = {CLOSED,OPEN}. The
default will be OPEN. Upgraded proto2 files will explicitly set
features.enum_type = CLOSED. The requirement of having the first enum value be
zero will be dropped.

NOTE: Nominally this exposes a new state in the configuration space, OPEN
enums with a non-zero default. We decided that excluding this option simply
because it was previously inexpressible was a false economy.

Alternatives

  • We could add a property for requiring a zero first value for an enum. This
    feels needlessly complicated.
  • We could drop the ability to have CLOSED enums, but that is a semantic
    change.

Migration Examples

Given the following files:

text
// foo.proto
syntax = "proto2"

enum Foo {
  A = 2, B = 4, C = 6,
}

// bar.proto
syntax = "proto3"

enum Bar {
  A = 0, B = 1, C = 5,
}

post-editions, they will look like this:

text
// foo.proto
edition = "tbd"
option features.enum_type = CLOSED;

enum Foo {
  A = 2, B = 4, C = 6,
}

// bar.proto
edition = "tbd"

enum Bar {
  A = 0, B = 1, C = 5,
}

If we wanted to merge them into one file, it would look like this:

text
// foo.proto
edition = "tbd"

enum Foo {
  option features.enum_type = CLOSED;
  A = 2, B = 4, C = 6,
}


enum Bar {
  A = 0, B = 1, C = 5,
}

features.repeated_field_encoding

In proto3, the repeated_field_encoding attribute defaults to PACKED. In
proto2, the repeated_field_encoding attribute defaults to EXPANDED. Users
explicitly enabled packed fields 12.3k times, but only explicitly disable it 200
times. Thus we can see a clear preference for repeated_field_encoding = PACKED
emerge. This data matches best practices. As such, the default value for
features.repeated_field_encoding will be PACKED.

The existing [packed = …] syntax will be made an alias for setting the feature
in edition zero. This alias will eventually be removed. Whether that removal
happens during the initial large-scale change to enable edition zero or as a
follow on will be decided at the time.

In the long term, we would like to remove explicit usages of
features.repeated_field_encoding = EXPANDED, but we would prefer to separate
that large-scale change from the landing of edition zero. So we will explicitly
set features.repeated_field_encoding to EXPANDED at the file level when we
migrate proto2 files to edition zero.

Alternatives

  • Force everyone to use packed fields. This is a semantic change, which we're
    trying to avoid in edition zero.
  • Don’t add features.repeated_field_encoding and instead specify [packed = false] when converting from proto2. This will be incredibly noisy,
    syntax-wise and diff-wise.

Migration Examples

Given the following files:

text
// foo.proto
syntax = "proto2"

message Foo {
  repeated int32 x = 1;
  repeated int32 y = 2 [packed = true];
  repeated int32 z = 3;
}

// bar.proto
syntax = "proto3"

message Foo {
  repeated int32 x = 1;
  repeated int32 y = 2 [packed = false];
  repeated int32 z = 3;
}

post-editions, they will look like this:

text
// foo.proto
edition = "tbd"
options features.repeated_field_encoding = EXPANDED;

message Foo {
  repeated int32 x = 1;
  repeated int32 y = 2 [packed = true];
  repeated int32 z = 3;
}


// bar.proto
edition = "tbd"

message Foo {
  repeated int32 x = 1;
  repeated int32 y = 2 [packed = false];
  repeated int32 z = 3;
}

Note that post migration, we have not changed packed to
features.repeated_field_encoding = PACKED, although we could choose to do so
if the diff cost is not monumental. We prefer to defer to an LSC after editions
are shipped, if possible.

features.string_field_validation

WARNING: UTF8 validation is actually messier than originally believed. This
feature is being reconsidered in Editions Zero Feature: utf8_validation.

This feature is a tristate:

  • MANDATORY - this means that a runtime MUST verify UTF-8.
  • HINT - this means that a runtime may refuse to parse invalid UTF-8, but it
    can also simply skip the check for performance in some build modes.
  • NONE - this field behaves like a bytes field on the wire, but parsers
    may mangle the string in an unspecified way (for example, Java may insert
    spaces as replacement characters).

The default will be MANDATORY.

Long term, we would like to remove this feature and make all string fields
MANDATORY.

Alternatives

  • Drop the UTF-8 requirements completely. This seems like it will create more
    problems than it will solve (e.g., random things relying on validation need
    to be fixed) and it will be a lot of work. This is also counter to the
    vision of string being a UTF-8 type, and bytes being its unchecked sibling.
  • Make opt-in verification a hard requirement instead of a hint, so that users
    have a nice performance needle they can play with.

Future Work

In the infinite future, we would like to remove this feature and force all
string fields to be UTF-8 validated. To do this, we need to recognize that
what many callers want from their string fields is a bytes field with a
string-like API. To ease the transition, we would add per-codegen backend
features, like java.bytes_as_string, that give a bytes field a generated API
resembling that of a string field (with caveats about replacement characters
forced by the host language's string type).

The migration would take HINT or SKIP string fields and convert them into
bytes fields with the appropriate API modifiers, depending on which languages
use that proto; C++-only protos, for example, are a no-op.

There is an argument to be made for "I want a string type, and I explicitly want
replacement U+FFFD characters if I get something that isn't UTF-8." It is
unclear if this is something users want and we would need to investigate it
before making a decision.

features.json_format

This feature is dual state in edition zero:

  • ALLOW - this means that a runtime must allow JSON parsing and
    serialization. Checks will be applied at the proto level to make sure that
    there is a well-defined mapping to JSON.
  • LEGACY_BEST_EFFORT - this means that a runtime will do the best it can to
    parse and serialize JSON. Certain protos will be allowed that can result in
    undefined behavior at runtime (e.g. many:1 or 1:many mappings).

The default will be ALLOW, which maps the to the current proto3 behavior.
LEGACY_BEST_EFFORT will be used for proto2 files that require it (e.g. they’ve
set deprecated_legacy_json_field_conflicts)

Alternatives

  • Keep the proto2 behavior - this will regress proto3 files by removing
    validation for JSON mappings, and lead to more undefined runtime behavior
  • Only use ALLOW - there are ~30 cases internally where protos have invalid
    JSON mappings and rely on unspecified (but luckily well defined) runtime
    behavior.

Future Work

Long term, we would like to either remove this feature entirely or add a
DISALLOW option instead of LEGACY_BEST_EFFORT. This will more strictly
enforce that protos without a valid JSON mapping can’t be serialized or parsed
to JSON. DISALLOW will be enforced at the proto-language level, where no
message marked ALLOW can contain any message/enum marked DISALLOW (e.g.
through extensions or fields)

Migration Examples

Extensions are Always Allowed

Extensions may be used on all messages. This lifts a restriction from proto3.

Extensions do not play nicely with TypeResolver. This is actually fixable, but
probably only worth it if someone complains.

Alternatives

  • Add features.allow_extensions, default true. This feels unnecessary since
    uttering extend and extensions is required to use extensions in the
    first place.

features.message_encoding

This feature defaults to LENGTH_PREFIXED. The group syntax does not exist
under editions. Instead, message-typed fields that have
features.message_encoding = DELIMITED set will be encoded as groups (wire type
3/4) rather than byte blobs (wire type 2). This reflects the existing API
(groups are funny message fields) and simplifies the parser.

A proto2 group field will be converted into a nested message type of the same
name, and a singular submessage field that is features.message_encoding = DELIMITED with the message type's name in snake_case.

This could be used in the future to switch new message fields to use group
encoding, which suggested previously as an efficiency direction.

Alternatives

  • Allow groups in editions with no changes. group syntax is deprecated, so
    we may as well take the opportunity to knock it out.
  • Add a sidecar allowlist like we do for required. This is mostly
    orthogonal.

Migration Examples

Given the following file

text
// foo.proto
syntax = "proto2"

message Foo {
  group Bar = 1 {
    optional int32 x = 1;
    repeated int32 y = 2;
  }
}

post-editions, it will look like this:

text
// foo.proto
edition = "tbd"

message Foo {
  message Bar {
    optional int32 x = 1;
    repeated int32 y = 2;
  }
  Bar bar = 1 [features.message_encoding = DELIMITED];
}

Proposed Features Message

Putting together all of the above, we propose the following Features message,
including retention and target rules associated with fields.

text
message Features {
  enum FieldPresence {
    EXPLICIT = 0;
    IMPLICIT = 1;
    LEGACY_REQUIRED = 2;
  }
  optional FieldPresence field_presence = 1 [
      retention = RUNTIME,
      target = FILE,
      target = FIELD
  ];

  enum EnumType {
    OPEN = 0;
    CLOSED = 1;
  }
  optional EnumType enum = 2 [
      retention = RUNTIME,
      target = FILE,
      target = ENUM
  ];

  enum RepeatedFieldEncoding {
    PACKED = 0;
    UNPACKED = 1;
  }
  optional RepeatedFieldEncoding repeated_field_encoding = 3 [
      retention = RUNTIME,
      target = FILE,
      target = FIELD
  ];

  enum StringFieldValidation {
    MANDATORY = 0;
    HINT = 1;
    NONE = 2;
  }
  optional StringFieldValidation string_field_validation = 4 [
      retention = RUNTIME,
      target = FILE,
      target = FIELD
  ];

  enum MessageEncoding {
    LENGTH_PREFIXED = 0;
    DELIMITED = 1;
  }
  optional MessageEncoding message_encoding = 5 [
      retention = RUNTIME,
      target = FILE,
      target = FIELD
  ];

  extensions 1000;  // for features_cpp.proto
  extensions 1001;  // for features_java.proto
}

Design Editions Edition Zero Json Handling (`docs/design/editions/edition-zero-json-handling.md`)

Edition Zero: JSON Handling

Author: @mkruskal-google

Approved: 2023-05-10

Background

Today, proto3 fully validates JSON mappings for uniqueness during parsing, while
proto2 takes a best-effort approach and allows cases that don't have a 1:1
mapping. This is laid out in more detail by JSON Field Name Conflicts (not
available externally). While we had hoped to unify these before
Protobuf editions launched, we ended up blocked
by some internal use-cases. This issue is now blocking the editions launch,
since we can't represent this behavior with the current set of
Edition Zero features.

Overview

Today, by default, we transform each field name to a CamelCase name that will
always be valid, but not necessarily unique in JSON. We also support a
json_name field option to override this for JSON parsing/serialization. This
allows conflicts to potentially arise where many proto fields map to the same
JSON field. Our JSON handling has the following behaviors:

  • All proto messages can be serialized to JSON
    • Conflicting mappings will produce JSON with duplicate keys
  • All proto messages can be parsed from JSON
    • Conflicting mappings lead to undefined behavior. While the behavior is
      deterministic in all of the cases we've encountered, it's inconsistent
      across runtimes and unexpected.
  • The Protobuf compiler will fail to parse any proto3 files if any JSON
    conflicts are detected by default
    • Disabled by deprecated_legacy_json_field_conflicts option
  • Proto2 files will only fail to parse if both of the conflicts fields have
    json_name set
    • We will still warn for default json mapping conflicts if
      deprecated_legacy_json_field_conflicts isn't set

The goal here is to unify these behaviors into a future-facing feature as part
of edition zero.

Recommendation

We recommend adding a new json_format feature as part of
Edition Zero features. The doc will be updated to
reflect the following details.

JSON format can have three possible states:

  • ALLOW - By default, fields will be fully validated during proto parsing.
    Any conflicting JSON mappings will trigger protoc errors, guaranteeing
    uniqueness. This will be consistent with the current proto3 behavior. No
    runtime changes are needed, since we allow JSON parsing/serialization.
  • DISALLOW - Alternatively, we will ban JSON encoding and disable all
    validation related to JSON mappings. All runtimes will fail to parse or
    serialize any messages to/from JSON when this feature is set on the
    top-level messages. This is a new mode which provides an alternative to
    LEGACY_BEST_EFFORT that doesn't involve any schema changes.
  • LEGACY_BEST_EFFORT - Fields will be validated for correctness, but not for
    uniqueness. Any conflicting JSON mappings will trigger protoc warnings, but
    no errors. This will be consistent with the current proto2 behavior, or
    proto3 where deprecated_legacy_json_field_conflicts is set. Since this is
    undefined behavior we want to get rid of, a parallel effort will attempt to
    remove this later. No runtime changes are needed, since we allow JSON
    parsing/serialization.

Long-term, we want JSON support to be specified at the proto level. For the
migration from proto2/proto3, we will just migrate everything to ALLOW and
LEGACY_BEST_EFFORT depending on the syntax and the value of
deprecated_legacy_json_field_conflicts.

We will additionally ban any ALLOW message from containing a DISALLOW type
anywhere in its tree (including extensions, which will fail to compile).
Attempting to add this will result in a compiler error. This has the following
benefits:

  • The implementation is a lot simpler, since most of the work is done in
    protoc and parsers only need to check the top level message

  • Runtime failures aren't dependent on the contents of the message being
    serialized/parsed

  • Avoids messy blurring of ownership. If a bug occurs because a DISALLOW
    field is sometimes set, is the owner of the child type required to change it
    to ALLOW? Or is the owner of the parent type responsible because they
    added the dependency?

    LEGACY_BEST_EFFORT will continue to allow serialization/parsing of types
    with DISALLOW set.

This feature will target messages and enums, but we will also provide it at the
file level for convenience.

Example use-cases for DISALLOW:

Alternatives

Dual State

Instead of a tri-state feature, we could have a simple allow/disallow feature
for JSON format.

Pros

  • Simpler conceptually

Cons

  • We would end up blocked by many of the protos that we were unable to migrate
    as part of JSON Field Name Conflicts (not available externally). While
    some of them could be migrated to DISALLOW, others are actually
    depending on our current behavior under JSON mapping conflicts (as a
    hack around some limitations in JSON customization).

Default to DISALLOW

Instead of defaulting to ALLOW, we could default to DISALLOW.

Pros

The majority of internal Google protos are used for binary/text encoding and
don't care about JSON, so this would:

  • Be less noisy for teams who forget to explicitly set DISALLOW and may have
    fields with conflicting JSON mappings
  • Decrease our support surface

Cons

  • We would need to figure out where DISALLOW can be added

Do Nothing

Pros

  • Short-term it saves some trouble and keeps edition zero simpler

Cons

  • We'll eventually hit the same issues we hit in JSON Field Name Conflicts
    (not available externally)
  • The current proto2/proto3 behaviors are mutually exclusive. There's nothing
    we can migrate to in today's edition zero that won't risk breaking one of
    them.

Design Editions Editions Feature Extension Layout (`docs/design/editions/editions-feature-extension-layout.md`)

Editions: Feature Extension Layout

Author: @mkruskal-google,
@zhangskz

Approved: 2023-08-23

Background

"What are Protobuf Editions" lays out a plan
for allowing for more targeted features not owned by the protobuf team. It uses
extensions of the global features proto to implement this. One thing that was
left a bit ambiguous was who should own these extensions. Language, code
generator, and runtime implementations are all similar but not identical
distinctions.

"Editions Zero Feature: utf8_validation" (not available externally, though a
later version,
"Editions Zero: utf8_validation Without Problematic Options"
is) is a recent plan to add a new set of generator features for utf8 validation.
While the sole feature we had originally created (legacy_closed_enum in Java
and C++) didn't have any ambiguity here, this one did. Specifically in Python,
the current behaviors across proto2/proto3 are distinct for all 3
implementations: pure python, Python/C++, Python/upb.

Overview

In meetings, we've discussed various alternatives, captured below. The original
plan was to make feature extensions runtime implementation-specific (e.g. C++,
Java, Python, upb). There are some notable complications that came up though:

  1. Polyglot - it's not clear how upb or C++ runtimes should behave in
    multi-language situations. Which feature sets do they consider for runtime
    behaviors? Note: this is already a serious issue today, where all proto2
    strings and many proto3 strings are completely unsafe across languages.

  2. Shared Implementations - Runtimes like upb and C++ are used as backing
    implementations of multiple other languages (e.g. Python, Rust, Ruby, PHP).
    If we have a single set of upb or cpp features, migrating to those
    shared implementations would be more difficult (since there's no independent
    switches per-language). Note: this is already the situation we're in today,
    where switching the runtime implementation can cause subtle and dangerous
    behavior changes.

Given that we only have two behaviors, and one of them is unambiguous, it seems
reasonable to punt on this decision until we have more information. We may
encounter more edge cases that require feature extensions (and give us more
information) during the rollout of edition zero. We also have a lot of freedom
to re-model features in later editions, so keeping the initial implementation as
simple as possible seems best (i.e. Alternative 2).

Alternatives

Alternative 1: Runtime Implementation Features

Features would be per-runtime implementation as originally described in
"Editions Zero Feature: utf8_validation." For example, Protobuf Python users
would set different features depending on the backing implementation (e.g.
features.(pb.cpp).<feature>, features.(pb.upb).<feature>).

Pros

  • Most consistent with range of behaviors expressible pre-Editions

Cons

  • Implementation may / should not be obvious to users.
  • Lack of levers specifically for language / implementation combos. For
    example, there is no way to set Python-C++ behavior independently of C++
    behavior which may make migration harder from other Python implementations.

Alternative 2: Generator Features

Features would be per-generator only (i.e. each protoc plugin would own one set
of features). This was the second decision we made in later discussions, and
while very similar to the above alternative, it's more inline with our goal of
making features primarily for codegen.

For example, all Python implementations would share the same set of features
(e.g. features.(pb.python).<feature>). However, certain features could be
targeted to specific implementations (e.g.
features.(pb.python).upb_utf8_validation would only be used by Python/upb).

Pros

  • Allows independent controls of shared implementations in different target
    languages (e.g. Python's upb feature won't affect PHP).

Cons

  • Possible complexity in upb to understand which language's features to
    respect. UPB is not currently aware of what language it is being used for.
  • Limits in-process sharing across languages with shared implementations (e.g.
    Python upb, PHP upb) in the case of conflicting behaviors.
    • Additional checks may be needed.

Alternative 3: Migrate to bytes

Since this whole discussion revolves around the utf8 validation feature, one
option would be to just remove it from edition zero. Instead of adding a new
toggle for UTF8 behavior, we could simply migrate everyone who doesn't enforce
utf8 today to bytes. This would likely need another new codegen feature for
generating byte getters/setters as strings, but that wouldn't have any of the
ambiguity we're seeing today.

Unfortunately, this doesn't seem feasible because of all the different behaviors
laid out in "Editions Zero Feature: utf8_validation." UTF8 validation isn't
really a binary on/off decision, and it can vary widely between languages. There
are many cases where UTF8 is validated in some languages but not others, and
there's also the C++ "hint" behavior that logs errors but allows invalid UTF8.

Note: This could still be partially done in a follow-up LSC by targeting
specific combinations of the new feature that disable validation in all relevant
languages.

Pros

  • Punts on the issue, we wouldn't need any upb features and C++ features would
    all be code-gen only
  • Simplifies the situation, avoids adding a very complicated feature in
    edition zero

Cons

  • Not really possible given the current complexity
  • There are O(10M) proto2 string fields that would be blindly changed to bytes

Alternative 4: Nested Features

Another option is to allow for shared feature set messages. For example, upb
would define a feature message, but not make it an extension of the global
FeatureSet. Instead, languages with upb implementations would have a field of
this type to allow for finer-grained controls. C++ would both extend the global
FeatureSet and also be allowed as a field in other languages.

For example, python utf8 validation could be specified as:

We could have checks during feature validation that enforce that impossible
combinations aren't specified. For example, with our current implementation
features.(pb.python).cpp should always be identical to features.(pb.cpp),
since we don't have any mechanism for distinguishing them.

Pros

  • Much more explicit than options 1 and 2

Cons

  • Maybe too explicit? Proto owners would be forced to duplicate a lot of
    features

Design Editions Editions Feature Visibility (`docs/design/editions/editions-feature-visibility.md`)

Editions Feature Visibility

Authors: @mkruskal-google

Approved: 2023-09-08

Background

While Editions: Life of a FeatureSet handles
how we propagate features to runtimes, what's left under-specified is how the
runtimes should expose features to their users. Exposing Editions Feature Sets
(not available externally) was an initial attempt to cover both these topics
(specifically the C++ API section), but much of it has been redesigned since.
This is a much more targeted document laying out how features should be treated
by runtimes.

Problem Description

There are two main concerns from a runtime's perspective:

  1. Direct access to resolved features protos - While runtime decisions
    should be made based on the data in these protos, their struct-like nature
    makes them very rigid. Once users start to depend on the proto API, it makes
    it very difficult for us to do internal refactoring. These protos are also
    naturally structured based on how feature specification is done in proto
    files, rather than the actual behaviors they represent. This makes it
    difficult to guarantee that complex relationships between features and other
    conditions are being uniformly handled.

  2. Accidental use of unresolved features - Unresolved features represent a
    clear foot-gun for users, that could also cause issues for us. Since they
    share the same type as resolved features, it's not always easy to tell the
    two apart. If runtime decisions are made using unresolved features, it's
    very plausible that everything will work as expected in a given edition by
    coincidence. However, when the proto's edition is bumped, it will very
    likely break this code unexpectedly.

Some concrete examples to help illustrate these concerns:

  • Remodeling features - We've bounced back and forth on how UTF8
    validation should be modeled as a feature. None of the proposals resulted in
    any functional changes, since edition zero preserves all proto2/proto3
    behavior, the question was just about what features should be used to
    control them. While the .proto file large-scale change to bump them to the
    next edition containing these changes is unavoidable, we'd like to avoid
    having to update any code simultaneously. If everyone is directly inspecting
    the utf8_validation feature, we would need to do both.

  • Incomplete features - Looking at a feature like packed, it's really
    more of a contextual suggestion than a strict rule. If it's set at the
    file level, all fields will have the feature even though only packable
    ones will actually respect it. Giving users direct access to this feature
    would be problematic, because they would also need to check if it's
    packable before making decisions based on it. Field presence is an even more
    complicated example, where the logic we want people making runtime decisions
    based on is distinct from what's specified in the proto file.

  • Optimizations - One of the major considerations in Exposing Editions
    Feature Sets
    (not available externally) was whether or not it would be
    possible to reduce the cost of editions later. Every descriptor is going to
    contain two separate features protos, and it's likely this will end up
    getting expensive as we roll out edition zero. We could decide to optimize
    this by storing them as a custom class with a much more compact memory
    layout. This is similar to other optimizations we've done to descriptor
    classes, where we have the freedom to because we don't generally expose
    them as protos.

  • Bumpy Edition Large-scale Change - The proto team is going to be
    responsible for rolling out the next edition to internal Google repositories
    every year (at least 80% of it per our churn policy). We expect that
    people are only making decisions based on resolved features, and therefore
    that Prototiller transformations are behavior-preserving (despite changing
    the unresolved features). If people have easy access to unresolved features
    though, we can expect a lot of Hyrum's law issues slowing down these
    large-scale changes.

We recommend a conservative approach of hiding all FeatureSet protos from
public APIs whenever possible. This means that there should be no public
features() getter, and that features should be stripped from any descriptor
options. All options() getters should have an unset features field. Instead,
helper methods should be provided on the relevant descriptors to encapsulate the
behaviors users care about. This has already been done for edition zero features
(e.g. has_presence, requires_utf8_validation, etc), and we should continue
this model.

The one notable place where we can't completely hide features is in
reflection. Most of our runtimes provide APIs for converting descriptors back to
their original state at runtime (e.g. CopyTo and DebugString in C++). In
order to give a faithful representation of the original proto file in these
cases, we should include the unresolved features here. Given how inefficient
these methods are and how hard the resulting protos are to work with, we expect
misuse of these unresolved features to be rare.

Note: While we may need to adjust this approach in the future, this is the
one that gives us the most flexibility to do so. Adding a new API when we have
solid use-cases for it is easy to do. Removing an existing one when we decide we
don't want it has proven to be very difficult.

Enforcement

While we make the recommendation above, ultimately this decision should be up to
the runtime owners. Outside of Google we can't enforce it, and the cost would be
a worse experience for their users (not the entire protobuf ecosystem). Inside
of Google, we should be more diligent about this, since the cost mostly falls on
us.

μpb

One notable standout here is μpb, which is a runtime implementation, but not a
full runtime. Since μpb only provides APIs to the wrapping runtime in a target
language, it's free to expose features anywhere it wants. The wrapping language
should be responsible for stripping them out where appropriate.

Pros

  • Prevents any direct access to resolved feature protos

    • Gives us freedom to do internal refactoring
    • Allows us to encapsulate more complex relationships
    • Users don't have to distinguish between resolved/unresolved features
  • Limits access to unresolved features

    • Accidental usage of these is less likely (especially considering the
      above)
  • This should be easy to loosen in the future if we find a real use-case for
    features() getters.

  • More inline with our descriptor APIs, which wrap descriptor protos but
    aren't strictly 1:1 with them. Options are more an exception here, mostly
    due to the need to expose extensions.

Cons

  • There's no precedent for modifying options() like this. Up until now it
    represented a faithful clone of what was specified in the proto file.

  • Deciding to loosen this in the future would be a bit awkward for
    options(). If we stop stripping it, people will suddenly start seeing a
    new field and Hyrum's law might result in breakages.

  • Requires duplicating high-level feature behaviors across every language. For
    example, has_presence will need to be implemented identically in every
    language. We will likely need some kind of conformance test to make sure
    these all agree.

Considered Alternatives

Expose Features

This is the simplest implementation, and was the initial approach taken in
prototypes. We would just have public features() getters in our descriptor
APIs, and keep the unresolved features in options().

Pros

  • Very easy to implement

Cons

  • Doesn't solve any of the problems laid out above

  • Difficult to reverse later

Hide Features in Generated Options

This is a tweak of the recommended solution where we add a hack to the generated
options messages. Instead of just stripping the features out and leaving an
empty field, we could give the features fields "package-scoped" visibility
(e.g. access tokens in C++). We would still strip them, but nobody outside of
our runtimes could even access them to see that they're empty. This eliminates
the Hyrum's law concern above.

Pros

  • Resolves one of the cons in the recommended approach.

Cons

  • We'd have to do this separately for each runtime, meaning specific hacks in
    every code generator

  • No clear benefit. This only helps if we decide to expose features and
    if a bunch of people start depending on the fact that features are
    always empty.

ClangTidy warning Options Features

Similar to the above alternative, but leverages ClangTidy to warn users against
checking options().features().

Pros

  • Resolves one of the cons in the recommended approach.

Cons

  • Doesn't work in every language

  • Doesn't work in OSS


Design Editions Editions Life Of A Featureset (`docs/design/editions/editions-life-of-a-featureset.md`)

Editions: Life of a FeatureSet

Author: @mkruskal-google

Approved: 2023-08-17

Background

Outside of some minor spelling tweaks, our current implementation of features
has very closely followed the original design laid out in
Protobuf Editions Design: Features. This
approach led to the creation of four different feature sets for each descriptor
though, and it's left under-specified who is responsible for generating these
(protoc, plugins, runtimes), who has access to them, and where they need to be
propagated to.

Exposing Editions Feature Sets (not available externally) was a first attempt
to try to define some of these concepts. It locks down feature visibility to
protoc, generators, and runtimes. Users will only be exposed to them indirectly,
via codegen changes or runtime helper functions, in order to avoid Hyrum's law
cementing every decision we make about them. We (incorrectly) assumed that the
protoc frontend would be able to calculate all the feature sets and then
propagate all four sets to the generators, who would then forward the fully
resolved runtime features to the runtime. This had the added benefit that we
could treat our C++ feature resolution logic as a source-of-truth and didn't
have to reimplement it identically in every language we support.

Editions: Runtime Feature Set Defaults (not available externally) was a
follow-up attempt to specifically handle the default feature sets of an edition.
We had realized that we would need proto2/proto3 default features in each
language to safely roll out editions, and that languages supporting descriptor
pools would have cases that bypass protoc entirely. The solution we arrived at
was that we should continue using the protoc frontend as the source-of-truth,
and propagate these defaults down to the necessary runtimes. This would fix the
proto2/proto3 issue, and at least provide some utilities to make the situation
easier for descriptor pool users.

Protobuf Editions Design: Features
defines the feature resolution algorithm, which can be summarized by the
following diagram:

Feature resolution for a given descriptor starts by using the proto file's
edition and the feature schemas to generate the default feature set. It then
merges all of the parent features from top to bottom, merging the descriptor's
features last.

Glossary

We will be discussing features a lot in this document, but the meaning
behind the word can vary in some subtle ways depending on context. Whenever it's
ambiguous, we will stick to qualifying these according to the following
definitions:

  • Global features - The features contained directly in FeatureSet as
    fields. These apply to the protobuf language itself, rather than any
    particular runtime or generator.

  • Generator features - Extensions of FeatureSet owned by a specific
    runtime or generator.

  • Feature resolution - The process of applying the algorithm laid out in
    Protobuf Editions Design: Features.
    This means that edition defaults, parent features, and overrides have all
    been merged together. After resolution, every feature should have an
    explicit value.

    • Unresolved features - The features a user has explicitly set on
      their descriptors in the .proto file. These have not gone through
      feature resolution and are a minimal representation that require more
      knowledge to be useful.

    • Resolved features - Features that have gone through feature
      resolution, with defaults and inheritance applied. These are the only
      feature sets that should be used to make decisions.

  • Option Retention - We support a retention specification on all options
    (see
    here),
    including features

    • Source features - The features available to protoc and generators,
      before option retention has been applied. These can be either resolved
      or unresolved.

    • Runtime features - The features available to runtimes after option
      retention has been applied. These can be either resolved or unresolved.

Problem Description

The flaw that all of these design documents suffer from is that protoc can't
be the universal source-of-truth for feature resolution under the original
design. For global features, there's of course no issue (protoc has a
bootstrapping setup for `descriptor.proto`` and always knows the global feature
set). For generator features though, we depend on imports to make them
discoverable
.

If a user is actually overriding one of these features, there will necessarily
be an import and therefore protoc will be able to discover generator features
and handle resolution. However, if the user is ok with the edition defaults
there's no need for an import. Without the import, protoc has no way of
knowing
that those generator features exist in general. We could hardcode the
ones we own, but that just pushes the problem off to third-party plugins. We
could also force proto owners to include imports for every (transitive)
language they generate code to, even if they're unused, but that would be very
disruptive and isn't practical or idiomatic.

Pushing the source-of-truth to the generators makes things a little better,
since they each know exactly what feature file needs to be included. There's no
longer any knowledge gap, and we don't need to rely on imports to discover the
feature extension. Additionally, many of our generators are written in C++ (even
non-built-in plugins), so we could at least reuse our existing feature
resolution utility for all of those and limit the amount of duplication
necessary. However, there's still a code-size issue with this approach. As
described in the previous documents, we would need to send four feature sets for
every descriptor to the runtime (i.e. in the generator request and embedded
as a serialized string). We wouldn't be able to use inheritance or references to
minimize the cost, and every generator that embeds a FileDescriptorProto into
its gencode would see a massive code-size increase.

There's also still the issue of descriptor pools that need to be able to build
descriptors at runtime. These are typically power users (and our own unit-tests)
doing very atypical things and bypassing protoc entirely. In previous documents
we've attempted to push some of the cost onto them by explicitly not giving them
feature resolution. They would have to specify every feature on every
descriptor, and would not be able to use edition defaults or inheritance.
However, this cost is fairly high and it also makes the edition field
meaningless. Any missing feature would be a runtime error, and there would be no
concept of "edition". This creates an inconsistent experience for developers,
where they think in terms of editions in one context and then throw it out in
another. Also, it would mean that we have two distinct ways of specifying a
`FileDescriptorProto``: with unresolved features meant to only go through
protoc, and with fully resolved features meant to always bypass protoc.
Round-tripping descriptors would become difficult or impossible.

The following image attempts illustrates the issue:

Here, a proto file is used in both A and B runtimes. The schema itself only
overrides features for A though, and doesn't declare an import on B's features.
This means that protoc doesn't know about B's features, and Generator B will
need to resolve them. Additionally, dynamic messages in both A and B runtimes
have issues because they've bypassed protoc and don't have any way to follow the
feature resolution spec.

Requirements

The following minimal feature sets are required by protoc:

  • Resolved global source features - to make proto-level decisions
  • Unresolved global source features - for validation

For each generator:

  • Resolved generator source features - to make language-specific codegen
    decisions
  • Unresolved generator source features - for validation
  • Resolved global source features - to make more complex decisions

For each runtime:

  • All resolved runtime features - for making runtime decisions
  • All unresolved runtime features - for round-trip behavior and debugging

With some additional requirements on an ideal solution:

  • Minimal code-size costs - code size bloat can easily block the rollout
    of editions, and once those limits are hit we don't have great solutions

  • Minimal performance costs - we want a solution that avoids any
    unnecessary CPU or RAM regressions

  • Minimal code duplication - obviously we want to minimize this, but where
    we can't, we need a suitable test strategy to keep the duplication in sync

  • Runtime support for dynamic messages - while dynamic messages are a
    less-frequently-used feature, they are a critical feature used by a lot of
    important systems. Our solution should avoid making them harder to use in
    any runtime that supports them.

Our long-term recommendation here is to support and use feature resolution in
every stage in the life of a FeatureSet. Every runtime, generator, and protoc
itself will all handle feature resolution independently, only sharing unresolved
features between each other. This will necessarily mean duplication across
nearly every language we support, and the following sections will go into detail
about strategies for managing this.

The main justification for this duplication is the simple fact that edition
defaults
will be needed almost everywhere. The generators need defaults for
their features to get fully resolved generator features to make decisions on,
and can't get them from protoc in every case. The runtimes need defaults for
both global and generator features in order to honor editions in dynamic
messages and to keep RAM costs down (e.g. the absence of feature overrides
should result in a reference to some shared default object). Since the
calculation of edition defaults is by far the most complicated piece of feature
resolution, with the remainder just being proto merges, it makes everything
simpler to understand if we just duplicate the entire algorithm.

Pros

  • Resolved feature sets will never be publicly exposed

    • Our APIs will be significantly simpler, cutting the number of different
      types of feature sets by a factor of 2

    • There will be no ambiguity about what a FeatureSet object means. It
      will always either be unresolved (outside of protobuf code) or fully
      resolved on all accessible features (inside protobuf code).

    • RAM and code-size costs will be minimal, since we'll only be storing and
      propagating the minimal amount of information (unresolved features)

    • Combats Hyrum's law by allowing us to provide wrappers around resolved
      features everywhere, instead of letting people depend on them directly

  • Minimal duplication on top of what's already necessary (edition
    defaults).

  • Dynamic messages will be treated on equal footing to proto files

  • The necessary feature dependencies will always be available in the
    appropriate context

  • We can simplify the current implementation since protoc won't need to handle
    resolution of imported features.

Cons

  • Requires duplication of feature resolution in every runtime and every unique
    generator language

    • This means building out additional infrastructure to enforce
      cross-language conformance

Runtimes Without Reflection

There are various runtimes that do not support reflection or dynamic messages at
all (e.g. Java lite, ObjC). They typically embed the "feature-like" information
they need directly into custom objects in the gencode. In these cases, the
problem becomes a lot simpler because they don't need the full FeatureSet
objects. We don't need to duplicate feature resolution in the runtime, and
the generator can just directly embed the fully resolved features values needed
by the runtime (of course, the generator might still need duplicate logic to get
those).

Staged Rollout for Dynamic Messages

Long-term, we want to be able to handle feature resolution at run-time for any
runtime that supports reflection (and therefore needs FeatureSet objects) to
reduce code-size/RAM costs and support dynamic messages. However, in any
language where these costs are less critical, a staged rollout could be
appropriate. Here, the generator would embed the serialized resolved source
features into the gencode along with the rest of the options. We would use the
raw_features field (which should eventually be deleted) to also include the
unresolved features for reflection.

This would allow us to implement and test editions, and unblock the migration of
all non-dynamic cases. A follow-up optimization at a later stage could push this
down the runtime, and only embed unresolved features in the gencode.

Under this scenario, dynamic messages could still allow editions, as long as
fully-resolved features were provided on every descriptor. When we do implement
feature resolution, it will just be a matter of deleting redundant/unnecessary
features, but there should always be a valid transformation from fully-resolved
features to unresolved ones.

C++ Generators

Generators written in C++ are in a better position since they don't require any
code duplication. They could be given visibility to our existing feature
resolution utility to resolve the features themselves. However, a better
alternative is to make improvements to this utility so that some helpers like
the ones we proposed in Exposing Editions Feature Sets can be used to access
the resolved features that already exist.

Protoc works by first parsing the input protofiles and building them into a
descriptor pool. This is the frontend pass, where only the global features are
needed. For built-in languages, the resulting descriptors are passed directly to
the generator for codegen. For plugins, they're serialized into descriptor
protos, rebuilt in a new descriptor pool (in the generator process), and then
sent to the generator code for codegen. In both of these cases, a
DescriptorPool build of the protos is done from a binary that necessarily
links in the relevant generator features.

Today, we discover features in the pool which are imported by the protos being
built. This has the hole we mentioned above where non-imported features can't be
discovered. Instead, we will pivot to a more explicit strategy for discovering
features. By default, DescriptorPool will only resolve the global features and
the C++ features (since this is the C++ runtime). A new method will be added to
DescriptorPool that allows new feature sets to replace the C++ features for
feature resolution. Generators will register their features via a virtual method
in CodeGenerator and the generator's pool build will take those into account
during feature resolution.

There are a few ways to actually define this registration, which we'll leave as
implementation details. Some examples that we're considering include:

  • Have the generator provide its own DescriptorPool containing the relevant
    feature sets
  • Have the generator provide a mapping of edition -> default FeatureSet
    objects

Expanding on previous designs, we will provide the following API to C++
generators via the CodeGenerator class:

They will have access to all the fully-resolved feature sets of any descriptor
for making codegen decisions, and they will have access to their own unresolved
generator features for validation. The FileDescriptor::CopyTo method will
continue to output unresolved runtime features, which will become unresolved
source features after option retention stripping (which generators should
already be doing), for embedding in the gencode for runtime use.

Example

As an example, let's look at some hypothetical language lang and how it would
introduce its own features. First, if it needs features at runtime it would
create a lang_features.proto file in its runtime directory and bootstrap the
gencode the same as it does for descriptor.proto. It would then also
bootstrap C++ gencode using a special C++-only build of protoc. This can be
illustrated in the following diagram:

This illustrates the bootstrapping setup for a built-in C++ generator. If
generator features weren't needed in the runtime, that red box would disappear.
If this were a separate plugin, the "plugin" box would simply be moved out of
protoc and protoc could also serve as protoc_cpp.

If lang didn't need runtime features, we would simply put the features proto
in the lang generator and only generate C++ code (using the same bootstrapping
technique as above).

After the generator registers lang_features.proto with the DescriptorPool, the
FeatureSet objects returned by GetFeatures will always have fully resolved
lang features.

Non-C++ Generators

As we've shown above, non-C++ generators are already in a situation where they'd
need to duplicate some of the feature resolution logic. With this solution,
they'd need to duplicate much more of it. The GeneratorRequest from protoc
will provide the full set of unresolved features, which they will need to
resolve and apply retention stripping to.

Note: If we're able to implement bidirectional plugin communication, the
Bidirectional Plugins alternative may be a simpler
solution for non-C++ generators that don't need features at runtime. Ones that
need it at runtime will need to reimplement feature resolution anyway, so it may
be less useful.

One of the trickier pieces of the resolution logic is the calculation of edition
defaults, which requires a lot of reflection. One of the ideas mentioned above
in C++ Generators could actually be repurposed to avoid
duplication of this in non-C++ generators as well. The basic idea is that we
start by defining a proto:

text
message EditionFeatureDefaults {
  message FeatureDefaults {
    string edition = 1;
    FeatureSet defaults = 2;
  }
  repeated FeatureDefaults defaults = 1;
  string minimum_edition = 2;
  string maximum_edition = 3;
}

This can be filled from any feature set extension to provide a much more usable
specification of defaults. We can package a genrule that converts from feature
protos to a serialized EditionFeatureDefaults string, and embed this anywhere
we want. Both C++ and non-C++ generators/runtimes could embed this into their
code. Once this is known, feature resolution becomes a lot simpler. The hardest
part is creating a comparator for edition strings. After that, it's a simple
search for the lower bound in the defaults, followed by some proto merges.

Bootstrapping

One major complication we're likely to hit revolves around our bootstrapping of
descriptor.proto. In languages that have dynamic messages, one codegen
strategy is to embed the FileDescriptorProto of the file and then parse and
build it at the beginning of runtime. For descriptor.proto in particular,
handling options can be very challenging. For example, in Python, we
intentionally strip all options from this file and then assume that the options
descriptors always exist during build (in the presence of serialized options).
Since features are options, this poses a challenge that's likely to vary
language by language.

We will likely need to special-case descriptor.proto in a number of ways.
Notably, this file will never have any generator feature overrides, since it
can't import those files. In every other case, we can safely assume that
generator features exist in a fully resolved feature set. But for
descriptor.proto, at least at the time it's first being built by the runtime,
this extension won't be present. We also can't figure out edition defaults at
that point since we don't have the generator features proto to reflect over.

One possible solution would be to codegen extra information specifically for
this bootstrapped proto, similar to what we suggested in Editions: Runtime
Feature Set Defaults
for edition defaults. That would allow the generator to
provide enough information to build descriptor.proto during runtime. As long
as these special cases are limited to descriptor.proto though, it can be left
to a more isolated language-specific discussion.

Conformance Testing

Code duplication means that we need a test strategy for making sure everyone
stays conformant. We will need to implement a conformance testing framework for
validating that all the different implementations of feature resolution agree.
Our current conformance tests provide a good model for accomplishing this, even
though they don't quite fit the problem (they're designed for
parsing/serialization). There's a runner binary that can be hooked up to another
binary built in any language. It sends a ConformanceRequest proto with a
serialized payload and set of instructions, and then receives a
ConformanceResponse with the result. In the runner, we just loop over a number
of fixed test suites to validate that the supplied binary is conformant.

We would want a similar setup here for language-agnostic testing. While we could
write a highly focused framework just for feature resolution, a more general
approach may set us up better in the future (e.g. option retention isn't
duplicated now but could have been implemented that way). This will allow us to
test any kind of transformation to descriptor protos, such as: proto3_optional,
group/DELIMITED, required/LEGACY_REQUIRED. The following request/response protos
describe the API:

text
message DescriptorConformanceRequest {
  // The file under test, pre-transformation.
  FileDescriptorProto file = 1;

  // The pool of dependencies and feature files required for build.
  FileDescriptorSet dependencies = 2;
}

message DescriptorConformanceResponse {
  // The transformed file.
  FileDescriptorProto file = 1;

  // Any additional features added during build.
  FileDescriptorSet added_features = 2;
}

Each test point would construct a proto file, its dependencies, and any feature
files to include in feature resolution. The conformance binary would use this to
fully decorate the proto file with resolved features, and send the result back
for comparison against our C++ source-of-truth. Any generator features added by
the binary will also need to be sent back to get matching results.

Documentation

Because we're now asking third-party generator owners to handle feature
resolution on their own, we will need to document this. Specifically, we need to
open-source documentation for:

On the other hand, we will have significantly less documentation to write about
which feature sets to use where. Descriptor protos will always contain
unresolved features, and C++ generators will have a simple API for getting the
fully-resolved features.

Considered Alternatives

Use Generated Pool for C++ Generators

Note: this was part of the original proposal, but has been refactored (see
cons)

Generators written in C++ are in a better position since they don't require any
code duplication. They could be given visibility to our existing feature
resolution utility to resolve the features themselves. However, a better
alternative is to make improvements to this utility so that some helpers like
the ones we proposed in Exposing Editions Feature Sets can be used to access
the resolved features that already exist.

Protoc works by first parsing the input protofiles and building them into a
descriptor pool. This is the frontend pass, where only the global features are
needed. For built-in languages, the resulting descriptors are passed directly to
the generator for codegen. For plugins, they're serialized into descriptor
protos, rebuilt in a new descriptor pool (in the generator process), and then
sent to the generator code for codegen. In both of these cases, a
DescriptorPool build of the protos is done from a binary that necessarily
links in the relevant generator features.

However, the FeatureSets we supply to generators are transformed to the
generated pool (i.e. FeatureSet objects rather than Message) where the
generator features will always exist. We've decided that there's no longer any
reason to scrape the imports for features, but we could scrape the generated
pool for them. This essentially means that when you call MergeFeatures to get
a FeatureSet, the returned set is fully resolved with respect to the current
generated pool
. This is a much clearer contract, and has the benefit that the
features visible to every C++ generator would automatically be populated with
the correct generator features for them to use.

Expanding on previous designs, we will provide the following API to C++
generators via the CodeGenerator class:

They will have access to all the fully-resolved feature set of any descriptor
for making codegen decisions, and they will have access to their own unresolved
generator features for validation. The FileDescriptor::CopyTo method will
continue to output unresolved runtime features, which will become unresolved
source features after option retention stripping (which generators should
already be doing), for embedding in the gencode for runtime use.

Pros

  • Automatic inclusion of any features used in a binary
  • Features will never be partially resolved

Cons

  • Implicit action at a distance could cause unexpected behaviors
  • Uses globals, making testing awkward
  • Not friendly to DescriptorPool cases who wouldn't necessarily want every
    linked-in feature to go through feature resolution.

Default Placeholders

Protoc continues to propagate and resolve core features and imported language
level features. For language level features that protoc does not know about
(that is, not imported), a core placeholder feature indicating that the default
for a given edition should be respected can be propagated.

text
message FeatureSet {
  optional string unknown_feature_edition_default = N; // e.g. 2023
}

Instead of duplicating the entire feature resolution algorithm, plugins must
only provide a utility mapping editions to their default FeatureSet using the
generator feature files and optionally caching them.

For example:

text
if features.hasUtf8Validation():
  return features.getUtf8Validation()
else:
  default_features = getDefaultFeatures(features.getUnknownFeatureEditionDefault())
  return default_features.getUtf8Validation()

Pros

  • Less duplicate logic for propagating features

Cons

  • Descriptor proto bloat that is technically redundant with
    FileDescriptorProto edition.
  • Confusing that some but not all features are fully resolved
  • Duplicated logic to resolve edition default from edition #
  • Code-size and memory costs associated with the original approach still exist
  • Still doesn't help with the descriptor pool case, which may require
    duplicate logic.

Bidirectional Plugins

Since the generators know the features they care about, we could have some kind
of bidirectional communication between protoc and the plugins. The plugin would
start by telling protoc the features it wants added, and then protoc would be
able to fully resolve all feature sets before sending them off. This has the
added benefit that it would allow us to do more interesting enhancements in the
future. For example, the plugin could send its minimum required edition and
other requirements before actually starting the build.

Note: Bidirectional plugins could still be implemented for other purposes.
This "alternative" is specifically for using that communication to pass
missing feature specs.

Pros

  • Eliminates code duplication problem
  • Provides infrastructure to enable future enhancements

Cons

  • Doesn't address the confusing API we have now where it's unclear what kind
    of features are contained in the features field
  • Doesn't address the code-size and memory costs during runtime
  • Doesn't address the descriptor pool case

Central Feature Registry

Instead of relying on generators and imports to supply feature specs, we could
pivot to a central registry of all known features. Instead of simply claiming an
extension number, generator owners could be required to submit all the feature
protos to a central repository of feature protos. This would give protoc access
to all features. There would be two ways to implement this:

  • If it were built into protoc, we could avoid requiring any import
    statements. We would probably still want an extension point to avoid adding
    a dependency to descriptor.proto, but instead of features.(pb.cpp) they
    would be something more like features.(pb).cpp.

  • We could keep the current extension and import scheme. Proto files would
    still need to import the features they override, but protoc would depend on
    all of them and populate defaults for unspecified ones.

Pros

  • Makes all features easily discoverable wherever they're needed
  • Eliminates the code duplication problem
  • Gives us an option to remove the import statements, which are likely to
    cause future headaches (in the edition zero LSC, in maintenance afterward,
    and also for proto files that need to support a lot of third-party
    runtimes).

Cons

  • Doesn't address the code-size and memory costs
  • Creates version skew problems
  • Confusing ownership semantics

Do Nothing

Doing nothing would basically mean abandoning editions. The current design
doesn't (and can't) work for third party generators. They'd be left to duplicate
the logic themselves with no guidance or support from us. We would also see
code-size and RAM bloat (except in C++) that would be very difficult to resolve.

Pros

  • Less work

Cons

  • Worse in every other way

Design Editions Group Migration Issues (`docs/design/editions/group-migration-issues.md`)

Editions: Group Migration Issues

Authors: @mkruskal-google

Summary

Address some unexpected issues in delimited encoding in edition 2023 before its
OSS release.

Background

Joshua Humphries reported some well-timed
issues discovered
while experimenting with our early release of Edition 2023. He discovered that
our new message encoding feature piggybacked a bit too much on the old group
logic, and actually ended up being virtually useless in general.

None of our testing or migrations caught this because they were heavily focused
on preserving old behavior (which is the primary goal of edition 2023).
Delimited messages structured exactly like proto2 groups (e.g. message and field
in the same scope with matching names) continued to work exactly as before,
making it seem like everything was fine.

All of this is especially problematic in light of Submessages: In Pursuit of a
More Perfect Encoding
(not available externally yet), which intends to migrate the
ecosystem to use delimited encoding everywhere. Releasing a semi-broken feature
as a migration tool to eliminate a deprecated syntax is one thing, but trying to
push the ecosystem to it is especially bad.

Overview

The problems here stem from the fact that before edition 2023, the field and
type name of group fields was guaranteed to always be unique and intuitive.
Proto2 splits groups into a synthetic nested message with a type name equivalent
to the group specification (required to be capitalized), and a field name that's
fully lowercased. For example,

text
optional group MyGroup = 1 { ... }

would become:

text
message MyGroup { ... }
optional MyGroup mygroup = 1;

The casing here is very important, since the transformation is irreversible. We
can't recover the group name from the field name in general, only if the group
is a single word.

The problem under edition 2023 is that we've removed the generation of
synchronized synthetic messages from the language. Users now explicitly define
messages, and any message field can be marked DELIMITED. This means that
anyone assuming that the type and field name are synchronized could now be
broken.

Codegen

While using the field name for generated APIs required less special-casing in
the generators, the field name ends up producing slightly-less-readable APIs for
multi-word camelcased groups. The result is that we see a fairly random-seeming
mix in different generators. Using protoc-explorer (not available externally),
we find the following:

Language Generated APIs Example proto2 getter
C++ field MyGroup mygroup()
Java (all) message MyGroup getMyGroup()
Python field mygroup
Go (all) field GetMygroup() *Foo_MyGroup
Dart V1 field/message* get mygroup
upb ** field Foo_mygroup()
Objective-c message MyGroup* myGroup
Swift message MyGroup myGroup
C# field/message* MyGroup Mygroup

* This codegen difference was caught during the implementation
and intentionally "fixed" in Edition 2023.
** This includes all upb-based runtimes as well (e.g. Ruby, Rust, etc.)
† Extensions use field

In the Dart V1 implementation, we decided to intentionally introduce a behavior
change on editions upgrades. It was determined that this only affected a handful
of protos in google3, and could probably be manually fixed as-needed. Java's
handling changes the story significantly, since over 50% of protos in google3
produce generated Java code. Objective-C is also noteworthy since we open-source
it, and Swift because it's widely used in OSS and we don't own it.

While the editions upgrade is still non-breaking, it means that the generated
APIs could have very surprising spellings and may not be unique. For example,
using the same type for two delimited fields in the same containing message will
create two sets of generated APIs with the same name in some languages!

Text Format

Our "official"
draft specification
of text-format explicitly states that group messages are encoded by the message
name
, rather than the lowercases field name. A group MyGroup will be
serialized as:

text
MyGroup {
  ...
}

In C++, we always serialize the message name and have special handling to only
accept the message name in parsing. We also have conformance tests locking down
the positive path here (i.e. using the message name round-trip). The negative
path (i.e. failing to accept the field name) doesn't have a conformance test,
but C++/Java/Python all agree and there's no known case that doesn't.

To make things even stranger, for extensions (group fields extending other
messages), we always use the field name for groups. So as far as group
extensions are concerned, there's no problem for editions.

There are a few problems with non-extension group fields in editions:

  • Refactoring the message name will change any text-format output
  • New delimited fields will have unexpected text-format output, that could
    conflict with other fields
  • Text parsers will expect the message name, which is surprising and could be
    impossible to specify uniquely

Recommendation

Clearly the end-state we want is for the field name to be used in all generated
APIs, and for text-format serialization/parsing. The only questions are: how do
we get there and can/should we do it in time for the 2023 release in 27.0 next
month?

We propose a combination of the alternatives listed below.
Smooth Extension seems like the best short-term path
forward to unblock the delimited migration. It mostly solves the problem and
doesn't require any new features. The necessary changes for this approach have
already been prepared, along with new conformance tests to lock down the
behavior changes.

Global Feature is a good long-term mitigation for tech debt
we're leaving behind with Smooth Extension. Ultimately we would like to remove
any labeling of fields by their type, and editions provides a good mechanism to
do this. Alternatively, we could implement aliases and use that to
unify this old behavior and avoid a new feature. Either of these options will be
the next step after the release of 2023, with aliases being preferred as long as
the timing works out.

If we hit any unexpected delays, Nerf Delimited Encoding in 2023 (not available
externally) is the quickest path forward to unblock the release of edition 2023.
It has a lot of downsides though, and will block any migration towards delimited
encoding until edition 2024 has started rolling out.

Alternatives

Smooth Extension {#smooth-extension}

Instead of trying to change the existing behavior, we could expand the current
spec to try to cover both proto2 and editions. We would define a "group-like"
concept, which applies to all fields which:

  • Have DELIMITED encoding
  • Have a type corresponding to a nested message directly under its containing
    message
  • Have a name corresponding to its lowercased type name.

Note that proto2 groups will always be "group-like."

For any group-like field we will use the old proto2 semantics, whatever they are
today. Otherwise, we will treat them as regular fields for both codegen and
text-format. This means that most new cases of delimited encoding will have
the desired behavior, while all old groups will continue to function. The main
exception here is that users will see the unexpected proto2 behavior if they
have message/field names that happen to match.

While the old behavior will result in some unexpected capitalization when it's
hit, it's mostly safe. Because of 2 and 3 (and the fact that we disallow
duplicate field names), we can guarantee that in both codegen and text encoding
there will never be any conflicting symbols. There can never be two delimited
fields of the same type using the old behavior, and no other messages or fields
will exist with either spelling.

Additionally, we will update the text parsers to accept both the old
message-based spelling and the new field-based spelling for group-like fields.
This will at least prevent parsing failures if users hit this unexpected change
in behavior.

Pros

  • Fully supports old proto2 behavior
  • Treats most new editions fields correctly
  • Doesn't allow for any of the problematic cases we see today
  • By updating the parsers to accept both, we have a migration path to change
    the "wire"-format
  • Decoupled from editions launch (since it's a non-breaking change w/o a
    feature)

Cons

  • Requires coordinated changes in every editions-compatible runtime (and many
    generators)
  • Keeps the old proto2 behavior around indefinitely, with no path to remove it
  • Plants surprising edge case for users if they happen to name their
    message/fields a certain way

Global Feature {#global-feature}

The simplest answer here is to introduce a new global message feature
legacy_group_handling to control all the changes we'd like. This will only be
applicable to group-like fields (see
Smooth Extension). With this feature enabled,
these fields will always use their message name for text-format. Each
non-conformant language could also use this feature to gate the codegen rules.

Pros

  • Simple boolean to gate all the behavior changes
  • Doesn't require adding language features to a bunch of languages that don't
    have them yet
  • Uses editions to ratchet down the bad behavior

Cons

  • It's a little late in the game to be introducing new features to 2023
    (go/edition-lifetimes)
  • Requires coordinated changes in every editions-compatible runtime (and many
    generators)
  • The migration story for users is unclear. Overriding the value of this
    feature is both a "wire"-breaking and API-breaking change they may not be
    able to do easily.
  • With the feature set, users will still see all of the problems we have today

Feature Suite

An extension of Global feature would be to
split the codegen changes out into separate per-language features.

Pros

  • Simple booleans to gate all the distinct behavior changes
  • Uses editions to ratchet down the bad behavior
  • Better migration story for users, since it separates API and "wire" breaking
    changes

Cons

  • Requires a whole slew of new language features, which typically have a
    difficult first-time setup
  • Requires coordinated changes in every editions-compatible runtime (and many
    generators)
  • Increases the complexity of edition 2023 significantly
  • With the features set, users will still see all of the problems we have
    today

Nerf Delimited Encoding in 2023

A quick fix to avoid releasing a bad feature would be to simply ban the case
where the message and field names don't match. Adding this validation to protoc
would cover the majority of cases, although we might want additional checks in
every language that supports dynamic messages.

This is a good fallback option if we can't implement anything better before 27.0
is released. It allows us to release editions in a reasonable state, where we
can fix these issues and release a more functional DELIMITED feature in 2024.

Pros

  • Unblocks editions rollout
  • Easy and safe to implement
  • Avoids rushed implementation of a proper fix
  • Avoids runtime issues with text format
  • Avoids unexpected build breakages post-editions (e.g. renaming the nested
    message)

Cons

  • We'd still be releasing a really bad feature. Instead of opening up new
    possibilities, it's just "like groups but worse"
  • We couldn't fix this in 2023 without potential version skew from third party
    plugins. We'd likely have to wait until edition 2024
  • Might requires coordinated changes in a lot of runtimes
  • Doesn't unblock our effort to roll out delimited

Rename Fields in Editions

While it might be tempting to leverage the edition 2023 upgrade as a place we
can just rename the group field, that doesn't actually work (e.g. rename
mygroup to my_group). Because so many runtimes already use the field name
in generated APIs, they would break under this transformation.

Pros

  • Works really well for text-format and some languages

Cons

  • Turns 2023 upgrade into a breaking change for many languages

Aliases {#aliases}

We've discussed aliases a lot mostly in the context of Any, but they would be
useful for any encoding scheme that locks down field/message names. If we had a
fully implemented alias system in place, it would be the perfect mitigation
here. Unfortunately, we don't yet and the timeline here is probably too tight to
implement one.

Pros

  • Fixes all of the problems mentioned above
  • Allows us to specify the old behavior using the proto language, which allows
    it to be handled by Prototiller

Cons

  • We want this to be a real fully thought-out feature, not a hack rushed into
    a tight timeline

Do Nothing

Doing nothing doesn't actually break anyone, but it is embarrassing.

Pros

  • Easy to do

Cons

  • Releases a horrible feature full of foot-guns in our first edition
  • Doesn't unblock our effort to roll out delimited

Design Editions Java Lite For Editions (`docs/design/editions/java-lite-for-editions.md`)

Java Lite For Editions

Author: @zhangskz

Approved: 2023-05-26

Background

The "Lite" implementation for Java utilizes a custom format for embedding
descriptors motivated by critical code-size and performance requirements for
Android.

The code generator for Java Lite encodes an descriptor-like info string which is
stored into RawMessageInfo. This is decoded into MessageSchema which serves
as the descriptor-like schema for Java lite for parsing and serialization.

The current implementation makes significant use of an is_proto3 bit in the
encoding, which is problematic for editions. Note that any parser changes to the
format would also need to maintain backwards compatibility, due to our
guarantees for parsers to remain backwards compatible within a major version.

Overview

Fortunately, we already have corresponding bits for most
Editions Zero Features in the corresponding
MessageInfo field entry encoding.

We will move existing remaining syntax usages reading is_proto3 to use these
bits. Several other syntax usages need to be made to be editions compatible by
merging implementations.

As new editions features are added that must be represented in MessageInfo, we
will eventually need to revamp MessageInfo encoding to support these changes.
However, this should be avoidable for Editions Zero.

Recommendation

Encoding: Add Is Edition Bit

RawMessageInfo should be augmented with an additional is_edition bit in
flags' unused bits.

[0]: flags, flags & 0x1 = is proto2?, flags & 0x2 = is message?, flags &
0x4 = is edition?

The decoded ProtoSyntax should add a corresponding Editions option based on
this bit.

text
public enum ProtoSyntax
  PROTO2;
  PROTO3;
  EDITIONS;

For now, there is no need to explicitly encode the raw editions string or
feature options. These resolved features will be encoded directly in their
corresponding field entries.

Encoding: Editions Zero Features

Field entries in RawMessageInfo already encode bits corresponding to most
resolved Editions Zero features in GetExperimentalJavaFieldType. This is
decoded in fieldTypeWithExtraBits by reading the corresponding bits.

Edition Zero Feature **Existing Encoding ** Changes
features.field_presence kHasHasBit (0x1000) Keep as-is.
java.legacy_closed_enum kMapWithProto2EnumValue (0x800) Replace with kLegacyEnumIsClosedBit

This will now be set for all enum fields, instead of just enum map values.

We will still need to check syntax in the interim in case of gencode. |
| features.enum_type | EnumLiteGenerator writes UNRECOGNIZED(-1) value for open enums in gencode.

This is not encoded in MessageInfo since this is an enum feature. | This is not needed in Editions Zero since enum closedness in Java Lite's runtime is dictated per-field by java.legacy_closed_enum. (Edition Zero Feature: Enum Field Closedness), but should be used when Java non-conformance is fixed.

Note, this is implicitly encoded in kLegacyEnumIsClosedBit if java.legacy_closed_enum is unset since the corresponding FieldDescriptor helper should fall back on the EnumDescriptor. |
| features.repeated_field_encoding | GetExperimentalJavaFieldTypeForPacked | Keep as-is. |
| features.string_field_validation | kUtf8CheckBit (0x200) | Keep as-is.

HINT does not apply to Java and will have the same behavior as MANDATORY or NONE |
| features.message_encoding | Not present. | Encode as type group.

See below. |

Several places already use these bits properly, but there are a few syntax
usages in the decoding that should be replaced by checking the corresponding
feature bit.

There are several unused bits that we could use for future field-level features
before breaking the encoding format, but we should not need these for editions
zero.

The results of the is_proto3 and feature bits only seem to be used within
protobuf, and don't seem to be publicly exposed.

features.message_encoding

In the compiler, message fields with features.message_encoding = DELIMITED
should be treated as a group before encoding message info.

This means that GetExperimentalJavaFieldTypeForSingular, should encode the
field's type GROUP (17), instead of its actual type MESSAGE (9), e.g.

text
int GetExperimentalJavaFieldTypeForSingular(const FieldDescriptor* field) {
  int result = field->type();
  if (result == FieldDescriptor::TYPE_MESSAGE) {
    if (field->isDelimited()) {
      return 17; // GROUP
    }
  }
}

ImmutableMessageFieldLiteGenerator::GenerateFieldInfo calls this when
generating the message field's field info.

The nested message's MessageInfo encoding does not need to be changed as this
is already identical for group and message.

Since each message field will be handled separately, this means that the
post-editions proto file below

text
// foo.proto
edition = "tbd"

message Foo {
  message Bar {
    int32 x = 1;
    repeated int32 y = 2;
  }
  Bar bar = 1 [features.message_encoding = DELIMITED];
  Bar baz = 2; // not DELIMITED

}

will be encoded and treated by MessageSchema like its pre-editions equivalent
below.

text
message Foo {
  group Bar = 1 {
    int32 x = 1;
    repeated int32 y = 2;
  }
  Bar baz = 2; // not DELIMITED
}

We recommended this alternative to minimize changes to the encoding and how
groups are treated.

In a future breaking change, we could consider renaming FieldType.GROUP to
FieldType.MESSAGE_DELIMITED while preserving the same number and encoding for
clarity. For now, we will leave the naming for this enum as-is.

Alternative: Add kIsMessageEncodingDelimitedBit

Alternatively, we could encode features.message_encoding = DELIMITED as-is as
type MESSAGE. The MessageInfo encoding would encode these as a normal
message field, using an unused (0x1100) bit as kIsMessageEncodingDelimitedBit.

This could be used to indicate that the message should be parsed/serialized from
the wire-format as if it were a group. This would need to be passed along to
MessageSchema which would then handle treating Messages with this bit set as
groups e.g. in case Message.

This is less ideal, since it would require handling this in multiple places.

Unify non-feature syntax usages

There are several places that branch on syntax into separate proto2/proto3
codepaths. These generally duplicate a lot of code and should be unified into a
single syntax-agnostic code path branching on the relevant feature bits.

This code tends to be pretty opaque, so we should document this with comments or
add helpers (e.g. isEnforceUtf8) to indicate what feature bits are used as we
make changes here.

ManifestSchemaFactory.newSchema() MessageInfo -> Schema Allow extensions for editions.
MessageSchema.getSerializedSize() Message -> Serialized Size Unify getSerializedSizeProto2/3
MessageSchema.writeTo() Serialize Message Unify writeFieldsInAscendingOrderProto2/3
MessageSchema.mergeFrom() Parse Message Unify parseProto2/3Message
DescriptorMessageInfoFactory.convert() Descriptor -> MessageInfo Unify convertProto2/3

There is a lot of dead code in Java Lite so several syntax usages can also be
deleted or merged where possible.

Alternatives

Alternative 1: Introduce New Backwards-compatible MessageInfo Encoding

Add a new backwards-compatible MessageInfo encoding for editions.

The is_edition bit could toggle the encoding format being used, where
is_edition == true indicates the new encoding format but is_edition == false
indicates the old encoding.

This would allow us to encode additional information that the current encoding
format does not currently have available bits to support, such as the editions
string or additional features.

For example, the current encoding format only has a fixed number of available
field entry bits where we could encode new feature bits. We will need to
introduce a new encoding format once we exceed these, or if we want to encode
features at the message level.

In a future major version bump when support for proto2/3 is officially dropped,
we could drop support for the previous encoding format.

The recommendation is to revisit alternative 1 along with alternative 2
post-Editions zero as we need to support additional feature bits.

Pros

  • Future-proof for future editions and features

Cons

  • Blocks editions zero on more complex encoding changes that won't be used
    yet.
  • Requires more invasive updates to all MessageInfo decodings

Alternative 2: Move to MiniDescriptor encoding

We could switch Java Lite to use the MiniDescriptor encoding specification.

Like Java Lite, this encoding seems to be optimized to be lightweight and with
minimal descriptor information.

MiniDescriptors do not encode proto2/proto3 syntax currently, which makes it
mostly editions-compatible. MiniDescriptors encode FieldModifier/MessageModifier
bits that correspond to some editions zero similarly to the Java Lite field
feature bits, and can be augmented to support additional features.

Supposedly, this encoding format should support an arbitrary number of
modifier bits, but this needs to be double-checked to verify there isn't a
similar hard limit to the number of features.

It is unclear whether this is sufficiently optimized for Android's needs and how
compatible this would be with Java Lite's Schemas.

The recommendation is to revisit alternative 2 along with alternative 1
post-Editions zero as we need to support additional feature bits.

Pros

  • Unify implementations for lower long-term maintenance cost

  • MiniDescriptor encoding will eventually need to be updated for editions
    anyways.

Cons

  • Blocks editions zero on more complex encoding changes that aren't necessary.

  • Requires even more invasive updates to all MessageInfo decodings

  • Probably requires major version bumps to break compatibility

  • Unknown code size /schema compatibility constraints that would need to be
    explored.

  • There are a few possible changes to MiniDescriptors on the table that we
    should wait to settle before bringing on additional implementations.

Alternative 3: Do Nothing

Doing nothing is always an alternative. Describe the pros and cons of it.

Pros

  • No work

Cons

  • Editions is blocked since Java Lite protos are stuck in the past

Design Editions Legacy Syntax Editions (`docs/design/editions/legacy-syntax-editions.md`)

Legacy Syntax Editions

Author: @mkruskal-google

Approved: 2023-09-08

Should proto2/proto3 be treated as editions?

Background

Edition Zero Features lays out our plan for edition
2023, which will unify proto2 and proto3. Since early in the design process,
we've discussed the possibility of making proto2 and proto3 "special" editions,
but never laid out what exactly it would look like or determined if it was
necessary.

We recently redesigned editions to be represented as enums
(Edition Naming), and also how edition defaults are
propagated to generators and runtimes
(Editions: Life of a FeatureSet). With these
changes, there could be an opportunity to special-case proto2 and proto3 in a
beneficial way.

Problem Description

While the original plan was to keep editions and syntax orthogonal, that naively
means we'd be supporting two very different codebases. This has some serious
maintenance costs though, especially when it comes to test coverage. We could
expect to have sub-optimal test coverage of editions initially, which would
gradually become poor coverage of syntax later. Since we need to support both
syntax and editions long-term, this isn't ideal.

In the implementation of editions in C++, we decided to unify a lot of the
infrastructure to avoid this issue. We define global feature sets for proto2 and
proto3, and try to use those internally instead of checking syntax directly. By
pushing the syntax/editions branch earlier in the stack, it gives us a lot of
indirect test coverage for editions much earlier.

A separate issue is how Prototiller will support the conversion of syntax to
edition 2023. For features it knows about, we can hardcode defaults into the
transforms. However, third party feature owners will have no way of signaling
what the old proto2/proto3 behavior was, so Prototiller won't be able to provide
any transformations by default. They'd need to provide custom Prototiller
transforms hardcoding all of their features.

We recommend adding two new special editions to our current set:

text
enum Edition {
  EDITION_UNKNOWN = 0;
  EDITION_PROTO2 = 998;
  EDITION_PROTO3 = 999;
  EDITION_2023 = 1000;
}

These will be treated the same as any other edition, except in our parser which
will reject edition = "proto2" and edition = "proto3" in proto files. The
real benefit here is that this allows features to specify what their
proto2/proto3 defaults are, making it easier for Prototiller to handle
migration. It also allows generators and runtimes to unify their internals more
completely, treating proto2/proto3 files exactly the same as editions.

Serialized Descriptors

As we now know, there are a lot of serialized descriptor.proto descriptor sets
out there that need to continue working for O(months). In order to avoid
blocking edition zero for that long, we may need fallbacks in protoc for the
case where feature resolution fails. If the file is proto2/proto3, failure
should result in a fallback to the existing hardcoded defaults. We can remove
these later once we're willing to break stale descriptor.proto snapshots that
predate the changes in this doc.

Bootstrapping

In order to get feature resolution running in proto2 and proto3, we need to be
able to support bootstrapped protos. For these builds, we can't use any
reflection without deadlocking, which means feature defaults can't be compiled
during runtime. We would have had to solve this problem anyway when it came time
to migrate these protos to editions, but this proposal forces our hand early.
Luckily, "Editions: Life of a FeatureSet" already set us up for this scenario,
and we have Blaze rules for embedding these defaults into code. For C++
specifically, this will need to be checked in alongside the other bootstrapped
protos. Other languages will be able to do this more dynamically via genrules.

Feature Inference

While we can calculate defaults using the same logic as in editions, actually
inferring "features" from proto2/proto3 needs some custom code. For example:

  • The required keyword sets LEGACY_REQUIRED feature
  • The optional keyword in proto3 sets EXPLICIT presence
  • The group keyword implies DELIMITED encoding
  • The enforce_utf8 options flips between PACKED and EXPANDED encoding

This logic needs to be written in code, and will need to be duplicated in every
language we support. Any language-specific feature transformations will also
need to be included in that language. To make this as portable as possible, we
will define functions like:

Each type of descriptor will have its own set of transformations that should be
applied to its features for legacy editions.

Pros

  • Makes it clearer that proto2/proto3 are "like" editions

  • Gives Prototiller a little more information in the transformation from
    proto2/proto3 to editions (not necessarily 2023)

  • Allows proto2/proto3 defaults to be specified in a single location

  • Makes unification of syntax/edition code easier to implement in runtimes

  • Allows cross-language proto2/proto3 testing with the conformance framework
    mentioned in "Editions: Life of a FeatureSet"

Cons

  • Adds special-case legacy editions, which may be somewhat confusing

  • We will need to port feature inference logic across all languages. This is
    arguably cheaper than maintaining branched proto2/proto3 code in all
    languages though

Considered Alternatives

Do Nothing

If we do nothing, there will be no built-in unification of syntax and editions.
Runtimes could choose any point to split the logic.

Pros

  • Requires no changes to editions code

Cons

  • Likely results in lower test coverage
  • May hide issues until we start rolling out edition 2023
  • Prototiller would have to hard-code proto2/proto3 defaults of features it
    knows, and couldn't even try to migrate runtimes it doesn't

Design Editions Life Of An Edition (`docs/design/editions/life-of-an-edition.md`)

Life of an Edition

Author: @mcy

How to use Protobuf Editions to construct a large-scale change that modifies the
semantics of Protobuf in some way.

Overview

This document describes how to use the Protobuf Editions mechanism (both
editions, themselves, and features) for
designing migrations and large-scale changes intended to solve a particular kind
of defect in the language.

This document describes:

  • How features are added to the language.
  • How editions are defined and "proclaimed".
  • How to build different kinds of large-scale changes.
  • Tooling in protoc to support large-scale changes.
  • An OSS strategy.

Defining Features

There are two kinds of features:

  • Global features, which are the fields of proto.Features. In this document,
    we refer to them as features.<name>, e.g. features.enum.
  • Language-scoped features, which are defined in a typed extension field for
    that language. In this document, we refer to them as
    features.(<lang>).name, e.g. features.(proto.cpp).legacy_string.

Global features require a descriptor.h change, and are relatively heavy
weight, since defining one will also require providing helpers in Descriptor
wrapper classes to avoid the need for users to resolve inheritance. Because they
are not specific to a language, they need to be carefully, visibility
documented.

Language-scoped features require only a change in a backend's feature extension,
which has a smaller blast radius (except in C++ and Java). Often these are
relevant only for codegen and do not require reflective introspection.

Adding a feature is never a breaking change.

Feature Lifetime

In general, features should have an original default and a desired default:
features are intended to gradually flip from one value to another throughout the
ecosystem as migrations progress. This is not always true, but this means most
features will be bools or enums.

Any migration that introduces a feature should plan to eventually deprecate and
remove that feature from both our internal codebase and open source, generally
with a multi-year horizon. Features are transient.

Removing a feature is a breaking change, but it does not need to be tied to an
edition. Feature removal in OSS must thus be batched into a breaking release.
Deletion of a feature should generally be announced to OSS a year in advance.

Do's and Don'ts

Here are some things that we could use features for, very broadly:

  • Changing the generated API of any syntax production (name, behavior,
    signature, whether it is generated at all). E.g.
    features.(proto.cpp).legacy_string.
  • Changing the serialization encoding of a field (so long as it does not break
    readers). E.g., features.packed, eventually features.group_encoding.
  • Changing the deserialization semantics of a field. E.g., features.enum,
    features.utf8.

Although almost any semantic change can be feature-controlled, some things would
be a bit tricky to use a feature for:

  • Changing syntax. If we introduce a new syntax production, gating it doesn't
    do people much good and is just noise. We should avoid changing how things
    are spelled. In Protobuf's history, it has been incredibly rare that we have
    needed to do this.
  • Shape of a descriptor. Features should generally not cause fields, message,
    or enum descriptors to appear or disappear.
  • Names and field numbers. Features should not change the names or field
    numbers of syntax entities as seen in a descriptor. This is separate from
    using features to change generated API names.
  • Changing the wire encoding in an incompatible way. Using features to change
    the wire format has some long horizons and caveats described below.

Proclaiming an Edition

An edition is a set of default values for all features that protoc's
frontend, and its backends, understand. Edition numbers are announced by
protobuf-team, but not necessarily defined by us. protoc only defines the
edition defaults for global features, and each backend defines the edition
defaults for its features.

Total Ordering of Editions

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 = "2022.1"; in a pinch.

However, protobuf-team does not define editions, it only proclaims them.
Third-party backends are responsible for changing defaults across editions. To
minimize the amount of synchronization, we introduce 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:

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

(Note: The above edition ordering is updated in
Edition Naming.)

Thus, if an imaginary Haskell backend defines a feature
feature.(haskell).more_monads, which becomes true in 2023, the backend can ask
file.EditionIsLaterThan("2023"). If it becomes false in 2023.1, a future
version would ask file.EditionIsBetween("2023", "2023.1").

This means that backends only need to change when they make a change to
defaults. However, backends cannot add things to editions willy-nilly. A backend
can only start observing an edition after protobuf-team proclaims the next
edition number, and may not use edition numbers we do not proclaim.

Proclamation

"Proclamation" is done via a two-step process: first, we announce an upcoming
edition some months ahead of time to OSS, and give an approximate date on which
we plan to release a non-breaking version that causes protoc to accept the new
edition. Around the time of that release, backends should make a release adding
support for that edition, if they want to change a default. It is a faux-pas,
but ultimately has no enforcement mechanism, for the meaning of an edition to
change long (> 1 month) after it has been released.

We promise to proclaim an edition once per calendar year, even if first-party
backends will not use it. In the event of an emergency (whatever that means), we
can proclaim a Y.1, Y.2, and so on. Because of the total order, only
backends that desperately need a new edition need to pay attention to the
announcement. As we gain experience, we should define guidelines for third
parties to request an unscheduled edition bump, but for the time being we will
deal with things case-by-case.

We may want to have a canonical way for finding out what the latest edition is.
It should be included in large print on our landing page, and protoc --latest-edition should print the newest edition known to protoc. The intent
is for tooling that wants to generate .proto templates externally can choose
to use the latest edition for new messages.

Large-scale Change Templates

The following are sketches of large-scale change designs for feature changes we
would like to execute, presented as example use-cases.

Large-scale Changes with No Functional Changes: Edition Zero

We need to get the ecosystem into the "editions" syntax. This migration is
probably unique because we are not changing any behavior, just the spelling of a
bunch of things.

We also need to track down and upgrade (by hand) any code that is using the
value of syntax. This will likely be a manual large-scale change performed
either by Busy Beavers or a handful of protobuf-team members furnished with
appropriate stimulants (coffee, diet mountain dew, etc). Once we have migrated
95% of callers of syntax, we will mark all accessors of that field in various
languages as deprecated.

Because the value of syntax becomes unreliable at this point, this will be a
breaking change.

Next, we will introduce the features defined in
Edition Zero Features. We will then implement
tooling that can take a proto2 or proto3 file and add edition = "2023";
and option features.* = ...; as appropriate, so that each file retains its
original behavior.

This second large-scale change can be fully automated, and does not require
breaking changes.

Large-scale Changes with Features Only: Immolation of `required`

We can use features to move fields off of features.field_presence = LEGACY_REQUIRED (the edition’s spelling of required) and onto
features.field_presence = EXPLICIT_PRESENCE.

To do this, we introduce a new value for features.field_presence,
ALWAYS_SERIALIZE, which behaves like EXPLICIT_PRESENCE, but, if the has-bit
is not set, the default is serialized. (This is sort of like a cross between
required and proto3 no-label.)

It is always safe to turn a proto from LEGACY_REQUIRED to ALWAYS_SERIALIZE,
because required is a constraint on initialization checking, i.e., that the
value was present. This means the only requirement is that old readers not
break, which is accomplished by always providing a value. Because required
fields don't set the value anyways, this is not a behavioral change, but it now
permits writers to veer off of actually setting the value.

After an appropriate build horizon, we can assume that all readers are tolerant
of a potentially missing value (even though no writer would actually be omitting
it). At this point we can migrate from ALWAYS_SERIALIZE to
EXPLICIT_PRESENCE. If a reader does not see a record for the field, attempting
to access it will produce the default value; it is not likely that callers are
actually checking for presence of required fields, even though that is
technically a thing you can do.

Once all required fields have gone through both steps, LEGACY_REQUIRED and
ALWAYS_SERIALIZE can be removed as variants (breaking change).

Large-scale Changes with Editions: absl::string_view Accessors

In C++, a string or bytes typed field has accessors that produce const std::string&s. The missed optimizations of doing this are well-understood, so
we won't rehash that discussion.

We would like to migrate all of them to return absl::string_view, a-la
ctype = STRING_PIECE.

To do this, we introduce features.(proto.cpp).legacy_string[^1], a boolean
feature by default true. When false on a field of appropriate type, it does the
needful and causes accessors to become representationally opaque.

The feature can be set at file or field scope; tooling (see below) can be used
to minimize the diff impact of these changes. Changing a field may also require
changing code that was previously assuming they could write std::string x = proto.string_field();. This has the usual "unspooling string" migration
caveats.

Once we have applied 95% of internal changes, we will upgrade the C++ backend at
the next edition to default legacy_string to false in the new edition. Tooling
(again, below) can be used to automatically delete explicit settings of the
feature throughout our internal codebase, as a second large-scale change. This
can happen in parallel to closing the loop on the last 5% of required internal
changes.

Once we have eliminated all the legacy accessors, we will remove the feature
(breaking change).

Large-scale Changes with Wire Format Break: Group-Encoded Messages

It turns out that encoding and decoding groups (end-marker-delimited
submessages) is cheaper than handling length-prefixed messages. There are
likely CPU and RAM savings in switching messages to use the group encoding.
Unfortunately, that would be a wire-breaking change, causing old readers to be
unable to parse new messages.

We can do what we did for packed. First, we modify parsers to accept message
fields that are encoded as either groups or messages (i.e., TYPE_MESSAGE and
TYPE_GROUP become synonyms in the deserializer). We will let this soak for
three years[^2] and bide our time.

After those three years, we can begin a large-scale change to add
features.group_encoded to message fields throughout our internal codebase
(note that groups don't actually exist in editions; they are just messages with
features.group_encoded). Because of our long waiting period, it is (hopefully)
unlikely that old readers will be caught by surprise.

Once we are 95% done, we will upgrade protoc to set features.group_encoded to
true by default in new editions. Tooling can be used to clean up features as
before.

We will probably never completely eliminate length-prefixed messages, so this
is a rare case where the feature lives on forever.

Large-scale Change Tooling

We will need a few different tools for minimizing migration toil, all of which
will be released in OSS. These are:

  • The features GC. Running protoc --gc-features foo.proto on a file in
    editions mode will compute the minimal (or a heuristically minimal, if this
    proves expensive) set of features to set on things, given the edition
    specified in the file. This will produce a Protochangifier ProtoChangeSpec
    that describes how to clean up the file.

  • The editions "adopter". Running protoc --upgrade-edition -I... file.proto
    figure out how to update file.proto from proto2 or proto3 to the
    latest edition, adding features as necessary. It will emit this information
    as a ProtoChangeSpec, implicitly running features GC.

  • The editions "upgrader". Running protoc --upgrade-edition as above on a
    file that is already in editions mode will bump it up to the latest edition
    known to protoc and add features as necessary. Again, this emits a
    features GC'd ProtoChangeSpec.

This is by no means all the tooling we need, but it will simplify the work of
robots and beavers, along with any bespoke, internal-codebase-specific tooling
we build.

The OSS Story

We need to export our large-scale changes into open source to have any hope of
editions not splitting the ecosystem. It is impossible to do this the way we do
large-scale changes in our internal codebase, where we have global approvals and
a finite but nonzero supply of bureaucratic sticks to motivate reluctant users.

In OSS, we have neither of these things. The only stick we have is breaking
changes, and the only carrots we can offer are new features. There is no "global
approval" or "TAP" for OSS.

Our strategy must be a mixture of:

  • Convincing users this is a good thing that will help us make Protobuf easier
    to use, cheaper to deploy, and faster in production.
  • Gently steering users to the new edition in new Protobuf definitions,
    through protoc diagnostics (when an old edition is going or has gone out of
    date) and developer tooling (editor integration, new-file-boilerplate
    templates).
  • Convincing third-party backend vendors (such as Apple, for Swift) that they
    can leverage editions to fix mistakes. We should go out of our way to design
    attractive migrations for them to execute.
  • Providing Google-class tooling for migrations. This includes the large-scale
    change tooling above, and, where possible, specialized tooling. When it is
    not possible to provide tooling, we should provide detailed migration guides
    that highlight the benefits.
  • Being clear that we have a breaking changes policy and that we will
    regularly remove old features after a pre-announced horizon, locking new
    improvements behind completing migrations. This is a risky proposition,
    because users may react by digging in their heels. Comms planning is
    critical.

The common theme is comms and making it clear that these are improvements
everyone can benefit from, and that there is no "I" in "ecosystem": using
Protobuf, just like using Abseil, means accepting upgrades as a fact of life,
not something to be avoided.

We should lean in on lessons learned by Go (see: their go fix tool) and Rust
(see: their rustfix tool); Rust in particular has an editions/epoch mechanism
like we do; they also have feature gates, but those are not the same concept as
our features. We should also lean on the Carbon team's public messaging about
upgrading being a fact of life, to provide a unified Google front on the matter
from the view of observers.

Prior Art: Rust Editions

The design of Protobuf Editions is directly
inspired by Rust's own
edition system[^3].

Rust defines and ships a new edition every three years, and focuses on changes
to the surface language that do not inhibit interop: crates of different
editions can always be linked together, and "edition" is a parallel ratchet to
the language/compiler version.

For example, keywords (like async) have been introduced using editions.
Editions have also been used to change the semantics of the borrow checker to
allow new programs, and to change name resolution rules to be more intuitive.
For Rust, an edition may require changes to existing code to be able to compile
again, but only at the point that the crate opts into the new edition, to
obtain some benefit from doing so.

Unlike Protobuf, Rust commits to supporting all past editions in perpetuity:
there is no ratcheting forward of the whole ecosystem. However, Rust does ship
with rustfix (runnable on Cargo projects via cargo fix), a tool that can
upgrade crates to a new edition. Edition changes are required to come with a
migration plan to enable rustfix.

Crates therefore have limited pressure to upgrade to the latest edition. It
provides better features, but because there is no EOL horizon, crates tend to
stay on old editions to support old compilers. For users, this is a great story,
and allows old code to work indefinitely. However, there is a maintenance burden
on the compiler that old editions and new language features (mostly) work
correctly together.

In Rust, macros present a challenge: rich support for interpreted, declarative
macros and compiled, fully procedural macros, mean that macros written for older
editions may not work well in crates written on newer editions, or vice versa.
There are mitigations for this in the compiler, but such fixes cannot be
perfect, so this is a source of difficulties in getting total conversion.
Protobuf does not have macros, but it does have rich descriptors that mirror
input files, and this is a potential source of problems to watch out for.

Overall, Rust's migration story is poor: they have accepted they need to support
old editions indefinitely, but only produce an edition every three years.
Protobuf plans to be much more aggressive, and we should study where Rust's
leniency to old versions is unavoidable and where it is an explicit design
choice.

Notes

[^1]: ctype has baggage and I am going to ignore it for the purposes of
discussion. The feature is spelled legacy_string because adding string
view accessors is not likely the only thing to do, given we probably want
to change the mutators as well.
[^2]: The correct size of the horizon is arbitrary, due to the "budget phones in
India" problem. Realistically we would need to pick one, start the
migration, and halt it if we encounter problems. It is quite difficult to
do better than "hope" as our strategy, but packed is an existence proof
that this is not insurmountable, merely very expensive.
[^3]: Rust also has feature gates, used mostly so that people may start trying
out experimental unstable features. These are largely orthogonal to
editions, and tied to compiler versions. Rust's feature gates generally do
not change the semantics of existing programs, they just cause new
programs to be valid. When a feature is "stabilized", the feature flag is
removed. Feature flags do not participate in Rust's stability promises.


Design Editions Minimum Required Edition (`docs/design/editions/minimum-required-edition.md`)

Minimum Required Edition

Author: @mcy

Approved: 2022-11-15

A versioning mechanism for descriptors that ensures old runtimes do not load
descriptors that are "too new."

Background

Suppose we decide to add a novel definition like

text
const int32 MY_CONSTANT = 42;

to the Protobuf language. This would entail a descriptor change to track the
values of constants, but they would not be loaded properly by older runtimes.
This document describes an addition to descriptor.proto that prevents this
version mismatch issue.

Protobuf Editions intends to add the concept of
an edition to Protobuf, which will be an approximately-annually incrementing
value. Because of their annual nature, and because runtimes need to be updated
to handle new features they implement regardless, we can use them as a poison
pill for old runtimes that try to load descriptors that are "too new."

Overview

We propose adding a new field to FileDescriptorProto:

text
optional string minimum_required_edition = ...;

This field would exist alongside the edition field, and would have the
following semantics:

Every Protobuf runtime implementation must specify the newest edition whose
constructs it can handle (at a particular rev of that implementation). If that
edition is less than minimum_required_edition, loading the descriptor must
fail.

"Less than" is defined per the edition total order given in
Life of an Edition. To restate it, it is the following
algorithm:

text
def edition_less_than(a, b):
  parts_a = a.split(".")
  parts_b = b.split(".")
  for i in range(0, min(len(parts_a), len(parts_b))):
    if int(parts_a[i]) < int(parts_b[i]): return True
  return len(a) < len(b)

protoc should keep track of which constructions require which minimum edition.
For example, if constants are introduced in edition 2025, but they are not
present in a file, protoc should not require that runtimes understand
constants by picking a lower edition, like 2023 (assuming no other construct
requires a higher edition).

In particular, the following changes should keep the minimum edition constant,
with all other things unchanged:

  • An upgrade of the proto compiler.
  • Upgrading the specified edition of a file via Prototiller.

Bootstrapping Concerns

"Epochs for descriptor.proto" (not available externally) describes a potential
issue with bootstrapping. It is not the case here: minimum edition is only
incremented once a particular file uses a new feature. Since descriptor.proto
and other schemas used by protoc and the backends would not use new features
immediately, introducing a new feature does not immediately stop the compiler
from being able to compile itself.

Concerns for Schema Producers

Schema producers should consider changes to their schemas that increase the
minimum required edition to be breaking changes, since it will stop compiled
descriptors from being loaded at runtime.

Recommendation

We recommend adding the aforementioned minimum required edition field, along
with the semantics it entails. This logic should be implemented entirely in the
protoc frontend.

Alternatives

Use a Non-Editions Version Number

Rather than using the editions value, use some other version number. This number
would be incremented rarely (3-5 year horizon). This is the approach proposed
in "Epochs for descriptor.proto."

Pros

  • Does not re-use the editions value for a semantically-different meaning; the
    edition remains being "just" a key into a table of features defaults.

Cons

  • Introduces another version number to Protobuf that increments at its own
    cadence.
  • Could potentially be confused with the edition value, even though they serve
    distinct purposes.

Minimum Required Edition Should Not Be Minimal

The proto compiler should not guarantee that the minimum required edition is as
small as it could possibly be.

Pros

  • Reduces implementation burden.

Cons

  • This introduces situations where an upgrade of the proto compiler, or an
    innocuous change to a schema, can lead the the minimum required edition
    being incremented. This is a problem for schema producers.

Do Nothing

Pros

  • Reduces churn in runtimes, since they do not need to implement new handling
    for new editions (as contrasted to just features) regularly.
  • Avoids a situation where old software cannot load new descriptors at
    runtime.

Cons

  • Commits us to never changing the descriptor wire format in
    backwards-incompatible ways, which has far-reaching effects on evolution.
    These consequences are discussed in "Epochs for descriptor.proto."

Design Editions Protobuf Design Options Attributes (`docs/design/editions/protobuf-design-options-attributes.md`)

Protobuf Design: Options Attributes

A proposal to create target and retention attributes to support.

Author: @kfm

Approved: 2022-08-26

Background

The Protobuf Editions project plans to use
custom options to model features and
encourage language bindings to build custom features off options as well.

This design proposed the specific addition of target and retention
attributes for options as well as their suggested meaning.

Both target and retention attributes are no-ops when applied to fields that
are not options (either from descriptor.proto or custom options).

Target Attributes

Historically, options have only applied to specific entities, but features will
be available on most entities. To allow language specific extensions to restrict
the places where options can bind, we will allow features to explicitly specify
the targets they apply to (similar in concept to the "target" attribute on Java
annotations). TARGET_TYPE_UNKNOWN will be treated as absent.

text
message FieldOptions {
  ...
  optional OptionTargetType target = 17;

  enum OptionTargetType {
    TARGET_TYPE_UNKNOWN = 0;
    TARGET_TYPE_FILE = 1;
    TARGET_TYPE_EXTENSION_RANGE = 2;
    TARGET_TYPE_MESSAGE = 3;
    TARGET_TYPE_FIELD = 4;
    TARGET_TYPE_ONEOF = 5;
    TARGET_TYPE_ENUM = 6;
    TARGET_TYPE_ENUM_VALUE = 7;
    TARGET_TYPE_SERVICE = 8;
    TARGET_TYPE_METHOD = 9;
  };
}

If no target is provided, protoc will permit the target to apply to any
entity. Otherwise, protoc will allow an option to be applied at either the
file level or to its target entity (and will produce a compile error for any
other placement). For example

text
message Features {
  ...

  enum EnumType {
    OPEN = 0;
    CLOSED = 1;
  }
  optional EnumType enum = 2 [
      target = TARGET_TYPE_ENUM
  ];
}

would allow usage of

text
// foo.proto
edition = "tbd"

option features.enum = OPEN;  // allowed at FILE scope

enum Foo {
  option features.enum = CLOSED;  // allowed at ENUM scope
  A = 2;
  B = 4;
}

message Bar {
  option features.enum = CLOSED;  // disallowed at Message scope

  enum Baz {
    C = 8;
  }
}

Retention

To reduce the size of descriptors in protobuf runtimes, features will be
permitted to specify retention rules (again similar in concept to "retention"
attributes on Java annotations).

text
enum FeatureRetention {
  RETENTION_UNKNOWN = 0;
  RETENTION_RUNTIME = 1;
  RETENTION_SOURCE = 2;
}

Options intended to inform code generators or protoc itself can be annotated
with SOURCE retention. The default retention will be RUNTIME as that is the
current behavior for all options. Code generators that emit generated
descriptors will be required to omit/strip options with SOURCE retention from
their generated descriptors.
For example:

text
message Cpp {
  enum StringType {
    STRING = 1;
    STRING_VIEW = 0;
    CORD = 2;
  }

  optional string namespace = 2 [
      retention = RETENTION_SOURCE,
      target = TARGET_TYPE_FILE
  ];
}

Motivation

While the proximal motivation for these options is for use with "features" in
"editions", I believe they provide sufficient general utility that adding them
directly to FieldDescriptorOptions is warranted. For example, significant
savings in binary sizes could be realized if ExtensionRangeOptions::Metadata
had only SOURCE retention. Previously, we have specifically special-cased this
behavior on a per-field basis, which does work but does not provide good
extensibility.

Discussion

In the initial design target was serving the dual purpose of identifying the
semantic entity, and also the granularity of inheritance for features. After
discussion about concerns around over use of inheritance, we decided for a
slightly refined definition that decouples these concerns. target only
specifies the semantic entity to which an option can apply. Features will be
able to be set on both the FILE level and their semantic entity. Everything in
between will be refused in the initial release. This allows us a clean
forward-compatible way to allow arbitrary feature inheritance, but doesn't
commit us to doing that until we need it.

Similarly, we will start with optional target, because we can safely move to
repeated later should the need arise.

The naming for target and retention are directly modeled after Java
annotations. Other names were considered, but no better name was found and the
similarity to an existing thing won the day.

Alternatives

Use a repeated `target` proposed

This is the proposed alternative.

Pros

  • Allows fine-grained control of target applicability.

Cons

  • Harder to generalize for users (every feature's specification is potentially
    unique).

Allow hierarchy based on `target` semantic location.

Rather than having a repeated target that specifies all locations, we allow
only the level at which it semantically applies to be specified. The protoc
compiler will implicitly allow the field to be used on entities that can
lexically group that type of entry. For this target can be either singular or
repeated.

Pros

  • Enables tooling that understands when a feature is used for grouping vs when
    it has semantic value (helpful for minimizing churn in large-scale changes).
  • Easier to generalize for users (any FIELD feature can apply to a message
    as opposed to only the FIELD features that explicitly specified an
    additional target).

Cons

  • Forces all target applications to be permitted on scoping entities.

Use Custom Options (aka "We Must Go Deeper")

Rather than building retention and target directly as fields of
FieldOptions, we could use custom options to define an equivalent thing. This
option was rejected because it pushes extra syntax onto users for a fundamental
feature.

Pros

  • Doesn't require modifying descriptor.proto.

Cons

  • Requires a less-intuitive spelling in user code.
  • Requires an additional import for users.
  • Language-level features would have to have a magic syntax or a side table
    instead of using the same consistent option as user per code gen features.

Hard Code Behaviors in `protoc`

Rather than building a generic mechanism we could simply hard code the behavior
of protoc and document it.

Pros

  • Can't be misused.

Cons

  • Not extensible for users.
  • Requires more special cases users need to learn.

Original Approved Proposal

The proposal as originally approved had some slight differences from what was
ultimately implemented:

  • The retention enum did not have an UNKNOWN type.
  • The enums were defined at the top level instead of nested inside
    FieldOptions.
  • The enum values did not have a scoping prefix.
  • The target enum had a STREAM entry, but this turned out to be unnecessary
    since the syntax that it applied to was removed.

Do Nothing

We could omit this entirely and get ice cream instead. This was rejected because
the proliferation of features on entities they do not apply to is considered too
high a cost.

Pros

  • Ice cream is awesome.

Cons

  • Doesn't address any of the problems that caused this to come up.
  • Some people are lactose intolerant.

Design Editions Protobuf Editions Design Features (`docs/design/editions/protobuf-editions-design-features.md`)

Protobuf Editions Design: Features

Author: @haberman,
@fowles

Approved: 2022-10-13

A proposal to use custom options as our way of defining and representing
features.

Background

The Protobuf Editions project uses "editions"
to allow Protobuf to safely evolve over time. An edition is formally a set of
"features" with a default value per feature. The set of features or a default
value for a feature can only change with the introduction of a new edition.
Features define the specific points of change and evolution on a per entity
basis within a .proto file (entities being files, messages, fields, or any other
lexical element in the file). The design in this doc supplants an earlier design
which used strings for feature definition.

Protobuf already supports
custom options
and we will leverage these to provide a rich syntax without introducing new
syntactic forms into Protobuf.

Sample Usage

Here is a small sample usage of features to give a flavor for how it looks

text
edition = "2023";

package experimental.users.kfm.editions;

import "net/proto2/proto/features_cpp.proto";

option features.repeated_field_encoding = EXPANDED;
option features.enum = OPEN;
option features.(pb.cpp).string_field_type = STRING;
option features.(pb.cpp).namespace = "kfm::proto_experiments";

message Lab {
  // `Mouse` is open as it inherits the file's value.
  enum Mouse {
    UNKNOWN_MOUSE = 0;
    PINKY = 1;
    THE_BRAIN = 2;
  }
  repeated Mouse mice = 1 [features.repeated_field_encoding = PACKED];

  string name = 2;
  string address = 3 [features.(pb.cpp).string_field_type = CORD];
  string function = 4 [features.(pb.cpp).string_field_type = STRING_VIEW];
}

enum ColorChannel {
  // Turn off the option from the surrounding file
  option features.enum = CLOSED;

  UNKNOWN_COLOR_CHANNEL = 0;
  RED = 1;
  BLUE = 2;
  GREEN = 3;
  ALPHA = 4;
}

Language-Specific Features

We will use extensions to manage features specific to individual code
generators.

text
// In net/proto2/proto/descriptor.proto:
syntax = "proto2";
package proto2;

message Features {
  ...
  extensions 1000;  // for features_cpp.proto
  extensions 1001;  // for features_java.proto
}

This will allow third-party code generators to use editions for their own
evolution as long as they reserve a single extension number in
descriptor.proto. Using this from a .proto file would look like this:

text
edition = "2023";

import "third_party/protobuf/compiler/cpp/features_cpp.proto"

message Bar {
  optional string str = 1 [features.(pb.cpp).string_field_type = true];
}

Inheritance

To support inheritance, we will specify a single Features message that extends
every kind of option:

text
// In net/proto2/proto/descriptor.proto:
syntax = "proto2";
package proto2;

message Features {
  ...
}

message FileOptions {
  optional Features features = ..;
}

message MessageOptions {
  optional Features features = ..;
}
// All the other `*Options` protos.

At the implementation level, feature inheritance is exactly the behavior of
MergeFrom

text
void InheritFrom(const Features& parent, Features* child) {
  Features tmp(parent);
  tmp.MergeFrom(child);
  child->Swap(&tmp);
}

which means that custom backends will be able to faithfully implement
inheritance without difficulty.

Target Attributes

While inheritance can be useful for minimizing changes or pushing defaults
broadly, it can be overused in ways that would make simple refactoring of
.proto files harder. Additionally, not all features are meaningful on all
entities (for example features.enum = OPEN is meaningless on a field).

To avoid these issues, we will introduce "target" attributes on features
(similar in concept to the "target" attribute on Java annotations).

text
enum FeatureTargetType {
  FILE = 0;
  MESSAGE = 1;
  ENUM = 2;
  FIELD = 3;
  ...
};

These will restrict the set of entities to which a feature may be attached.

text
message Features {
  ...

  enum EnumType {
    OPEN = 0;
    CLOSED = 1;
  }
  optional EnumType enum = 2 [
      target = ENUM
  ];
}

Retention

To reduce the size of descriptors in protobuf runtimes, features will be
permitted to specify retention rules (again similar in concept to "retention"
attributes on Java annotations).

text
enum FeatureRetention {
  SOURCE = 0;
  RUNTIME = 1;
}

Specification of an Edition

An edition is, effectively, an instance of the Feature proto which forms the
base for performing inheritance using MergeFrom. This allows protoc and
specific language generators to leverage existing formats (like text-format) for
specifying the value of features at a given edition.

Although naively we would think that field defaults are the right approach, this
does not quite work, because the default is editions-dependent. Instead, we
propose adding the following to the protoc-provided features.proto:

text
message Features {
  // ...
  message EditionDefault {
    optional string edition = 1;
    optional string default = 2;  // Textproto value.
  }

  extend FieldOptions {
    // Ideally this is a map, but map extensions are not permitted...
    repeated EditionDefault edition_defaults = 9001;
  }
}

To build the edition defaults for a particular edition current in the context
of a particular file foo.proto, we execute the following algorithm:

  1. Construct a new Features feats;.
  2. For each field in Features, take the value of the
    Features.edition_defaults option (call it defaults), and sort it by the
    value of edition (per the total order for edition names,
    Life of an Edition).
  3. Binsearch for the latest edition in defaults that is earlier or equal to
    current.
    1. If the field is of singular, scalar type, use that value as the value of
      the field in feats.
    2. Otherwise, the value of the field in feats is given by merging all of
      the values less than current, starting from the oldest edition.
  4. For the purposes of this algorithm, Features's fields all behave as if
    they were required; failure to find a default explicitly via the editions
    default search mechanism should result in a compilation error, because it
    means the file's edition is too old.
  5. For each extension of Features that is visible from foo.proto via
    imports, perform the same algorithm as above to construct the editions
    default for that extension message, and add it to feat.

This algorithm has the following properties:

  • Language-scoped features are discovered via imports, which is how they need
    to be imported for use in a file in the first place.
  • Every value is set explicitly, so we correctly reject too-old files.
  • Files from "the future" will not be rejected out of hand by the algorithm,
    allowing us to provide a flag like --allow-experimental-editions for ease
    of allowing backends to implement a new edition.

Edition Zero Features

Putting the parts together, we can offer a potential Feature message for
edition zero: Edition Zero Features.

text
message Features {
  enum FieldPresence {
    EXPLICIT = 0;
    IMPLICIT = 1;
    LEGACY_REQUIRED = 2;
  }
  optional FieldPresence field_presence = 1 [
      retention = RUNTIME,
      target = FIELD,
      (edition_defaults) = {
        edition: "2023", default: "EXPLICIT"
      }
  ];

  enum EnumType {
    OPEN = 0;
    CLOSED = 1;
  }
  optional EnumType enum = 2 [
      retention = RUNTIME,
      target = ENUM,
      (edition_defaults) = {
        edition: "2023", default: "OPEN"
      }
  ];

  enum RepeatedFieldEncoding {
    PACKED = 0;
    EXPANDED = 1;
  }
  optional RepeatedFieldEncoding repeated_field_encoding = 3 [
      retention = RUNTIME,
      target = FIELD,
      (edition_defaults) = {
        edition: "2023", default: "PACKED"
      }
  ];

  enum StringFieldValidation {
    REQUIRED = 0;
    HINT = 1;
    SKIP = 2;
  }
  optional StringFieldValidation string_field_validation = 4 [
      retention = RUNTIME,
      target = FIELD,
      (edition_defaults) = {
        edition: "2023", default: "REQUIRED"
      }
  ];

  enum MessageEncoding {
    LENGTH_PREFIXED = 0;
    DELIMITED = 1;
  }
  optional MessageEncoding message_encoding = 5 [
      retention = RUNTIME,
      target = FIELD,
      (edition_defaults) = {
        edition: "2023", default: "LENGTH_PREFIXED"
      }
  ];

  extensions 1000;  // for features_cpp.proto
  extensions 1001;  // for features_java.proto
}

Design Editions Protobuf Editions For Schema Producers (`docs/design/editions/protobuf-editions-for-schema-producers.md`)

Protobuf Editions for Schema Producers

Author: @fowles

Explains the expected use of and interaction with editions for schema providers
and their customers.

Background

The Protobuf Editions project uses "editions"
to allow Protobuf to safely evolve over time. This is primarily accomplished
through "features". The first edition
(colloquially known as "Edition Zero") will use features to unify proto2 and
proto3 (Edition Zero Features). This document will
use definitions from Protobuf Editions: Rollout
but focus primarily on the use case of Schema Producers and Schema
Consumers
:

Schema Producers are teams that produce .proto files for the consumption
of other teams.

As a reminder, features will generally not change the wire format of messages
and thus changing the edition for a .proto will not change the wire format of
message.

Initial Release

There will be a large period of time during which protoc is able to consume
proto3, proto2, and editions files. Once all of the supported protoc
releases handle editions, schema producers should upgrade their published
.proto files to edition zero. The protobuf team will provide a tool that
upgrades proto2 and proto3 files to edition zero in a fully compatible way.

Edition Zero Features

In order to unify proto2 and proto3, "Edition Zero" is taking an opinionated
stance on which choices are good and bad, by choosing "good" defaults and
requiring explicit requests for the "bad" semantics
(Edition Zero Features). Schema producers that are
simply upgrading existing .proto files should publish these files as produced
by the upgrade tool. This will ensure wire compatibility. Newly published
.proto files should use the default values from this first edition.

Steady State Flow

For the most part, editions should not disturb the general pattern for schema
producers. Any schema producer should already specify what versions of protobuf
they support and should not support versions of protobuf that are themselves
unsupported. Schema producers should generally publish all of their .proto
files with a consistent edition for the simplicity of their users. When updating
the edition for their .proto files, producers should target an edition
supported by all of the versions of protobuf in their support matrix. A good
rule of thumb is to target the newest edition supported by the oldest release of
protobuf in the support matrix.

Best Practices

Publish `.proto` files

Schema producers should publish .proto files and not generated sources. This
is already the case and editions do not change it. Publishing generated sources
can lead to mismatches between the compiler and runtime environment used.
Protobuf does not support mixed generation/runtime configurations and sometimes
security patches require updating both.

Minimize use of features

Codegenerator specific features (like
features.(pb.cpp).string_field_type) should only be applied within the context
of a single code base. Consumers of published schemas may wish to add
generator specific features (either by hand or with an automated .proto
refactoring tool), but producers should not force that onto users.

Client Usage Patterns

Consumers’ usage is heavily constrained by their build system. Language agnostic
build systems, like Bazel, can run protoc as one of the build steps. Language
specific build systems, like Maven or Go, make running protoc more difficult
and so consumers often avoid it. Languages like Python that traditionally lack a
build system are more extreme.

Running `protoc` Directly

Because language-specific features will not change the wire format of messages,
clients will be able to update to newer editions or specify specific features
appropriate to their environment while still connecting to external endpoints.

In particular, protobuf will provide two distinct mechanisms for supporting
these users. First, we will provide tools for automating updates to .proto
files in a safe way. These tools will apply semantic patches to .proto files
that they can then commit into source control. Second, we will provide
primitives in protoc to compile a .proto file and a semantic patch as a set
of inputs so that users never have to materialize the modified .proto file.
Protobuf team will investigate adding support for semantic patches when it
addresses Bazel rules.

In the long term, we want a Bazel rule (and possibly similar for other build
systems) that seamlessly packages changes like:

proto
proto_library(
    name = "cloud_spanner_proto",
    modifications = ["cloud_spanner.change_spec"],
    deps = ["@com_google_cloud//spanner"],
)

Publishing Generated Libraries

As a reminder, publishing generated code is not a good idea. It frequently runs
afoul of runtime/generation time mismatches and is an active source of confusion
where users are unable to reason about what version they are on.

Teams determined to do this anyway should adhere to the following best
practices.

  • Publish generated libraries using semver.
  • Publish both the generated code and the protobuf runtime.
  • Pin the protobuf runtime library to the exact version used to generate the
    library.
  • When upgrading the protoc generator for a major, minor, or micro release,
    increment the corresponding number in the published library’s version.
  • When updating the edition of the .proto file, increment the major number
    of the published library’s version.

It is worth note that only the last bullet point is new, everything else is a
restatement of current best practice.


Design Editions Stricter Schemas With Editions (`docs/design/editions/stricter-schemas-with-editions.md`)

Stricter Schemas with Editions

Author: @mcy

Approved: 2022-11-28

Overview

The Protobuf language is surprisingly lax in what it allows in some places, even
though these corners of the syntax space are rarely exercised in real use, and
which add complexity to backends and runtimes.

This document describes several such corners in the language, and how we might
use Editions to fix them (spoiler: we'll add a feature for each one and then
ratchet the features).

This is primarily a memo on a use-case for Editions, and not a design doc per
se.

Potential Lints

Entity Names

Protobuf does not enforce any constraints on names other than the "ASCII
identifier" rule: they must match the regex [A-Za-z_][A-Za-z0-9_]*. This
results in problems for backends:

  • Backends need to be able to convert between PascalCase, camelCase,
    snake_case, and SHOUTY_CASE. Doing so correctly is surprisingly tricky.
  • Extraneous underscores, such as underscores in names that want to be
    PascalCase, trailing underscores, leading underscores, and repeated
    underscores create problems for case conversion and can clash with private
    names generated by backends.
  • Protobuf does not support non-ASCII identifiers, mostly out of inertia more
    than anything else. Because some languages (Java most prominent among them)
    do not support them, we can never support them, but we are not particularly
    clear on this point.

The Protobuf language should be as strict as possible in what patterns it
accepts for identifiers, since these need to be transformed to many languages.
Thus, we propose the following regexes for the three casings used in Protobuf:

  • ([A-Z][a-zA-Z0-9]*)+ for PascalCase. We require this case for:
    • Messages.
    • Enums.
    • Services.
    • Methods.
  • [a-z][a-z0-9]*(_[a-z0-9]+)* for snake_case. We require this case for:
    • Fields (including extensions).
    • Package components.
  • [A-Z][A-Z0-9]*(_[A-Z0-9]+)* for SHOUTY_CASE. We require this case for:
    • Enum values.

These patterns are intended to reject extraneous underscores, and to make casing
of ASCII letters consistent. We explicitly only support ASCII for maximal
portability to target languages. Note that option names are not included, since
those are defined as fields in a proto, and would be subject to this rule
automatically.

To migrate, we would introduce a bool feature feature.relax_identifier_rules,
which can be applied to any entity. When set, it would cause the compiler to
reject .proto files which contain identifiers that don't match the above
constraints. It would default to true and would switch to false in a future
edition.

Keywords as Identifiers

Currently, the Protobuf language allows using keywords as identifiers. This
makes the parser somewhat more complicated than it has to be for minimal
benefit, and shadowing behavior is not well-specified. For example, what does
the following compile as?

text
message Foo {
  message int32 {}
  optional int32 foo = 1;
}

This is particularly fraught in places where either a keyword or a type name can
follow. For example, optional foo = 1; is a proto3 non-optional with type
optional, but the parser can't tell until it sees the = sign.

To avoid this and eventually stop supporting this in the parser, we make the
following set of keywords true reserved names that cannot be used as
identifiers:

text
bool      bytes    double  edition   enum      extend    extensions  fixed32
fixed64   float    group   import    int32     int64     map         max
message   oneof    option  optional  package   public    repeated    required
reserved  returns  rpc     service   sfixed32  sfixed64  sint32      sint64
stream    string   syntax  to        uint32    uint64    weak

Additionally, we introduce the syntax #optional for escaping a keyword as an
identifier. This may only be used on keywords, and not non-keyword
identifiers.

To migrate, we would introduce a bool feature feature.keywords_as_identifiers,
which can be applied to any entity. When set, it would cause the compiler to
reject .proto files which contain identifiers that use the names of keywords.
It would migrate true->false in a future edition. The #optional syntax would
not need to be feature-gated.

From time to time we may introduce new keywords. The best procedure for doing so
is to add a feature.xxx_is_a_keyword feature, start it out as true, and then
switch it to false in an edition, which would cause it to be treated as a
keyword for the purposes of this check. There's nothing stopping us from
starting to use it in the syntax without an edition if it would be relatively
unambiguous (i.e., a "contextual" keyword). Rust provides guidance here: they
really hate contextual keywords since it complicates the parser, so keywords
start out as contextual and become properly reserved in the next Rust edition.

Nonempty Package

Right now, an empty package is technically permitted. We should remove this
functionality from the language completely and require every file to declare a
package.

We would introduce a feature like feature.allow_missing_package, start it out
as true, and switch it to false.

Invalid Names in `reserved`

Currently, reserved "foo-bar"; is accepted. It is not a valid name for a field
and thus should be rejected. Ideally we should remove this syntax altogether and
only permit the use of identifiers in this position, such as reserved foo, bar;.

We would introduce a feature like feature.allow_strings_in_reserved, start it
out as true, and then switch it to false.

Almost All Names are Fully Qualified

Right now, Protobuf defines a complicated name resolution scheme that involves
matching subsets of names inspired by that of C++ (which is even more
complicated than ours!). Instead, we should require that every name be either a
single identifier OR fully-qualified. This is an attempt to move to Go-style
name resolution, which is significantly simpler to implement and explain.

In particular, if a name is a single identifier, then:

  • It must be the name of a type defined at the top level of the current file.
  • If it is the name of a message or enum for a field's type, it may be the
    name of a type defined in the current message. This does not apply to
    extension fields.

Because any multi-component path must be fully qualified, we no longer need the
.foo.Bar syntax anymore, except to refer to messages defined in files without
a package. We forbid .-prefixed names except in that case.

We would introduce a feature like features.use_cpp_style_name_resolution,
start it out as true, and then switch it to false.

Ideally, if we get strict identifier names, we can tell that Foo.Bar is rooted
at a message, rather than a package. In that case, we could go as far as saying
that "names that start with a lower-case letter are fully-qualified, otherwise
they are relative to the current package, and will only find things defined in
the current file."

Unlike Go, we do not allow finding things in other packages without being
fully-qualified; this mostly comes from doing source-diving in very large
packages, like the Go runtime, where it is very hard to find where something is
defined.

Unique Enum Values

Right now, we allow aliases in enums:

text
enum Foo {
  BAR = 5;
  BAZ = 5;
}

This results in significant complexity in some parts of the backend, and weird
behavior in textproto and JSON. We should disallow this.

We would introduce a feature like features.allow_enum_aliases, which would
switch from true to false.

Imports are Used

We should adopt the Go rule that all non-public imports are used (i.e, every
import provides at least one type referred to in the file).

We would introduce a feature like features.allow_unused_imports, which would
switch from true to false.

Next Field # is Reserved

There's a few idioms for this checked by linters, such as // Next ID: N. We
should codify this in the language by rewriting that every message begin with
reserved N to max;, with the intent that N is the next never-used field
number. Because it is required to be the first production in the message, it can
be

We could, additionally, require that every field number be either used or
reserved, in addition to having a single N to max; reservation. Alternatively,
we could require that every field number up to the largest one used be reserved;
gaps between message numbers are usually a smell.

This applies equally to message fields and enum values.

We would introduce a feature like features.allow_unused_numbers, which we
would switch from true to false.

Disallow Implicit String Concatenation

Protobuf will implicitly concatenate two adjacent strings in any place it allows
quoted strings, e.g. option foo = "bar " "baz;. This has caused interesting
problems around reserved in the past, if a comma is omitted: reserved "foo" "bar"; is reserved "foobar";.

We would introduce a feature like features.concatenate_adjacent_strings, which
would switch from true to false.

Package Is First

The package declaration can appear anywhere in the file after syntax or
edition. We should take cues from Go and require it to be the first thing in
the file, after the edition.

We would introduce a feature like features.package_anywhere, which would
switch from true to false.

Strict Boolean Options

Boolean options can use true, false, True, False, T, or F as a value: option my_bool = T;. We should restrict to only true and false.

We would introduce a feature like features.loose_bool_options, which would
switch from true to false.

Decimal Field Numbers

We permit non-decimal integer literals for field numbers, e.g. optional int32 x = 0x01;. Thankfully(?) we do not already permit a leading + or -. We should
require decimal literals, since there is very little reason to allow other
literals and makes the Protobuf language harder to parse.

We would introduce a feature like features.non_decimal_field_numbers, which
would switch from true to false.


Design Editions What Are Protobuf Editions (`docs/design/editions/what-are-protobuf-editions.md`)

What are Protobuf Editions?

Authors: @mcy, @fowles

Summary

This document is an introduction to the Protobuf Editions project, an ambitious
re-imagining of how we migrate Protobuf users into the future.

Goal

Enable incremental evolution of Protobuf across the entire ecosystem without
introducing permanent forks in the Protobuf language.

TL;DR

  1. We are replacing
    syntax = ... with edition = ....
    • We plan to produce a new "edition" on a roughly yearly basis.
    • We plan to regularly deprecate and remove old editions after a wide
      horizon.
    • This gradual churn is enabled by the
      Protobuf Breaking Changes policy.
  2. "Features" are a special kind of file/message/field/enum/etc option.
    • Features control the individual codegen and runtime behavior of fields,
      messages, enums, etc.
    • Features cannot introduce changes that would directly break existing
      binaries.
    • We expect heavy churn of features in .proto files, so their design is
      optimized to minimize diffs to .proto files while permitting
      fine-grained control.
    • Features are usually attached to the field/message/enum they apply
      to.
      • Features can be specified at a higher-level entity, such as a file,
        to apply to all definitions inside of that entity. This is called
        feature inheritance.
      • Inheritance is intended to allow us to factor frequently-occurring
        feature declarations, minimizing clutter during migrations.
  3. Editions change only the defaults of features and do not otherwise introduce
    new behavior.
    • New behavior is fundamentally controlled by features (explicitly set or
      implicit from an edition).
    • Editions allow us to ratchet the ecosystem forward.
      • Editions can be incremented on a per .proto file basis; projects
        can upgrade incrementally.
  4. Messages with any permutation of features are always interoperable (they can
    import each other freely and use messages from each other).
    • Editions do not split the ecosystem, and migration is largely automated.
    • Directly inspired by
      Rust editions.
    • Carbon has a similar philosophy
  5. The proto2/proto3 distinction is going away.
    • Editions will support everything from both and allow mixed semantics
      even within the same message or field.
    • Undesirable features will be LSC'd away, using the same template as any
      other feature/edition migration.

Motivation

Arguably the biggest hard-earned lesson among Software Foundations is that
successful migrations are incremental. Most of our experience with these has
been for internal migrations. Externally, progress has often ossified because of
a lack of established evolution mechanisms. More recently large projects have
started planning incremental evolution into their structure. For example, Carbon
is heavily focused on evolution as a core precept, and Rust has built language
evolution via editions into its core design..

Protobuf is one of Google's oldest and most successful toolchain projects.
However, it was designed before we learned and internalized this lesson, making
modernization difficult and haphazard. We still have required and group,
packed is not everywhere, and string accessors in C++ still return const std::string&. The last radical change to Protobuf (syntax = "proto3";) split
the ecosystem.

Editions and features are new language features that will allow us to
incrementally evolve Protobuf into the future. This will be done by introducing
a new syntax, hopefully the last syntax addition we will ever need.

This high-level document is intended as an introduction to Protobuf Editions for
engineers not familiar with the background and the set of tradeoffs that lead us
here. Low-level technical details are skipped in preference to describing the
kernel of our proposed design. This document reflects the approximate consensus
of protobuf-team members who have been developing Protobuf Editions, but please
beware: many open questions remain.

What is a feature?

A feature, in the narrow context of Protobuf Editions, is an option on any
syntax entity of a .proto file that has the following properties:

  • It is a field or extension of a top-level option named features, which is
    present on every syntax entity (file, message, enum, field, etc). It can be
    of any type, but bool and enum are the most common.
  • If a syntax entity's lexical parent has a particular value for a feature,
    then the child has the same value, unless the feature has a new value
    specified on the child, explicitly. This is called feature inheritance,
    and applies recursively. Features can specify a new value at any of the
    points where a feature can be added.
  • It explicitly specifies what syntax entities it can be set on, similar to
    Java annotations (although this does not preclude inheritance to or through
    an entity that it cannot be set on).

Features allow us to control the behavior of protoc, its backends, and the
Protobuf runtimes at arbitrary granularity. This is critical for large-scale
changes: if a message has few usages, features can be changed at a bigger scope,
minimizing diff churn, but if it has heavy usage and the CL to migrate a single
field is large, cleanups can happen at the field level, as necessary.

Features won't change a message’s serialization formats (binary, text, or json)
in incompatible ways except for extreme circumstances that will always be
managed directly by protobuf-team. It is critical for migrations that any
behavioral change coming from a feature is the result of a textual change to a
.proto file (either an edition bump or a feature change).

ctype is an existing field option that looks exactly like a feature: it
controls the behavior of the codegen backend, although it does not have the nice
ratcheting properties of editions.

Because features can be extensions, language backends can specify
language-scoped features. For example, [ctype = CORD] could instead be
phrased as [features.(pb.cpp).string_type = CORD]. Codegen backends own the
definitions of their features.

What is an Edition?

An edition is a collection of defaults for features understood by protoc and
its backends. Editions are year-numbered, although we have defined a breakout in
case we need multiple editions in a particular year.

Instead of writing syntax = "...";, a Protobuf Editions-enabled .proto file
begins with edition = "2022"; or similar. edition implies syntax = "editions";, and the syntax keyword itself becomes deprecated. This is to
ensure that old tools not owned by protobuf-team, which only work for old
Protobuf syntaxes, crash or fail quickly and noticeably, instead of wandering
into a descriptor that they cannot understand (we will attempt to migrate what
we can, of course).

protoc specifies which editions it understands, and will reject .proto files
"from the future", since it cannot meaningfully parse them. protoc backends,
which can specify their own set of language-scoped features, must advertise the
defaults for a particular edition that they understand (and reject editions that
they don't). Runtimes must be able to handle descriptors "from the future"; this
only means that upon encountering a descriptor with an edition or feature it
does not understand, there must be a reasonable fallback for the runtime's
behavior.

What is an Edition used for?

Editions provide the fundamental increments for the lifecycle of a feature. At
this point it is important to reiterate that mo