Go 绑定

假设有 library 声明:

library fuchsia.examples;

绑定代码会生成到 examples Go 软件包中,该软件包将通过获取 FIDL 库名称的最后一个组成部分来获取。

可使用以下路径导入软件包:

import "fidl/fuchsia/examples"

常量

常量作为 const 块生成。例如,以下常量:

const BOARD_SIZE uint8 = 9;
const NAME string = "Tic-Tac-Toe";

生成为:

const (
  BoardSize uint8  = 9
  Name      string = "Tic-Tac-Toe"
)

内置类型中概述了 FIDL 基元类型和 Go 类型之间的对应关系。

字段

本部分介绍 FIDL 工具链如何将 FIDL 类型转换为 Go 中的原生类型。这些类型可以在聚合类型中显示为成员,或显示为协议方法的参数。

内置类型

根据下表,FIDL 类型转换为 Go 类型。

FIDL 类型 围棋类型
bool bool
int8 int8
int16 int16
int32 int32
int64 int64
uint8 uint8
uint16 uint16
uint32 uint32
uint64 uint64
float32 float32
float64 float64
array<T, N> [N]T
vector<T>:N []T
vector<T>:N? *[]T
string string
string:optional *string
server_end:P 生成的服务器端类型 PInterfaceRequest,请参阅协议
client_end:P 生成的客户端端类型 PInterface,请参阅协议
zx.Handle:S,zx.Handle:<S, optional> 如果 Go 运行时支持,则使用等效的句柄类型(例如 zx.VMOzx.Channelzx.Event)。否则,使用 zx.Handle
zx.Handle,zx.Handle:optional zx.Handle

用户定义的类型

在 Go 中,用户定义的类型(位、枚举、常量、结构体、联合或表)使用生成的类型来引用(请参阅类型定义)。用户定义的类型 T 的可为 null 版本,是通过使用指向生成的类型 *T 的指针来引用的。

类型定义

请注意,在本部分中,生成的 Go 代码示例并未代表 FIDL 生成的确切代码。例如,生成的结构体可能包含无法通过反射检查的非导出字段。

块数

根据 bits 定义:

const BOARD_SIZE uint8 = 9;
const NAME string = "Tic-Tac-Toe";

FIDL 会为基础类型(如果未指定,则为 uint32)生成一个类型别名,并为每个位成员生成常量:

type FileMode uint16

const (
  FileModeRead    FileMode = 1
  FileModeWrite   FileMode = 2
  FileModeExecute FileMode = 4
  FileMode_Mask   FileMode = 7
)

FileMode_Mask 值是一个位掩码,其中包含 FIDL 架构中定义的每个位成员。

此外,它还为 FileMode 提供了以下方法:

  • func (x FileMode) String() string:返回人类可读的位字符串。
  • func (x FileMode) GetUnknownBits() uint64:以 uint64 的形式返回仅包含此位值中的未知成员的值。对于严格位,始终返回 0。
  • func (x FileMode) HasUnknownBits() bool:返回此值是否包含任何未知位。对于严格位,始终返回 false
  • func (x FileMode) InvertBits() FileMode:反转所有已知位。所有未知位都设置为 false。
  • func (x FileMode) ClearBits(mask FileMode) FileMode:修改位字段,以便取消设置掩码中设置的所有位。
  • func (x FileMode) HasBits(mask FileMode) bool:验证是否已设置掩码中的所有翻转位。

用法示例:

func ExampleFileMode() {
	fmt.Println(examples.FileModeRead.String())
	fmt.Println(examples.FileModeWrite | examples.FileModeExecute)

	// Output:
	// Read
	// Write|Execute
}

枚举

根据 enum 定义:

type LocationType = strict enum {
    MUSEUM = 1;
    AIRPORT = 2;
    RESTAURANT = 3;
};

FIDL 会为基础类型(如果未指定,则为 uint32)生成一个类型别名,并为每个枚举成员生成常量:

type LocationType uint32

const (
  LocationTypeMuseum     LocationType = 1
  LocationTypeAirport    LocationType = 2
  LocationTypeRestaurant LocationType = 3
)

如果 LocationType弹性的,则它也将具有未知的占位符成员:

    LocationType_Unknown LocationType = 0x7fffffff

如果枚举具有使用 [Unknown] 属性标记的成员,则生成的未知变量将与标记的未知成员具有相同的值。

LocationType 提供了以下方法:

  • func (x LocationType) IsUnknown() bool:返回此枚举值是否未知。对于严格枚举,始终返回 false
  • func (x LocationType) String() string:返回直观易懂的枚举字符串。

用法示例:

func ExampleLocationType() {
	fmt.Println(examples.LocationTypeMuseum.String())
	// Output: Museum
}

结构体

根据 struct 声明:

type Color = struct {
    id uint32;
    @allow_deprecated_struct_defaults
    name string:MAX_STRING_LENGTH = "red";
};

FIDL 工具链会生成一个包含匹配字段的 Color 结构体:

type Color struct {
  Id   uint32
  Name string
}

Go 绑定目前不支持结构体字段的默认值。

用法示例:

func ExampleColor() {
	red := examples.Color{Id: 1, Name: "ruby"}
	fmt.Println(red.Id)
	fmt.Println(red.Name)

	// Output:
	// 1
	// ruby
}

联合体

根据联合定义:

type JsonValue = strict union {
    1: reserved;
    2: int_value int32;
    3: string_value string:MAX_STRING_LENGTH;
};

FIDL 会生成别名以及表示联合标记的相关常量:

type I_jsonValueTag uint64

const (
  JsonValueIntValue     = 2
  JsonValueStringValue  = 3
)

以及一个 JsonValue 结构体(其中包含标记和联合的每个变体的字段):

type JsonValue struct {
  I_jsonValueTag
  IntValue       int32
  StringValue    string
}

JsonValue 提供了以下方法:

  • func (_m *JsonValue) Which() I_jsonValueTag:返回联合标记。
  • func (_m *JsonValue) SetIntValue(intValue int32)func (_m *JsonValue) SetStringValue(stringValue string):将联合设置为包含特定变体,并相应地更新标记。

如果 JsonValue 为灵活,则它还可以使用以下方法:

  • func (_m *JsonValue) GetUnknownData() fidl.UnknownData:返回未知数据的原始字节和句柄。句柄切片按遍历顺序返回,如果联合是资源类型,则保证为空。

FIDL 工具链还会生成用于构建 JsonValue 实例的工厂函数:

  • func JsonValueWithIntValue(intValue int32) JsonValue
  • func JsonValueWithStringValue(stringValue string) JsonValue

用法示例:

func ExampleJsonValue() {
	val := examples.JsonValueWithStringValue("hi")
	fmt.Println(val.Which() == examples.JsonValueStringValue)
	fmt.Println(val.StringValue)
	val.SetIntValue(1)
	fmt.Println(val.Which() == examples.JsonValueIntValue)
	fmt.Println(val.IntValue)

	// Output:
	// true
	// hi
	// true
	// 1
}

灵活联合和未知变体

灵活联合在生成的标记类中有一个额外的变体:

const (
  JsonValue_unknownData = 0
  // other tags omitted...
)

当包含具有未知变体的联合体的 FIDL 消息被解码到 JsonValue 中时,.Which() 将返回 JsonValue_unknownData

对具有未知变体的并集进行编码,将未知数据和原始序数重新写入线上。

对未知变体进行解码时,严格并集会失败。 在解码具有句柄的未知变体时,属于类型的灵活并集会失败。

表格

根据以下定义:

type User = table {
    1: reserved;
    2: age uint8;
    3: name string:MAX_STRING_LENGTH;
};

FIDL 工具链会生成一个 User 结构体,其中包含每个字段的在线状态字段:

type User struct {
  Age         uint8
  AgePresent  bool
  Name        string
  NamePresent bool
}

User 提供了以下方法:

  • func (u *User) HasAge() boolfunc (u *User) HasName() bool:检查是否存在字段。
  • func (u *User) SetAge(age uint8)func (u *User) SetName(name string):字段 setter。
  • func (u *User) GetAge() uint8func (u *User) GetName() string:字段 getter。
  • func (u *User) GetAgeWithDefault(_default uint8) uint8func (u *User) GetNameWithDefault(_default string) string:如果不存在,则返回指定默认值的字段 getter。
  • func (u *User) ClearAge()func (u *User) ClearName():清除字段的存在。
  • func (u *User) HasUnknownData() bool:检查是否存在任何未知字段。
  • func (u *User) GetUnknownData() map[uint64]fidl.UnknownData:返回从序数到字节的映射,以及任何未知字段的句柄。句柄列表按遍历顺序返回,如果表是类型,则保证为空。

用法示例:

func ExampleUser() {
	var user examples.User
	fmt.Println(user.HasAge(), user.HasName())
	user.SetAge(30)
	user.SetName("John")
	fmt.Println(user.GetAge(), user.GetName())
	user.ClearAge()
	user.ClearName()
	fmt.Println(user.HasAge(), user.HasName())
	fmt.Println(user.GetNameWithDefault("Unknown"))

	// Output:
	// false false
	// 30 John
	// false false
	// Unknown
}

内嵌布局

生成的 Go 代码会将 fidlc 预留的名称用于内嵌布局。

协议

根据 protocol

closed protocol TicTacToe {
    strict StartGame(struct {
        start_first bool;
    });
    strict MakeMove(struct {
        row uint8;
        col uint8;
    }) -> (struct {
        success bool;
        new_state box<GameState>;
    });
    strict -> OnOpponentMove(struct {
        new_state GameState;
    });
};

FIDL 会生成一个 TicTacToeWithCtx 接口,客户端在向服务器代理调用时使用它,并由服务器使用该接口来实现协议:

type TicTacToeWithCtx interface {
  StartGame(ctx_ fidl.Context, startFirst bool) error
  MakeMove(ctx_ fidl.Context, row uint8, col uint8) (bool, *GameState, error)
}

每个方法都将 Context 作为第一个参数,后跟请求参数。触发和忘记方法返回 error,双向方法返回响应参数,后跟 error

TicTacToe 协议交互的入口点具有以下函数:

func NewTicTacToeWithCtxInterfaceRequest() (TicTacToeWithCtxInterfaceRequest, *TicTacToeWithCtxInterface, error)

此函数会创建一个通道,并返回一个绑定到该通道一端的 TicTacToeWithCtxInterfaceRequest(表示服务器端),以及一个绑定到另一端的 TicTacToeWithCtxInterface(表示客户端)。如需了解这些内容,请参阅客户端服务器部分。

客户端

用于通过 TicTacToe 协议进行通信的通道的客户端是 TicTacToeWithCtxInterface。它实现了协议中所述的 TicTacToeWithCtx 接口以及用于处理事件的方法。请注意,在此实现中,双向方法调用是同步的,并且会在收到响应之前一直阻塞。

您可以在 //examples/fidl/go/client 中找到 Go FIDL 客户端的示例。

服务器

为此 FIDL 协议实现服务器时,需要提供 TicTacToeWithCtx 接口的具体实现。

这些绑定会生成一个 TicTacToeWithCtxInterfaceRequest 类型,用于表示通过 TicTacToe 协议进行通信的通道的服务器端。它提供了以下方法:

  • func (c EchoWithCtxInterfaceRequest) ToChannel() zx.Channel:将接口请求转换回非类型化通道。

您可以在 //examples/fidl/go/server 中找到 Go FIDL 服务器的示例。

事件

客户端

TicTacToeWithCtxInterface 提供了处理事件的方法:

  • func (p *TicTacToeWithCtxInterface) ExpectOnOpponentMove(ctx_ fidl.Context) (GameState, error)OnOppponentMove 的事件处理脚本,它接受一个 Context 并返回事件参数。

调用任何事件处理脚本方法时,系统都会读取下一个缓冲事件或块,直到接收到下一个缓冲事件或块。调用方应及时排空事件缓冲区,以免 FIDL 绑定中出现无限缓冲。如果下一个事件与调用的方法匹配,则返回其参数。否则,系统会返回错误。客户端需确保所接收事件的顺序与所处理事件的顺序一致。

服务器

服务器可以使用 TicTacToeEventProxy 发送事件,为协议中的每个事件提供方法:

  • func (p *TicTacToeEventProxy) OnOpponentMove(newState GameState) error:发送 OnOpponentMove 事件。

创建 TicTacToeEventProxy 需要访问客户端的通道。Go 服务器示例展示了如何在服务器端获取 EventProxy

成果

Go 绑定对具有错误类型的方法没有任何特殊处理。

假设该方法具有错误类型:

protocol TicTacToe {
    MakeMove(struct {
      row uint8;
      col uint8;
    }) -> (struct {
      new_state GameState;
    }) error MoveError;
};

TicTacToeWithCtx 接口MakeMove 的方法签名为:

MakeMove(ctx_ fidl.Context, row uint8, col uint8) (TicTacToeMakeMoveResult, error)

TicTacToeMakeMoveResult 是作为具有以下两个变体的并集生成的:ErrMoveError)和 ResponseTicTacToeMakeMoveResponse)。

TicTacToeMakeMoveResponse 会以结构体的形式生成,其字段与成功响应的参数相对应。在本例中,它包含一个类型为 GameStateNewState 字段。

协议构成

FIDL 没有继承的概念,并会生成上述所有组合协议的完整代码。换句话说,

protocol A {
    Foo();
};

protocol B {
    compose A;
    Bar();
};

提供的 API 与为以下内容生成的代码相同:

protocol A {
    Foo();
};

protocol B {
    Foo();
    Bar();
};

除了方法序数外,生成的代码完全相同。

协议和方法属性

过渡风格

为了在 Go 中支持 @transitional 属性,FIDL 会生成一个 TicTacToeWithCtxTransitionalBase 类型,为标记为 @transitional 的每个方法提供默认实现。嵌入 TicTacToeWithCtxTransitionalBase 的服务器实现将继续构建新的过渡方法。

可检测到

标记为 @discoverable 时,生成的 InterfaceRequest 类型(在此示例中为 TicTacToeWithCtxInterfaceRequest)会实现 fidl.ServiceRequest,从而允许在服务发现中使用服务器端。

此外,FIDL 还会生成包含协议名称的 TicTacToeName 常量。