| RFC-0286: Restricting zx.Status in FIDL to Non-Zero Values | |
|---|---|
| Status | Accepted |
| Areas |
|
| Description | Restricts zx.Status in FIDL to non-zero values and adds zx.Result. |
| Issues | |
| Gerrit change | |
| Authors | |
| Reviewers | |
| Date submitted (year-month-day) | 2026-08-10 |
| Date reviewed (year-month-day) | 2026-09-08 |
Problem Statement
FIDL includes zx.Status as a built-in primitive type representing Zircon
status codes (zx_status_t). Under
RFC-0085,
standard Zircon status codes define 0 (ZX_OK) as success and negative
integers as error codes. In addition, applications and subsystems within Fuchsia
use positive 32-bit integers as application-defined status or epitaph codes.
Currently, none of the FIDL bindings validate that zx.Status contains a
non-zero status code on the wire. The new Rust bindings (rust_next) map
zx.Status (or method error types using error zx.Status) to an idiomatic
error type (zx::Status).
While Rust is working towards enforcing non-zero status invariants for
zx::Status, zx.Status in FIDL currently allows 0 (ZX_OK) on the wire in
all contexts. In method response results that map status codes to error types,
receiving 0 (ZX_OK) as an error payload is nonsensical, as it forces
generated code to represent Err(ZX_OK) - an error variant holding a success
status code.
Summary
This RFC proposes two related changes to FIDL's handling of status codes:
- Change
zx.Statusto Non-Zero Status Codes: The existingzx.Statusprimitive type in FIDL is changed to represent non-zero status codes across all FIDL bindings. Sending0(ZX_OK) in azx.Statusfield or payload is prohibited at encoding time and rejected as a decoding error. Positive and negative status codes remain valid. - Add
zx.ResultPrimitive: A new FIDL primitive type,zx.Result, is added to explicitly represent operations that return a Zircon status result (which may be0/ZX_OKfor success or any non-zero status code).
Stakeholders
Facilitator: abarth@google.com
Authors:
- csuter@google.com
Reviewers:
- dkoloski@google.com
- hjfreyer@google.com
Socialization:
- Discussed within the FIDL team.
Requirements
- Cross-Binding Uniformity: All FIDL language bindings should enforce
identical wire validation rules for
zx.Status(rejecting0). - Ergonomic Bindings: There has been an attempt in the new Rust bindings
to use more ergonomic types (such as
zx::Status), and we should keep that goal in mind for this RFC, though there is no requirement to make other existing bindings more ergonomic.
Design
FIDL Primitive Definitions
zx.Status(Changed): Defined as a constrained 32-bit signed integer representing a non-zero Zircon status code.- Valid wire values:
n != 0(negative error codes representingZX_ERR_*, and positive application-defined error codes). - Invalid wire values:
0(ZX_OK). - Encoders must prohibit sending
zx.Status == 0. Decoders across all bindings must reject messages containingzx.Status == 0.
- Valid wire values:
zx.Result(Added): Added as a primitive type representing a Zircon status result.- Valid wire values: any 32-bit integer (
0/ZX_OKfor success, non-zero for error or status codes). - Invalid wire values: none (all 32-bit signed integers are valid).
- Behavior: Behaves identically to the current
zx.Statusrepresentation on the wire today, accepting0(ZX_OK) as success and non-zero values as errors.
- Valid wire values: any 32-bit integer (
Support for Positive Status Codes
Zircon defines standard kernel error codes as negative integers (ZX_ERR_*).
However, certain subsystems and applications in Fuchsia use positive 32-bit
integers as application-defined error or exit codes. For example, component
manager uses positive error codes for component runner termination epitaphs
(such as fuchsia.component/Error).
Because positive status codes are load-bearing for application-specific
epitaphs and error reporting in existing protocols, this RFC leaves positive
status codes supported for both zx.Status (n != 0) and zx.Result.
Restricting status codes to strictly negative integers would require broader
cross-tree migration of application-specific epitaph patterns, and there is no
pressing need to restrict positive values at this time.
Language Binding Mappings
- New Rust Bindings (
rust_next):zx.Statusmaps tozx::Status(non-zero status code).zx.Resultmaps toResult<(), zx::Status>(decoding0toOk(())and non-zerontoErr(zx::Status)).- FIDL method error syntax (
-> (...) error zx.Status) maps error payloads tozx::Status.
- Existing Rust Bindings (
fidl):zx.Statusmaps toi32/zx_status_t(validatingn != 0on decode).zx.Resultmaps toResult<(), zx::Status>(decoding0toOk(())and non-zerontoErr(zx::Status)).
- C++ Bindings:
zx.Statusmaps tozx_status_t(validatingn != 0on decode).zx.Resultmaps tozx::result<>(decoding0tozx::ok()and non-zerontozx::error(status)). Note that unlike Rust,zx::result<>in C++ does not utilize a niche optimization, resulting in an extra 4 bytes of in-memory size compared tozx_status_t.
- Go Bindings:
zx.Statusmaps toint32(validatingn != 0on decode).zx.Resultmaps toint32.
- Python Bindings (
fidlgen_pythonand Fuchsia Controller):zx.Statusmaps toint(validatingn != 0on decode).zx.Resultmaps toint.
- Other Languages (e.g., Dart):
- For language bindings that lack dedicated result or non-zero status
abstractions (or out-of-tree bindings such as Dart), both types fall
back to standard integer representations (
int/int32). zx.Statusmaps to an integer type (validatingn != 0on decode).zx.Resultmaps to an integer type.
- For language bindings that lack dedicated result or non-zero status
abstractions (or out-of-tree bindings such as Dart), both types fall
back to standard integer representations (
Implementation
- FIDL Toolchain Support:
- Add
zx.Resultas a recognized primitive type infidlc. - Add
fidlccompiler lints to guide developers toward the correct type:- Lint against using
zx.Statusin a struct, table, or union (wherezx.Resultis almost certainly intended). - Lint against using
zx.Resultin method error syntax (-> (...) error zx.Result), wherezx.Statusshould be used instead.
- Lint against using
- Update the
abi-compattool to reason about compatibility betweenzx.Statusandzx.Result. - Add GIDL conformance tests verifying that
zx.Resultaccepts both0and non-zero status codes. - Implement
zx.Resultmapping in all binding generators (fidlgen_*), and update out-of-tree generators (such asfidlgen_dart).
- Add
- Schema and Call Site Migration:
- Migrate existing non-error
zx.Statususages tozx.Resultin-tree and across SDK levels for OOT uses.
- Migrate existing non-error
- Wire Validation Enforcement:
- Update
fidlgen_rust_next,fidlgen_cpp,fidlgen_rust,fidlgen_go, andfidlgen_pythonto enforcezx.Status != 0during encoding and decoding, and coordinate corresponding updates to out-of-tree bindings (such asfidlgen_dart). - Add GIDL conformance tests verifying that decoding a message with
zx.Status == 0fails across all language bindings.
- Update
- Documentation:
- Update FIDL language reference and wire format specifications for
zx.Statusandzx.Result.
- Update FIDL language reference and wire format specifications for
Migration Strategy
The migration will proceed in three distinct phases:
Introduce
zx.Result:- Add
zx.Resultto the FIDL toolchain (fidlc, GIDL, and in-tree binding generators), and update out-of-tree generators (such asfidlgen_dart). - Update the
abi-compattool to recognize migrations betweenzx.Statusandzx.Resultas ABI compatible. - At this stage,
zx.Statusremains unchanged on the wire and continues to accept all status values.
- Add
Migrate Uses of
zx.Statustozx.Result:- Migrate existing FIDL fields and method payloads that expect
0(ZX_OK) or status results fromzx.Statustozx.Result. - Changing a FIDL field from
zx.Statustozx.Resultis a source-incompatible change, as bindings will generate structured result types (such asResult<(), zx::Status>in Rust orzx::result<>in C++) instead of raw integer status types. - Unstable APIs: For unstable APIs, making atomic changes is supported since all usage is either in-tree or "at your own risk". We will migrate FIDL definitions and their call sites directly.
- Stable APIs: For stable APIs published in the SDK, we will use
standard FIDL API versioning: we will introduce the change at the
NEXTAPI level. Once the API level with thezx.Resultchange is frozen and downstream repositories migrate to it, we'll retire API levels referencingzx.Statusand the migration will be complete.
- Migrate existing FIDL fields and method payloads that expect
Enforce Non-Zero Validation for
zx.Status:- When we are certain there are no legacy uses of
zx.Statusexpecting or sending0(ZX_OK), we will update all FIDL bindings (both in-tree and out-of-tree generators such asfidlgen_dart) to reject sending and receiving0forzx.Status. - Encoders will prohibit sending
0, and decoders will return a decoding error upon receiving0on the wire forzx.Status.
- When we are certain there are no legacy uses of
Performance
Minimal performance impact. Decoding zx.Status requires a single non-zero
check (raw != 0).
Ergonomics
Changing zx.Status to non-zero status codes allows generated method signatures
in Rust to remain clean and idiomatic:
// Ergonomic Result in new Rust bindings
fn my_method(&self) -> impl Future<Output = Result<Result<MyResponse, zx::Status>, FidlError>>;
For C++, zx.Result maps directly to zx::result<>. Other bindings without
dedicated result types (such as Go, Python, and Dart) use integer
representations.
To prevent API authors from accidentally using the wrong type, fidlc will
provide lints:
- Warn on uses of
zx.Statusinside structs or tables (where fields usually represent operation results that may succeed withZX_OKand should usezx.Result). - Warn on uses of
zx.Resultin method error syntax (-> (...) error zx.Result), where error payloads already represent a failure condition and should usezx.Status.
Backwards Compatibility
- Source Compatibility: Changing FIDL fields from
zx.Statustozx.Resultis source incompatible. Unstable APIs will be migrated directly, while stable APIs will be migrated across SDK API levels as described in the Migration Strategy. - Wire/ABI Compatibility: Changing
zx.Statusto reject0(ZX_OK) on the wire is an ABI breaking change, specifically the kind of breaking change that RFC-0229: FIDL at API Level 2023 commits to avoiding until after 2026-10-11. We will respect RFC-0229 (the rollout to downstream users across API level stabilization and retirement will take longer than that date anyway). Furthermore, as far as we know, no users ever sendErr(OK)and we are willing to take on the risk that we are wrong about that. Enforcing wire validation only in Phase 3 after migrating all valid status result uses tozx.Resultensures no legitimate communications are broken.
Security considerations
Enforcing non-zero status validation at the decoder boundary prevents unexpected
logic errors caused by processing an error payload that contains ZX_OK.
Privacy considerations
No privacy implications.
Testing
Comprehensive unit tests and GIDL conformance tests will be added across all language bindings to verify:
zx.Statusdecodes correctly for non-zero values (both negative and positive) and fails for0.zx.Resultdecodes correctly for0, positive, and negative values.
Documentation
- Update FIDL wire format specification for
zx.Statusandzx.Result. - Update FIDL language reference.
Drawbacks, alternatives, and unknowns
Alternatives Considered
Introduce
zx.Errorand leavezx.Statusunchanged- Description: Introduce a new
zx.Errortype that rejects0(ZX_OK), intended for use in error contexts (e.g.... error zx.Error).zx.Statuswould remain unchanged, continuing to allow0. - Why Dismissed: Using
zx.Statusin error contexts (error zx.Status) is far more common than using status codes in non-error contexts. Withzx.Error, developers would frequently writeerror zx.Statusby habit, forgetting to usezx.Error. Changingzx.Statusto reject0aligns with developer intuition for Rustzx::Status.
- Description: Introduce a new
Context-aware FIDL compiler validation
- Description: Make the FIDL compiler context-aware such that
zx.Statusdisallows0(ZX_OK) on the wire when used in an error context (... error zx.Status), but allows0when used as a field in a struct or table. - Why Dismissed: Violates FIDL's canonical representation design principle. The valid value domain of a FIDL primitive type should be consistent regardless of where it appears in a schema declaration.
- Description: Make the FIDL compiler context-aware such that
Generic FIDL Result syntax
- Description: Extend FIDL syntax to support generic result types
directly in protocol method returns (e.g.,
flexible Frobinate() -> zx.Result<()>). - Why Dismissed: This represents a larger change to FIDL language grammar and syntax, expanding scope beyond resolving status code zero representation.
- Description: Extend FIDL syntax to support generic result types
directly in protocol method returns (e.g.,
Treat
Err(ZX_OK)as a framework-level error inrust_nextonly- Description: Keep
zx.Statusunchanged in FIDL and on the wire. Inrust_next, translate received0in an error payload into a framework-level error (e.g.Err(FidlError::OkError)), while leaving other bindings unchanged. - Why Dismissed: Violates cross-binding uniformity. Having
rust_nextreject messages that other bindings accept creates cross-language incompatibilities and complicates conformance testing.
- Description: Keep