블록

블록 (Blocks)

블록(block)은 Zig에서 변수 선언의 유효 범위(scope)를 제한하는 데 쓰여요. 코드 여러 줄을 { }로 묶으면 그 안에서 선언한 변수는 블록을 벗어나는 순간 사라진다고 생각하면 돼요. Zig를 읽을 때 "이 이름이 어디까지 유효한가"를 판단하는 가장 기본적인 단위가 바로 이 블록이에요.

아래 예시를 보면 블록 안에서 선언한 x를 블록 밖에서 사용하려다 컴파일 오류가 나는 모습을 확인할 수 있어요.

test "access variable after block scope" {
    {
        var x: i32 = 1;
        _ = &x;
    }
    x += 1;
}
$ zig test test_blocks.zig
/home/ci/work/zig-bootstrap/zig/doc/langref/test_blocks.zig:6:5: error: use of undeclared identifier 'x'
    x += 1;
    ^

출처: Zig Documentation

본문

블록은 그 자체로 표현식(expression) 이에요. 즉 블록은 값을 만들어낼 수 있다는 뜻이죠. 블록에 라벨(label)을 붙이면, 그 라벨과 함께 break를 사용해 블록에서 값을 반환할 수 있어요. 라벨 이름은 blk처럼 아무 이름이나 써도 돼요.

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

test "labeled break from labeled block expression" {
    var y: i32 = 123;

    const x = blk: {
        y += 1;
        break :blk y;
    };
    try expectEqual(124, x);
    try expectEqual(124, y);
}
$ zig test test_labeled_break.zig
1/1 test_labeled_break.test.labeled break from labeled block expression...OK
All 1 tests passed.

여기서 blk:가 바로 블록 라벨이고, break :blk y;가 그 라벨 블록에서 값 y를 반환하면서 블록을 빠져나가는 부분이에요. 결과적으로 x에는 y의 값인 124가 들어가요. 라벨이 붙은 블록에서 값을 꺼내는 패턴이니 꼭 기억해 두면 좋아요.

섀도잉 (Shadowing)

Zig에서는 섀도잉을 허용하지 않아요. 즉, 같은 이름으로 다른 식별자를 "숨기는" 것은 원칙적으로 금지돼요. 아래 코드처럼 바깥 스코프의 pi와 같은 이름을 내부 블록에서 다시 선언하면 오류가 나요.

const pi = 3.14;

test "inside test block" {
    // Let's even go inside another block
    {
        var pi: i32 = 1234;
    }
}
$ zig test test_shadowing.zig
/home/ci/work/zig-bootstrap/zig/doc/langref/test_shadowing.zig:6:13: error: local variable shadows declaration of 'pi'
        var pi: i32 = 1234;
            ^~
/home/ci/work/zig-bootstrap/zig/doc/langref/test_shadowing.zig:1:1: note: declared here
const pi = 3.14;
^~~~~~~~~~~~~~~

이 규칙 덕분에 Zig 코드를 읽을 때, 어떤 식별자가 정의된 스코프 안에서는 항상 같은 의미로 쓰인다는 점을 믿을 수 있어요. 다만 중요한 예외가 하나 있어요. 스코프가 완전히 분리되어 있으면 같은 이름을 다시 써도 괜찮아요. 두 블록이 서로 겹치지 않으면 이름이 충돌할 일이 없으니까요.

test "separate scopes" {
    {
        const pi = 3.14;
        _ = pi;
    }
    {
        var pi: bool = true;
        _ = π
    }
}
$ zig test test_scopes.zig
1/1 test_scopes.test.separate scopes...OK
All 1 tests passed.

빈 블록 (Empty Blocks)

내용이 없는 빈 블록 {}void 타입의 단일 값을 반환해요. 블록이 표현식이라는 점에서, 빈 블록의 값이 곧 void라는 걸 확인해 보면 자연스럽게 이해돼요.

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

test {
    const a = {};
    try expectEqual(void, @TypeOf(a));
}
$ zig test test_empty_block.zig
1/1 test_empty_block.test_0...OK
All 1 tests passed.

더 알아보기

블록과 라벨을 함께 쓰는 패턴은 특히 반복문에서도 자주 등장해요. 같은 라벨 개념이 적용된 아래 주제를 함께 보면 흐름이 이어져요.