ざっくりZig - 共用型(union)と列挙型(enum)

2024/04/10に公開

共用型

共用型(Union type)は複数の選択肢のうち、どれか一つを選んで使用できる型です。この選択肢をフィールド(Field)といいます。共用型自身も1つの型ですが、具体的な値が設定されるときはいずれかのフィールドが持つ型となります。

たとえば、u8のフィールドuboolのフィールドbからなる共用型UBがあるとき、各フィールドの型を持つ値はUB{ .u = u8型の値 }あるいはUB{ .b = bool型の値 }とします。それぞれの値は変数名.uもしくは変数名.bで参照できます。

共用型(Union type)
// 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はあるフィールドの型で設定した値を同じ共用型に含まれる別のフィールドの値としても参照できる共用型です。ただし、配列など使用できない型があります。

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)といい、列挙型名.タグ名で表します。荷物の発送済みや受取済みのような状態(ステータス)を表すときなどに利用できます。

たとえばokerrというフィールドを持つ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とを組み合わせるとタグに応じた振り分けができます。

列挙型と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による振り分けができる
  • 別々の共用型と列挙型を組み合わせることもできる

関数 >
< 配列とタプル
ざっくりZig 一覧

Discussion