{% set rfcid = “RFC-0024” %} {% include “docs/contribute/governance/rfcs/_common/_rfc_header.md” %}
Note: Formerly known as FTP-024.
Establish a source-compatibility standard for FIDL language bindings, as well as a process for evolving that standard.
Today, there are few written rules for the code generated by language bindings. It's expected that they conform to a specific wire ABI, but aside from that, binding authors are given lots of leeway over how they shape their APIs. Any change to a FIDL definition could cause arbitrary changes to the generated bindings.
In practice, users expect a sort of “common sense” list of things that should be source-compatible, such as defining a new top-level type. However, there‘s no explicit rule saying that this is the case. While this case seems somewhat absurd, it illustrates how a lack of specification can ruin users’ expectations. Real examples of this that have occurred in practice include adding fields to tables, adding a new xunion variant, or adding a new defaulted field to a struct. Users could reasonably expect that these changes wouldn‘t be source-breaking, but there’s no standard specifying this, and all of these changes cause source-level breakage in one or more language bindings today (e.g. due to positional initializers in C++ or Go, or struct patterns in Rust).
Furthermore, there are a number of extremely useful extensions to FIDL language bindings that have been rejected in the past due to their interaction with source compatibility. Examples of this include adding a copy
or clone
function to types that don't contain handles. Types that contain arbitrary handles cannot be cloned, so adding a handle to a type would prevent it from offering a clone
function (or prevent it from offering a clone function that worked, at any rate). A change to introduce conditional inclusion of a clone
function to generated Rust bindings based on the absence of handles has been rejected multiple times due to its effects on source compatibility. As a result, Fuchsia developers have had to manually roll their own clone
functions and add wrapper types for the FIDL generated types that clone
via these hand-rolled methods. This document proposes a consistent standard against which we can evaluate functionality like this, hopefully providing a more ergonomic, user-friendly, and boilerplate-free experience to developers.
This FTP establishes an initial set of source compatibility constraints. This list will be tracked in a document in the Fuchsia source tree. Additional source compatibility constraints must be added using the FTP process. To facilitate easy addition of source compatibility rules related to new features, the “Backwards Compatibility” section of the FTP template will be amended to include a suggestion to introduce new source compatibility constraints (where applicable).
Changes below are required to be either source-compatible (i.e., non-source-breaking) or transitionable.
Source-compatible changes must not cause source-breakage to any valid (compiling) usage of the public API of the generated FIDL bindings. There‘s some reasonable argument over the definition and feasibility of restricting which features are part of the “public API”, and which aren’t; so for the purposes of this document we consider the “public API” to be any use of the generated bindings that does not require either extraordinary language gymnastics (e.g. reflection) or explicit developer intent to violate privacy (e.g. calling __private_dont_use_me_function_2()). All other APIs exposed (e.g., positional initialization, pattern-matching, etc.) must be constrained so that user code cannot be broken by source-compatible changes to FIDL libraries.
Transitionable changes are changes for which it is possible to write code that compiles both before and after the change. Each transitionable source-compatibility rule must specify exactly what “use” of the API must be possible during the transition.
The following are a list of changes that must be source-comptabile:
strict
modifier can be used.strict
modifier can be used.[Layout = "Simple"]
to an existing protocol[Layout = "Simple"]
exists in order to enable usage in simple C bindings. Existing protocols that conform should not require a breaking source change in order to specify that they can be used in the simple C bindings.[MaxHandles]
to an existing type[MaxHandles]
exists to provide extra information about a type so that it can be used more permissively. It should not require a breaking source change in order to specify that a type already contains a fixed maximum number of handles and may be assumed to continue containing at most that number of handles.The following are a list of changes that must be transitionable:
[Transitional]
to a method[Transitional]
attribute to that method.[Transitional]
method[Transitional]
method (though the API need not allow implementation of the method during the transition).[Transitional]
method[Transitional]
method (though the API need not allow implementation of the method during the transition).strict
modifier can be used on the table.strict
modifier can be used on the table.strict
strict
is added.strict
is intended to be added to a type declaration once that type has stabilized, allowing increased reasoning and developer tooling. However, this is only required as a transitionable change and not a non-breaking change because extensible types may wish to allow access to unrecognized fields or variants. These capabilities don't make sense for a strict
type, as unrecognized fields or variants would be rejected.[Transitional]
to a member of an enum or bits, field of a table, or variant of an extensible union.[Transitional]
value using the same source both before and after the introduction of [Transitional]
.[Transitional]
.[Transitional]
value using the same source both before and after the introduction of the new [Transitional]
field.[Transitional]
value using the same source both before and after the removal of the [Transitional]
field.The following are potential constraints that have been omitted from this list, including justification as to why they have been omitted:
strict
is intended to enable additional developer tooling that relies on “for all fields”-style reasoning about a type, including automatic method derivation (e.g. “does this type contain any floats”), positional initializers, and exhaustive field matching and construction. Forcing this to be a non-breaking change would inhibit this purpose.[MaxHandles]
This FTP establishes the initial proposed language compatibility standard. Bugs will be filed and assigned to one author of each language binding to ensure that their languages bindings are compliant.
This change makes FIDL easier to use by setting clear standards for source compatibility, allowing for automatic checking as well as easier manual checking of FIDL changes' source-compatibility, as well as offering bindings authors clearer guidance on source compatibility, allowing them the freedom to make bindings that are language-idiomatic while still respecting standard requirements of the project.
Following the acceptance of this FTP, the process established by the FTP as well as the source compatibility rules themselves will be published along with other FIDL reference documentation.
Application of the guidance proposed may require changes to bindings and uses of those bindings, it is up to the respective binding authors to navigate such changes.
This section (the “backwards compatibility” section) will be amended to include the following text:
“If you are introducing a new data type or language feature, consider what changes you would expect users to make to FIDL definitions without breaking users of the generated code. If your feature places any new source compatibility restrictions on the generated language bindings, list those here.”
Note that you should include the source compatibility text as an actual link to this FTP, that is:
[source compatibility](/docs/contribute/governance/rfcs/0024_mandatory_source_compatibility.md)
This FTP does not restrict runtime behavior, although the restrictions on source APIs may cause language binding authors to design more or less performant APIs. The feasibility of creating performant bindings in supported languages should be considered when new source compatibility restrictions are introduced.
This feature might affect compile-time performance as a result of pushing towards patterns that require heavier inlining and compiler optimizations in order to be performant (e.g. optimizing away a complex builder API into a simple struct initialization). Bindings authors should strive to make design choices that don't significantly hamper compile times, but the compile-time-consequence of a particular language API should not necessarily prevent the introduction of a new source compatibility restriction.
This feature does not affect security.
Many source compatibility rules are of the form “there cannot exist any user code that compiled before this change but not after this change.” Unfortunately, these restrictions are difficult or impossible to test because they would require enumerating every possible usage of the API before the change.
However, we can (and should) add items to the FIDL change test suite to show that there does exist some usage of the API before the change that remains valid after the change. This is a necessary but not sufficient condition for meeting the source compatibility requirements.
Previous attempts have been made to introduce evolvability restrictions via the [MaxHandles]
attribute. This design and the intended modifications to it have been discussed in earlier parts of this proposal.