Google is committed to advancing racial equity for Black communities. See how.

RFC-0044 - Extensible method arguments

RFC-0044 - Extensible method arguments
StatusRejected
Areas
  • FIDL
Description

We encourage FIDL library authors to use tables rather than structs when extensibility is required however method arguments are encoded as structs. This proposes a way to have extensible method arguments built on top of tables.

Issues
Authors
Date submitted (year-month-day)2019-04-08

Rejection rationale

Obsoleted by RFC-0050.

Summary

We encourage FIDL library authors to use tables rather than structs when extensibility is required however method arguments are encoded as structs. This proposes a way to have extensible method arguments built on top of tables.

Motivation

The lack of syntax for extensible method arguments discourages library authors from using tables to make method arguments extensible. By including this in the syntax it will put extensibility considerations front-and-center when designing protocols.

The Modular team is designing protocols that need to both maintain ABI compatibility and be extensible as they discover new requirements and evolve their designs. They're considering defining their methods in terms of tables that they're declaring out of line.

Design

This proposal extends the FIDL source language and affects the language bindings.

FIDL Syntax

It proposes an extension to the syntax for method and event request and response arguments to add a table to the argument structs.

For example this protocol:

protocol Example {
    Foo(int32 arg1, { 1: string arg2, 2: bool arg3 }) -> ({});
};

declares a method with one required argument, two optional arguments, an extensible request and a response that is extensible. It's equivalent to declaring:

table ExampleFooRequestExtension {
    1: string arg2;
    2: bool arg3;
};
table ExampleFooResponseExtension {
};
protocol Example {
    Foo(int32 arg1, ExampleFooRequestExtension extension)
        -> (ExampleFooResponseExtension extension);
}

IR

The current version of the IR can be extended in a backward-compatible way. Method arguments that are extensible with have tables named: {Protocol}{Method}RequestExtension, {Protocol}{Method}ResponseExtension or {Protocol}{Method}EventExtension included. The [ExtensionArgument] attribute will be set on these generated tables so that bindings generators can handle them specially if they wish.

A future IR may elevate this idea to a more first-class structure. A future IR should allow a more flexible approach to naming of declaration so that languages could make better choices in naming the extension tables, for example C++ could nest them inside the protocol definition class.

Bindings

Bindings don't need special support for extensible method arguments. Existing bindings generators will simply include the generated tables as the last argument to methods.

C++

For example the C++ bindings, without any changes for the protocol above would roughly look like:

class ExampleFooRequestExtension;
class ExampleFooResponseExtension;
class Example {
  using FooCallback = fit::function<void(ExampleFooResponseExtension)>;
  virtual void Foo(int32_t arg1,
                   ExampleFooRequestExtension extension,
                   FooCallback callback) = 0;
};

An alternative way to bind this protocol would be:

class Example {
  using FooCallback = fit::function<void()>;
  virtual void Foo(int32_t arg1,
                   FooCallback callback,
                   std::optional<std::string> arg2 = std::optional<std::string>(),
                   std::optional<bool> arg3 = std::optional<bool>()) = 0;
};

This would more closely match how the method was declared but would put the callback argument in between the static and extensible arguments.

Dart

Dart's support for optional, named arguments allows a nice mapping of the FIDL concept to its syntax. Dart's lack of support for tuples or variadic futures remains limiting. The binding interface might look like:

abstract class Example {
  Future<ExampleFooResponseExtension> foo(int arg1, {String arg2, bool arg3});
}

With this syntax adding additional extension arguments preserves source compatibility.

Rust

TBD

Go

TBD

Simple C

The simple C bindings don't support tables so this feature would be incompatible with them.

Implementation strategy

The first step would be to add support for the new syntax into fidlc and update the reference and tutorial documentation.

Next we would add support to the Dart bindings because they're where the most obvious ergonomic benefits exist.

Ergonomics

Allowing the evolution of FIDL protocols is an important feature that users are seeking. Currently changing the arguments of a method is an ABI and API breaking change. The approaches to softening that are either to introduce new method names for each change and continuing to support the old method as long as there are still callers, or including a table as an argument and adding new arguments to that table. This proposal allows the expression of the latter approach in a more ergonomic way. It keeps arguments defined in the method definition so that they can be easily referred to in documentation comments.

The idea of optional arguments is common in many programming languages. It won't be a surprising concept to library authors.

The need for explicit ordinals in tables makes extension arguments inconsistent with required arguments but it's better to keep them consistent with tables rather than introduce a new table-like structure with hashed ordinals.

Documentation and examples

As an extension to the FIDL language the FIDL reference and tutorial documentation should be updated.

Backwards compatibility

Existing FIDL libraries aren't affected by this change.

This proposal significantly improves the ability library authors to maintain ABI compatible interfaces over the long-term.

Constraints around source compatibility are still TBD and will be informed by how we plan to bind to our supported languages.

Performance

Tables are more expensive to encode and decode than structs so performance critical protocols should use this feature sparingly

Security

No impact

Testing

Tests should be added to fidlc. The dangerous identifiers test should test the use of dangerous identifiers as optional arguments.

Drawbacks, alternatives, and unknowns

Alternatives

We could switch from structs to tables on a per-method or per-protocol basis. We could even switch the default from being structs to tables. This approach is less flexible. Often only the request or the response is expected to be extended. Often some of the arguments (for example in Modular's case a module id) is expected to remain stable in the long term while others are not.

Instead of using tables and requiring ordinals for each extended argument we could define a table-like data structure that hashes names to calculate ordinals. This would simplify the syntax of the source language but add complexity to the encoders and decoders and language bindings.

We could have versioned methods. This option wasn't explored in depth.

Open questions

We should decide how this will be bound in C++, Rust and Go.

Should adding arguments be allowed to break source compatibility?

Prior art and references

Protobuf declares messages out of line from protocol methods.

Flatbuffers and cap'n proto use versioning.