{% set rfcid = “RFC-0057” %} {% include “docs/contribute/governance/rfcs/_common/_rfc_header.md” %}
Note: Formerly known as FTP-057.
“Look Ma, no hand(le)s!”
We propose to disallow handles in FIDL type declarations by default, and add a new keyword resource
[^1] to mark types that are allowed to contain handles or other resource types. Adding or removing a resource
modifier MAY be a source-breaking change.
A distinctive feature of FIDL is its support for Zircon handles. Handles are 32-bit integers in memory, but they are treated specially: they must be moved rather than copied, and they must be closed to avoid leaking resources. This special treatment leads to problems when considering features that only make sense for plain data without handles. Although FIDL bindings can conditionally enable code based on the presence of handles, doing so is undesirable because it breaks evolvability guarantees. For example, adding a field to a table is normally safe, but adding a handle field would become source-breaking—not only for that table, but for all types transitively containing it. This pushes bindings to be conservative, always assuming that types might contain handles even if the library author never intends to add them.
The need to accommodate handles has led to compromise:
MaxHandles
attribute, but this was a temporary solution because the attribute only applies to the outermost type, not to all types transitively reachable from it.Clone
trait. (Properly cloning a handle requires invoking the zx_handle_duplicate syscall, which can fail.)All these cases can be handled in a safer, more ergonomic way if we require library authors to indicate whether a type may contain handles, and if changing the indication is expected to be source-breaking.
A FIDL type is either a value type or a resource type. Resource types include:
handle
and handle<H> where H is a handle subtypeAll other types are value types.
When the resource
modifier is used correctly, value types never contain handles, whereas resource types may (now, or in the future) contain handles.
A new modifier resource
can be applied to struct, table, and union declarations.
Without resource
, the declaration is not allowed to contain resource types. The FIDL compiler must verify this. It only needs to check direct fields: if A contains B, neither are marked as resources, and B contains a handle, then compilation will fail due to B and there is no need for a separate error message about A transitively containing a handle.
With resource
, the declaration is allowed to contain resource types. The new type it declares is also considered a resource type, even if it does not contain resources.
In principle the language could allow resource
on newtype declarations RFC-0052. However, there is no practical use for a resource newtype wrapping a value type, so instead newtypes implicitly inherit value/resource status from the type they wrap.
This proposal modifies one rule in the FIDL grammar:
declaration-modifiers = "strict" | "resource" ;
This proposal adds a key "resource"
with a boolean value to all objects in the "struct_declarations"
, "table_declarations"
, and "union_declarations"
arrays.
Note that this key is not redundant with "max_handles"
. Value types must have max_handles
set to zero, but resource types can have any number of max_handles
, as it reflects the actual contents of the declaration (as opposed to the library author's intent to allow handles).
This proposal does not include specific changes to the bindings. However, it enables FIDL bindings authors (including the FIDL team) to address the issues discussed in Motivation. Here are some examples made possible by this FTP, but not required in accepting it:
Clone()
method on value/resource types, to emphasize that only resource cloning can fail.&T
and resource-type arguments as T
, instead of using &mut T
for both and only mutating the resource types.The API rubric should provide guidance on when to use resource
. Some simple cases:
resource
, since structs are not designed to be extended (adding a handle later would in most cases break ABI).resource
, since the strictness already signals that modifying its fields is a source-breaking change.It should also address the case of flexible tables and unions that do not initially have handles. For instance, we might want to recommend erring on one side or the other depending on the purpose of the library, how widely it will be used, the anticipated cost of source-breaking changes in the languages being used, and other factors.
The high-level implementation steps include:
resource
keyword in fidlc.resource
(more on this in Unknowns.resource
flag in the JSON IR and expose it in fidlgen.This proposal makes FIDL more complex since it introduces a new concept. Unlike other FIDL constructs such as “struct” and “protocol,” new users are unlikely to guess what “resource” means, so they will need to learn from documentation.
It is arguable whether this proposal makes the FIDL language more or less ergonomic. It helps by drawing attention to declarations that contain handles, especially if the actual handle values are hidden in nested structures. Anyone skimming a library will immediately see that a structure carries handles, not just data. On the other hand, it might feel less ergonomic to worry about whether to use resource
and to type the keyword. Changing one declaration from value to resource could have a painful cascading effect where many types must become resources (though this can be seen as a good thing, since otherwise it would show up as source breakage).
The increased complexity is justified by improvements in FIDL bindings. With the freedom to offer different APIs for value types and resource types, the bindings can be made safer and more ergonomic. For examples of these improvements, see Bindings.
The following tasks need to be done:
resource
as appropriate.resource
modifier.resource
in the FIDL Tutorial. There should be a short note explaining all the modifiers (so, strict
and resource
).This proposal has no impact on ABI compatibility.
Amendment (Jul 2021). During implementation, we discovered an edge case in this proposal's interaction with RFC-0033: Handling of unknown fields and strictness. Some bindings store unknown members when decoding tables and flexible unions; this is not possible for value types if the unknown member contains handles, so decoding must fail in this case. See the compatibility guide for more details.
Amendment (Oct 2021). After RFC-0137: Discard unknown data in FIDL, bindings no longer store unknown data, so there is no more edge case. The value/resource distinction thus has no impact on ABI compatibility.
Adding or removing a resource
modifier is neither source-compatible nor transitionable,[^2] in the sense of RFC-0024. The bindings are explicitly allowed to generate incompatible APIs for two types that differ only in the presence of the modifier, and it may in fact be impossible to write code that compiles before and after adding/removing the modifier. Library authors wishing to transition to/from resource
in a source-compatible manner must create new types and methods instead of changing existing ones.
Once bindings authors start taking advantage of the value/resource distinction, we will revisit this decision. It might be worthwhile to require a transitionable path (perhaps using an intermediate stage with the [Transitional]
attribute). At the outset, this is unclear: it might be too restrictive, undermining potential API improvements this proposal is meant to enable.
This proposal has negligible impact on build performance: the FIDL compiler will do slightly more work to parse the new keyword and validate its use. It has no direct impact on IPC performance. It might enable a small improvement in some languages if bindings use the value/resource distinction to create APIs that discourage unnecessary copies. For example, there should be no need to clone a value-type object in order to send it multiple times.
This proposal does not directly affect security, but it enables bindings to provide safer APIs. For example, C++ could force error handling on Clone()
for resource types with [[nodiscard]]
, or Rust could take resource-type method arguments by move to prevent accidental use of the mutated object afterwards. These kinds of changes could prevent bugs, including security bugs.
This feature will be tested in the following ways:
resource
(or not) fails to conform to the definition of a resource type (or value type).resource
.This proposal introduces a new keyword, which makes the language more complex. Having too many keywords could be a problem; “strict resource union” is a bit of a mouthful.
This proposal weakens FIDL evolvability guarantees in two ways:
resource
in anticipation of needing to add a handle).There are two main alternatives to this proposal:
value
keyword to disallow resource types in a declaration.The Motivation and Ergonomics sections argue against doing nothing. For the other alternative, experience has shown that most messages do not contain handles, and that passing handles in protocols requires care and upfront planning. In other words, value types are the common case, and the ability to add handles as an afterthought might not be as useful as it seems. This suggests that not allowing handles is the better default.
This proposal mainly benefits end-developers using FIDL bindings, whereas its drawbacks apply to library authors who design the APIs. This tradeoff is in keeping with the Fuchsia API Council Charter, which prioritizes end-developers over API designers and implementers.
One more alternative has been suggested: handles as references. Instead of banning handles from value types, it would resolve the value/resource issues by representing handles as references. Cloning a structure containing a handle would just make another reference to the same handle. This could be accomplished using shared_ptr
in C++, and it could greatly simplify things without needing to add the resource
keyword. However, it has its challenges:
Several open questions remain for this proposal:
resource
would be safe, but not reflect library authors' intentions. Marking only the bare minimum (i.e., types that contain handles) would work, but might be too aggressive in assuming that anything without handles was intended to never contain any.Pair<A, B>
type, it should logically be a resource type if A
or B
is a resource type, rather than having to annotate Pair
itself. Are there other cases where it is preferable to derive whether a type is a resource?The goal of this proposal is to allow source-breaking changes to occur when changing a type's value/resource status. RFC-0024 is relevant to this goal, since it established the source-compatibility standard for FIDL. It also touched on the issue of handles making it difficult to use the Clone
trait in Rust, which this proposal solves.
We are not aware of other IPC systems addressing this exact issue (distinguishing types that may contain handles, or system resources). However, the concept of annotating types in a way that “infects” all use-sites is common in programming languages. For example, async functions in JavaScript, Python, and Rust have this behaviour, as well as the IO monad in Haskell.
[^1]: An earlier version of this proposal instead called the keyword entity
.
[^2]: An earlier version of this proposal required the change to be transitionable.