Zero Bit Types

Zero Bit Types (0비트 타입)

어떤 타입은 값을 표현하는 데 비트가 전혀 필요 없어요. Zig에서는 이런 타입들에 대해 @sizeOf가 0이 됩니다. 이런 걸 '0비트 타입(Zero Bit Type)'이라고 불러요. 어떤 타입들이 그럴까요? 다음 목록을 보면 감이 올 거예요.

  • void
  • 정수 타입 u0
  • 길이가 0이거나, 원소 타입이 0비트 타입인 배열(Array)벡터(Vector)
  • 태그가 하나뿐인 enum
  • 모든 필드가 0비트 타입인 struct
  • 필드가 하나뿐이고 그 필드가 0비트 타입인 union

이런 타입들은 가질 수 있는 값이 단 하나뿐이라서, 표현하는 데 0비트면 충분해요. 그래서 이 타입을 다루는 코드는 최종 생성된 기계어에 아예 포함되지 않아요. 아래 예제를 볼게요.

export fn entry() void {
    var x: void = {};
    var y: void = {};
    x = y;
    y = x;
}

이것이 기계어로 변환되면, entry의 본문 안에는 Debug 모드에서조차 아무 코드도 생성되지 않아요. 예를 들어 x86_64에서 이렇게 됩니다.

0000000000000010 <entry>:
  10:	55                  	push   %rbp
  11:	48 89 e5            	mov    %rsp,%rbp
  14:	5d                  	pop    %rbp
  15:	c3                  	retq   

이 어셈블리 명령들에는 void 값과 연관된 코드가 하나도 없어요. 그저 함수 호출의 프롤로그(prologue)와 에필로그(epilogue)만 수행할 뿐이죠.

출처: Zig Documentation

본문

void

void제네릭 타입을 인스턴스화할 때 특히 유용해요. 예를 들어 Map(Key, Value)가 있다면, Value 자리에 void를 넘겨서 Set처럼 만들 수 있어요.

const std = @import("std");
const expect = std.testing.expect;

test "turn HashMap into a set with void" {
    var map = std.AutoHashMap(i32, void).init(std.testing.allocator);
    defer map.deinit();

    try map.put(1, {});
    try map.put(2, {});

    try expect(map.contains(2));
    try expect(!map.contains(3));

    _ = map.remove(2);
    try expect(!map.contains(2));
}
$ zig test test_void_in_hashmap.zig
1/1 test_void_in_hashmap.test.turn HashMap into a set with void...OK
All 1 tests passed.

여기서 중요한 건, 해시맵 값에 더미 값을 쓰는 것과는 다르다는 점이에요. 값의 타입을 void로 두면 해시맵 엔트리 타입에 값 필드가 생기지 않아서, 해시맵이 차지하는 공간이 줄어들어요. 게다가 값을 저장하고 불러오는 코드도 모두 삭제돼요 — 위에서 본 것처럼요.

한 가지 주의할 점은, voidanyopaque와는 구별되는 타입이라는 거예요. void는 크기가 0바이트로 알려져 있지만, anyopaque는 크기가 0이 아닌 값인데 그 값이 알려지지 않았어요.

void 타입의 표현식은 그 값을 무시해도 되는 유일한 경우예요. 예를 들어, void가 아닌 표현식의 값을 무시하면 컴파일 에러가 납니다.

test "ignoring expression value" {
    foo();
}

fn foo() i32 {
    return 1234;
}
$ zig test test_expression_ignored.zig
/home/ci/work/zig-bootstrap/zig/doc/langref/test_expression_ignored.zig:2:8: error: value of type 'i32' ignored
    foo();
    ~~~^~
/home/ci/work/zig-bootstrap/zig/doc/langref/test_expression_ignored.zig:2:8: note: all non-void values must be used
/home/ci/work/zig-bootstrap/zig/doc/langref/test_expression_ignored.zig:2:8: note: to discard the value, assign it to '_'

하지만 표현식의 타입이 void라면 아무 에러도 나지 않아요. 그리고 표현식의 결과는 _에 할당해서 명시적으로 무시할 수도 있어요.

test "void is ignored" {
    returnsVoid();
}

test "explicitly ignoring expression value" {
    _ = foo();
}

fn returnsVoid() void {}

fn foo() i32 {
    return 1234;
}
$ zig test test_void_ignored.zig
1/2 test_void_ignored.test.void is ignored...OK
2/2 test_void_ignored.test.explicitly ignoring expression value...OK
All 2 tests passed.

더 알아보기 (Learn more)

앞으로 이어질 주제들이 궁금하다면 아래 링크를 따라가 보세요.

  • Result Location Semantics — 함수 호출·구조체 리터럴의 결과가 놓이는 위치
  • Primitive Typesvoid를 포함한 기본 타입들의 개요
  • Arrays · Vectors — 0비트 타입이 될 수 있었던 배열·벡터
  • enum — 태그 하나뿐인 열거형
  • struct · union — 0비트 필드로 구성되는 구조체·공용체
  • @sizeOf — 타입의 바이트 크기를 구하는 내장 함수