inline while
inline while
while 루프도 inline으로 만들 수 있어요. 그러면 루프가 언롤(unroll) 되는데, 그 덕분에 컴파일 타임에만 동작하는 일들을 코드 안에서 처리할 수 있게 됩니다. 대표적인 게 타입을 일급 값(first class value)처럼 다루는 거예요. 어떤 뜻인지 바로 코드로 확인해 볼게요.
본문
while 루프 앞에 inline을 붙이면 그 루프는 컴파일 타임에 펼쳐집니다(unrolled). 루프가 풀리면서 반복마다 본문이 그대로 복제되는 셈이라, 컴파일 타임에만 가능한 일들—예를 들어 타입을 값처럼 다루는 코드—이 그 안에서 동작할 수 있어요.
const expectEqual = @import("std").testing.expectEqual;
test "inline while loop" {
comptime var i = 0;
var sum: usize = 0;
inline while (i < 3) : (i += 1) {
const T = switch (i) {
0 => f32,
1 => i8,
2 => bool,
else => unreachable,
};
sum += typeNameLength(T);
}
try expectEqual(9, sum);
}
fn typeNameLength(comptime T: type) usize {
return @typeName(T).len;
}
$ zig test test_inline_while.zig
1/1 test_inline_while.test.inline while loop...OK
All 1 tests passed.
여기서 inline while이 하는 일이 보이죠? 루프 변수 i가 comptime var라서, 반복할 때마다 switch (i)가 컴파일 타임에 결정됩니다. 그 덕분에 각 반복마다 T가 f32, i8, bool로 고정되면서 typeNameLength(T)에 타입이 그대로 전달돼요. T는 type형 값이라서, 만약 런타임 변수였다면 @typeName(T).len 같은 호출은 아예 성립하지 않아요. 합계가 3 + 2 + 4 = 9가 되는 것도 전부 이렇게 타입이 컴파일 타임에 확정되기 때문에 가능한 거예요.
그런데 이렇게 반복문을 강제로 펼치는 건 그냥 해서 좋을 게 별로 없어요. 그래서 inline 루프를 쓸 만한 이유는 딱 두 가지로 좁혀서 권장합니다.
- 루프가 컴파일 타임에 실행되어야 의미가 맞는 경우. 위 예시가 정확히 이 경우예요.
- 이렇게 강제로 펼치는 게 측정 가능할 만큼 더 빠르다는 걸 벤치마크로 증명한 경우.
즉 "펼치면 더 빨라지지 않을까" 하는 막연한 기대만으로는 쓰지 않는 게 좋아요. 컴파일 타임 의미론이 필요할 때, 혹은 벤치마크로 이득이 확인됐을 때만 쓰는 게 맞아요.
더 알아보기(Learn more)
- if — 컴파일 타임 분기 처리와 관련돼요.
- Optionals — 값이 없을 수 있는 타입을 다루는 방법이에요.
- Errors — 컴파일 타임에 검증되는 오류 처리 흐름이에요.
- comptime — 컴파일 타임 평가가 어디까지 전파되는지 다뤄요.
- unreachable — 도달할 수 없는 분기를 컴파일러에 알려주는 방법이에요.