RFC-0087:RFC-0050 更新:FIDL 方法参数语法 | |
---|---|
状态 | 已接受 |
领域 |
|
说明 | 通过明确定义顶级类型,修改用于指定请求和响应参数的语法。 |
问题 | |
Gerrit 更改 | |
作者 | |
审核人 | |
提交日期(年-月-日) | 2021-03-09 |
审核日期(年-月-日) | 2021-04-14 |
摘要
在 RFC-0050 之后,FIDL 方法请求或响应的参数为
以 (name1 Type1, name2 Type2)
的形式内嵌指定,即隐式
定义了一个结构体的请求/响应类型,并以参数作为其成员。
此 RFC 提议更改语法,以明确指定顶级类型,
例如(struct { name1 Type1; name2 Type2; })
。用户还可以执行以下操作:
除了结构体之外,还可指定联合体或表的请求/响应类型。这个
RFC 对传输格式没有影响。
另见:
术语
在此 RFC 中,“将类型封装在结构体中”是指接受
并定义一个新结构体,该结构体由该现有类型的单个成员组成
类型。例如,将类型 T
封装在结构体中是指定义一个新类型
type Wrapped = struct { my_t T; }
。这种方法可用于解决
FIDL 语言的某些限制。例如,uint8
不能
可为 null,但结构体可以,因此实际上可以拥有一个可为 null 的
uint8
:首先将其封装到一个结构体中。另外值得注意的是,T
和
Wrapped
具有相同的传输格式。
设计初衷
更改语法以明确指定顶级类型,例如(struct {
name1 Type1; name2 Type2; })
有两大优势,分别是:
- 将 ABI 的含义放在语法的最前面,并遵循 FIDL 的
设计原则。例如,编写
(struct { name1 Type1; })
而不是(name1 Type1)
可明确表明顶层 请求或响应类型是结构体,因此添加或移除 新参数与 ABI 或 API 不兼容。 - 用户可以指定其他顶级类型 需要额外的间接将类型封装在结构体中。 除了通过内嵌定义提高可读性之外,这还允许 为 FIDL 编译器选择合适的名称, 开发者。可取的做法是, 其中,请求参数的可扩展性是重中之重。 在这种情况下,用户可以使用表而不是结构体。
引入此 RFC 的时间以两种方式链接到 RFC-0050:
- 在 RFC 中引入匿名布局后,可以重复使用 用于指定请求/响应类型的语法,而无需提供 而是一个单独的名称
- 此 RFC 中提议的语法更改可归入现有 RFC-0050 所需的实施和迁移,这样便无需 单独迁移。
设计
语法
之前:
protocol Oven {
StartBake(temp Temperature);
// message with no payload
-> OnReady();
};
之后:
protocol Oven {
StartBake(struct { temp Temperature; });
// message with no payload
-> OnReady();
};
所有可能的方法变体如下:
MyMethod(struct { ... }) -> (struct { ... }); // Two-way
MyMethod(struct { ... }) -> (); // Two-way, but response is empty
MyMethod() -> (struct { ... }); // Two-way, but request is empty
MyMethod() -> (); // Two-way, but both request and response are empty
MyMethod() -> (struct { ... }) error zx.status; // Two-way; response leverages error syntax
MyMethod() -> () error zx.status; // Error: must specify a type for success case.
MyMethod(struct { ... }); // One-way
MyMethod(); // One-way, but request is empty
-> MyMethod(struct { ... }) // Event
-> MyMethod(); // Event, but response is empty
更正式地说,
protocol-method = ( attribute-list ) , IDENTIFIER , parameter-list,
( "->" , parameter-list , ( "error" type-constructor ) ) ;
protocol-event = ( attribute-list ) , "->" , IDENTIFIER , parameter-list ;
parameter-list = "(" , ( parameter ( "," , parameter )+ ) , ")" ;
parameter = ( attribute-list ) , type-constructor , IDENTIFIER ;
会变为:
protocol-method = ( attribute-list ) , IDENTIFIER , method-params
( "->" , method-params ( "error" type-constructor ) ) ;
protocol-event = ( attribute-list ) , "->" , IDENTIFIER , method-params;
method-params = "(" , type-constructor , ")"
type
如 RFC-0050 中所定义,即它要么是
对现有类型(如 MyType<args>:constraints
)的引用,或匿名
布局,例如struct { name Type; }:constraints
。
尽管该语法允许将任意类型用作请求和 则 FIDL 编译器会验证顶级类型是否为 结构体、联合或表。
如 RFC-0050 中所指定的,编译器会为 任何内嵌的顶级请求或响应类型, 而不是内联样式(例如,为了改进 可读性)。例如, 可能从:
protocol MyProtocol {
Foo(struct {
// input param
input uint32;
}) -> (struct {
// output param
output uint32;
});
};
更改为:
type FooRequest = struct {
// input param
input uint32;
};
type FooResponse = struct {
// output param
output uint32;
};
protocol MyProtocol {
Foo(FooRequest) -> (FooResponse);
}
对 API 或 ABI 没有影响(假设 FooRequest
和 FooResponse
是
编译器预留的名称)。
绑定
绑定的主要影响是,在某些情况下 对应于一组请求/响应参数的映射是否扁平化 具体取决于请求或响应的顶级类型。目前位于该区域 同时存在扁平化和非扁平化生成的 API 实例。
在这里,“扁平化”的API 是指绑定中使用请求和
响应参数,从而避免将其封装在
结构体。例如,与
FIDL 方法 GetName(struct { id uint32; }) -> (struct { name string; })
为 void GetName(uint32_t id, GetNameCallback callback)
。通过
FIDL 中指定的形参直接对应于 C++ 中的函数形参。
“非扁平化”的API 是指顶级类型本身为
提供给用户的应用在前面的示例中,大致如下所示:
void GetName(GetNameRequest req, GetNameCallback callback)
。GetNameRequest
对应于顶级结构体类型,并且只有一个 uint32
id
字段。
在当前语法中,所有顶级请求或响应类型均为
隐式结构体,展平参数,使其直接对应
添加到函数签名的参数中是可以的,因为
结构体成员无论如何都与 ABI 和 API 不兼容(即
生成的绑定不会为保证增加额外的限制
(由 FIDL 提供)。但对诸如 Google 地图之类的表和联合,
支持添加和移除成员因此,在某些情况下
如果语言
构造表示方法(在此示例中为位置定位),
函数参数),其限制比顶层提供的函数参数更严格。
级别类型(例如表或联合)。仍以上面的示例为例,
意味着 GetName(table { 1: id uint32; }) -> (table { 1: name string;})
将需要生成 void
GetName(GetNameRequest req, GetNameCallback callback)
形式的非扁平化签名,以保留
表的顶层类型提供的兼容性保证。
对于生成的函数或方法,Dart 等某些编程语言可以 通过在发送文本中使用具名实参来解决此问题 但这在接收端仍与源不兼容,因为 因此必须相应地向接收方法添加一个新参数。
总之,对结构体使用扁平化 API 的绑定代码可能需要
如果顶级类型是表或
并集。如果绑定当前已生成非扁平化 API,则
例如 MyProtocol::MyRequest
或 MyProtocol::MyResponse
( LLCPP),则存在
顶级结构体的 API 之间就没有这种区别了
请求/响应、顶级联合或表请求/响应。
JSON IR
maybe_request
和 maybe_response
的 JSON 条目将会更改。旧版
架构图:
"maybe_request": {
"description": "Optional list of interface method request parameters",
"type": "array",
"items": {
"$ref": "#/definitions/interface-method-parameter"
}
},
会变为:
"maybe_request_payload": {
"description": "Optional type of the request",
"$ref": "#/definitions/compound-identifier"
},
(以及 maybe_response
的相同更改)
已存在与此形状匹配的"maybe_request_payload"
字段,但
尚未在 JSON IR 中指定作为更改我们的
消息的表示形式”。在实践中,JSON IR 将更改为
对于此 RFC,您需要完成从 "maybe_request"
到
"maybe_request_payload"
(请参阅实现)。
实现
此 RFC 的实现分为两部分:第一部分是纯粹的 对所有现有文件进行修饰性更改,以符合新语法 第二部分是将 FIDL 编译器和 允许将表和联合作为顶级类型。语法更改将 作为更广泛的 RFC-0050 FIDL 语法转换的一部分进行实现,但支持 可以延迟,以免成为 。以 “new”语法将符合此 RFC 规范, 并且正式的 FIDL 语法将会更新,以反映其设计, 与 RFC-0050 的其余部分相同。
在现有绑定中,在某些情况下启用顶级类型的 请求和响应的表和联合体并不需要大量 除了处理新的 JSON IR 格式之外,还会进行一些更改。如果情况并非如此,即 绑定中的编码和解码代码依赖于 顶级类型是结构体,则有两种可能的方法:
- 第一种方法是先将所有表和联合封装到一个结构体中,然后再 编码和解码。这样可能没有吸引力, 并增加编码和解码的额外步骤。
- 另一种方法是修改编码/解码代码, 支持非结构体的输入。目前至少有一些代码 它假设输入始终是结构体(例如, LLCPP 中的 trait 只为结构体生成,并且请求和响应 Rust 中的编码通过元组而非结构体进行),但 但目前还无法做出这种假设。 决定权衡利弊,最终在两者之间做出决定 方法。除了方法调用之外,后一种方法可能还有其他好处, 例如,无需将类型封装在 数据使用场景。
JSON IR
在 https://fxbug.dev/42157011 中,
已在进行迁移,以便将"maybe_request"
和
"maybe_response"
字段,以便对
请求和响应类型仅出现在 FIDL 后端中。这项工作已暂停
但会恢复(以便实施此 RFC)。
目前,C++ 后端是唯一一个使用
"maybe_request"
和 "maybe_response"
(不过,其他库使用的是 JSON
IR(例如 FIDL 编解码器)也需要更新)。
安全和隐私权
此 RFC 不会修改 FIDL 线路格式,因此对安全性没有影响 和隐私权。
测试
将使用现有基础架构来测试此 RFC:单元测试、黄金测试 和集成测试(例如 FIDL 兼容性测试)。
文档
启用此功能后,应添加文档(包括示例) 来描述新功能
缺点、替代方案和未知
语法
此 RFC 中建议的语法提供了使用结构体作为 顶级类型更详细,因为需要明确指定。 替代方法可能包括在常见情况中引入语法糖(例如 保留结构体的当前语法,并对结构体使用新的显式语法 表和联合),但任何情况下清晰易读的可读性 比降低详细程度更重要。
另一方面可能被认为没有吸引力的语法是冗余
(放在括号中):(struct { ... })
,这也是我们在 Google Cloud 中
FTP-058。这里要实现一致性,即保留大括号。
可确保请求内部类型的语法与
。FTP-058 中采用了
这种情况下,也可以用空格替换多余的大括号(例如 MyMethod struct { ... } ->
union { ... };
)。在 FIDL 文本中,这一功能更强大。
风格与建议的其他部分一致,
但在这里,它与
FIDL 的语法更加基于 C 系列/Go。
最后,建议使用的另一种替代方法是
用于指定与方法参数语法保持一致的类型:结构体应为
使用类似语法 type MyStruct = (foo Foo, bar
Bar);
的元组/记录指定。这样,当我们返回顶部时,
level 类型是一种结构体,因为它省略了一组额外的圆括号 MyMethod(foo
Foo, bar Bar);
。作为完整的示例,此建议应如下所示:
// Declare a struct with two fields foo, bar.
type SomeStruct = (foo Foo, bar Bar);
protocol MyProtocol {
// Declare a method with two request parameters.
// The two parameters are stored in a struct.
MyStructMethod(foo Foo, bar Bar);
// Declare a method with two optional parameters.
// The two parameters are stored in a table.
MyTableMethod table { 1: foo Foo, 2: bar Bar };
};
绑定
如设计中所述,在许多情况下,绑定不能展平或 在为表和联合生成的 API 中内嵌顶级类型的成员 与对结构体相同,避免引入额外的 兼容性限制。关于何时拥有方法的顶级类型成员的规则 他们需要依靠文档或生成的代码检查 确定每种 FIDL 协议方法生成的 API 是什么。这个 比起当前情况,绑定 API 会导致 始终内嵌/扁平化顶级类型成员。
从理论上讲,可以通过永不展平请求来提供一致的 API 或响应参数,但这在实践中被视为不可行, 需要迁移依赖于此 API 的所有用户代码实例( 是与 FIDL 方法交互的大部分用户代码)。
先前艺术作品与参考文献
此 RFC 中建议的语法更接近 gRPC 中使用的语法,其中 请求和响应使用单个 protobuf 消息指定。
之前 ctiller@google.com 提出了与此 RFC 类似的建议,
允许序数语法(例如 MyMethod(1: foo Foo; 2: bar Bar)
)暗示
顶层类型是表,而不是结构体。它们的主要区别在于
除了表和结构体之外,此 RFC 还支持顶级联合,
鉴于联合还使用
序数。