튜플(Tuples)
튜플(Tuples)
필드 이름을 하나씩 붙이지 않고도 값들을 한 덩어리로 묶고 싶을 때가 있어요. 그럴 때 Zig의 튜플(tuple)을 쓰면 돼요. 이번에는 튜플이 뭔지, 그 특징이 뭔지, 그리고 여러 값을 한 번에 돌려받을 때 어떻게 쓰는지 하나씩 살펴볼게요.
본문
Zig에서는 필드 이름을 지정하지 않고도 익명 구조체(anonymous struct)를 만들 수 있어요. 이렇게 만든 것을 우리는 튜플(tuple) 이라고 불러요. 빈 튜플은 .{ }처럼 생겼는데, 이 모양을 Hello World 예제 중 하나에서 이미 본 적이 있을 거예요.
튜플의 필드는 0부터 시작하는 숫자로 이름이 암시적으로 매겨져요. 필드 이름이 정수라서, @""로 감싸지 않는 한 . 문법으로는 접근할 수 없어요. @"" 안에 들어 있는 이름은 항상 식별자(identifier)로 인식된답니다.
배열과 마찬가지로 튜플도 .len 필드를 갖고 있고, 인덱스로 접근할 수 있으며(단, 인덱스는 comptime에 알려져 있어야 해요), ++ 연산자와도 함께 쓸 수 있어요. 또 inline for로 순회하는 것도 가능하죠. 코드로 확인해 볼게요.
const std = @import("std");
const expect = std.testing.expect;
const expectEqual = std.testing.expectEqual;
test "tuple" {
const values = .{
@as(u32, 1234),
@as(f64, 12.34),
true,
"hi",
} ++ .{ false, false };
try expectEqual(1234, values[0]);
try expectEqual(false, values[4]);
inline for (values, 0..) |v, i| {
if (i != 2) continue;
try expect(v);
}
try expectEqual(6, values.len);
try expectEqual('h', values.@"3"[0]);
}
$ zig test test_tuples.zig
1/1 test_tuples.test.tuple...OK
All 1 tests passed.
여기서 눈여겨볼 점이 몇 가지 있어요. .{ ... }에 담긴 값들이 서로 다른 타입(u32, f64, bool, []const u8)이라도 한 튜플로 묶였죠. 그리고 ++로 뒤에 튜플을 이어 붙였으니 values.len이 6이 되는 거예요. 마지막의 values.@"3"[0]은 숫자 이름 3을 @""로 감싸 접근한 뒤, 그 문자열의 0번째 문자인 'h'를 꺼낸 겁니다.
튜플 구조 분해(Destructuring Tuples)
튜플은 구조 분해(destructuring)를 할 수 있어요. 구조 분해는 블록에서 여러 값을 한 번에 돌려받고 싶을 때 특히 유용하답니다.
const print = @import("std").debug.print;
pub fn main() void {
const digits = [_]i8 { 3, 8, 9, 0, 7, 4, 1 };
const min, const max = blk: {
var min: i8 = 127;
var max: i8 = -128;
for (digits) |digit| {
if (digit < min) min = digit;
if (digit > max) max = digit;
}
break :blk .{ min, max };
};
print("min = {}\n", .{ min });
print("max = {}\n", .{ max });
}
$ zig build-exe destructuring_block.zig
$ ./destructuring_block
min = 0
max = 9
blk: 라벨이 붙은 블록 안에서 최솟값과 최댓값을 구한 뒤, break :blk .{ min, max }로 두 값을 튜플 하나에 담아 돌려줬어요. 그걸 const min, const max = ...처럼 좌변에서 구조 분해로 받아낸 거죠. 이렇게 하면 함수를 만들지 않고도 블록 하나로 여러 값을 돌려받을 수 있어요.
함수나 내장 함수가 튜플 형태로 여러 값을 반환하는 경우에도 구조 분해는 편리해요.
const print = @import("std").debug.print;
fn divmod(numerator: u32, denominator: u32) struct { u32, u32 } {
return .{ numerator / denominator, numerator % denominator };
}
pub fn main() void {
const div, const mod = divmod(10, 3);
print("10 / 3 = {}\n", .{div});
print("10 % 3 = {}\n", .{mod});
}
$ zig build-exe destructuring_return_value.zig
$ ./destructuring_return_value
10 / 3 = 3
10 % 3 = 1
divmod의 반환 타입이 struct { u32, u32 }인데, 필드 이름 없이 타입만 나열했죠. 이것도 튜플이에요. divmod(10, 3)이 튜플 .{ 3, 1 }을 돌려주면, 그것을 const div, const mod = ...로 각각 나눠 받는 겁니다. 여러 값을 돌려받아야 할 때 함수 하나로 깔끔하게 처리되는 게 보이시죠?