ざっくりZig - 共用型(union)と列挙型(enum)
共用型
共用型(Union type)は複数の選択肢のうち、どれか一つを選んで使用できる型です。この選択肢をフィールド(Field)といいます。共用型自身も1つの型ですが、具体的な値が設定されるときはいずれかのフィールドが持つ型となります。
たとえば、u8
のフィールドu
とbool
のフィールドb
からなる共用型UB
があるとき、各フィールドの型を持つ値はUB{ .u = u8型の値 }
あるいはUB{ .b = bool型の値 }
とします。それぞれの値は変数名.u
もしくは変数名.b
で参照できます。
// UBはu8とboolの共用型
const UB = union {
u: u8,
b: bool,
};
// UB自身も型(@TypeOfは型を返す)
print("@TypeOf(UB) = {}\n", .{@TypeOf(UB)});
// 共用型の値を代入 - uを選択、型はu8
var ub = UB{ .u = 1 };
try stdout.print("ub.u = {}, @TypeOf(ub.u) = {}\n", .{ ub.u, @TypeOf(ub.u) });
// 共用型の値を代入 - bを選択、型はbool
ub = UB{ .b = true };
try stdout.print("ub.b = {}, @TypeOf(ub.b) = {}\n", .{ ub.b, @TypeOf(ub.b) });
// 結果
@TypeOf(UB) = type
ub.u = 1, @TypeOf(ub.u) = u8
ub.b = true, @TypeOf(ub.b) = bool
Packed union
Packed unionはあるフィールドの型で設定した値を同じ共用型に含まれる別のフィールドの値としても参照できる共用型です。ただし、配列など使用できない型があります。
// u4型の値をi4型としても参照できる共用型
const PU = packed union {
u: u4,
i: i4,
};
try stdout.print("@TypeOf(PU) = {}\n", .{@TypeOf(PU)});
const pu = PU{ .u = 15 };
try stdout.print("pu.u = {}, @Typeof(pu.u) = {}\n", .{ pu.u, @TypeOf(pu.u) });
try stdout.print("pu.i = {}, @Typeof(pu.i) = {}\n", .{ pu.i, @TypeOf(pu.i) });
// 結果
@TypeOf(PU) = type
pu.u = 15, @Typeof(pu.u) = u4
pu.i = -1, @Typeof(pu.i) = i4
すこし工夫が必要ですが、1つの値を数ビットごとに分割して参照することもできます。
// 24ビットの値を8ビットごとに分割して参照できる共用型
const UC = packed union {
hex: u24,
color: packed struct { // structは別途紹介予定
blue: u8, // hexの下位8ビット
green: u8, // hexの中間8ビット
red: u8, // hexの上位8ビット
},
};
const uc = UC{ .hex = 0xabcdef };
try stdout.print("uc.hex = 0x{x}\n", .{uc.hex});
try stdout.print("uc.color.red = 0x{x}, uc.color.blue = 0x{x}, uc.color.green = 0x{x}\n",
.{ uc.color.red, uc.color.green, uc.color.blue });
// 結果
uc.hex = 0xabcdef
uc.color.red = 0xab, uc.color.blue = 0xcd, uc.color.green = 0xef
列挙型
列挙型(Enumeration type)は複数の選択肢を持つ型で、選択肢のことをタグ(Tag)といい、列挙型名.タグ名
で表します。荷物の発送済みや受取済みのような状態(ステータス)を表すときなどに利用できます。
たとえばok
とerr
というフィールドを持つE
という列挙型があるとき、タグはE.ok
またはE.err
となります。
const E = enum { ok, err };
const r = if (.....) E.ok else E.err;
タグの数値
タグには個別に整数の数値を設定できます。このときenum (型名) {...}
のように整数の型名を明示します。明示しない場合はデフォルトで0, 1, 2, ...という値が設定されます。この値は@intFromEnum
関数で取得できます。
const EV = enum (u8) { ok = 1, err = 255 };
try stdout.print("EV.ok = {}, EV.err = {}\n",
.{ @intFromEnum(EV.ok), @intFromEnum(EV.err) });
// 結果
EV.ok = 1, EV.err = 255
このほか数値から列挙型のタグを取得する@enumFromInt
関数やタグ名を文字列で取得する@tagName
関数もあります。代入の時に型名を指定するのがポイントです。
var ev: EV = @enumFromInt(1); // EV.ok
try stdout.print("ev = EV.{s}\n", .{@tagName(ev)});
ev = @enumFromInt(255); // EV.err
try stdout.print("ev = EV.{s}\n", .{@tagName(ev)});
// 結果
ev = EV.ok
ev = EV.err
switchとの組み合わせ
列挙型とswitch
とを組み合わせるとタグに応じた振り分けができます。
const r = if (.....) E.ok else E.err;
const es = switch (r) {
.ok => "OK",
.err => "ERR",
};
try stdout.print("es = {s}\n", .{ es });
// 結果 (r == E.okのとき)
es = OK
共用型と列挙型の組み合わせ
共用型と列挙型を組み合わせると共用型でありながらswitch
による振り分けができます。これはタグ化共用型(tagged union)といいます。ただしpacked union
には対応していません。
// union + enum の組み合わせ
const UE = union(enum) {
uint: u8,
bool: bool,
};
// 共用型でありながら@tagName関数を実行できる
var ue = UE{ .uint = 1 };
try stdout.print("ue.u = {}, @tagName(UE.uint) = {s}\n", .{ ue.uint, @tagName(UE.uint) });
ue = UE{ .bool = false };
try stdout.print("ue.bool = {}, @tagName(UE.bool) = {s}\n", .{ ue.bool, @tagName(UE.bool) });
// 共用型でありながらswitchによる振り分けもできる
const uer = switch (ue) {
.uint => "INTEGER",
.bool => "BOOLEAN",
};
try stdout.print("uer = {s}\n", .{uer});
// 結果
ue.u = 1, @tagName(UE.uint) = uint
ue.bool = false, @tagName(UE.bool) = bool
uer = BOOLEAN
union (enum)
の部分は以下のように定義を分離することもできます。
const ET = enum {
uint,
bool,
};
const UE = union(ET) {
uint: u8,
bool: bool,
};
まとめ
- 共用型(Union type)は複数の型を持つフィールドからなる型で、その値の型はいずれか1つのフィールドが持つ型となる
- Packed unionはあるフィールドの型で設定された値を別のフィールドの型でも参照できる共用型
- 列挙型(Enumeration type)は複数のタグを持つ型で、状態(ステータス)を表すときなどに利用できる
- 列挙型のタグには数値が設定される
- 列挙型に関連する関数
関数 引数 取得できる値 @intFromEnum タグ タグに設定された値 @enumFromInt 数値 タグ(代入時に型名の指定が必要) @tagName タグ タグ名の文字列 - 列挙型と
switch
を組み合わせるとタグに応じた振り分けができる - 共用型と列挙型を組み合わせるとタグ化共用型となり、共用型でありながら
switch
による振り分けができる - 別々の共用型と列挙型を組み合わせることもできる
Discussion