@constCast

@constCast

const 표시가 붙은 포인터를 그대로 두면 안 되는 상황이 있어요. 예를 들어 const 포인터를 받지 않는 C 함수 시그니처가 있는데, 넘겨야 할 대상이 *const T로 잠겨 있는 경우죠. 그럴 때 @constCast가 포인터에서 const 한정자를 벗겨내요. 다만 이건 안전한 연산이 아니에요. const를 벗겨내고 나서 원래 바뀌면 안 되는 값을 수정해 버리면, 런타임 오류나 정의되지 않은 동작(undefined behavior)으로 이어질 수 있으니 꼭 필요한 곳에만 써야 해요.

출처: Zig Documentation

본문

@constCast는 포인터에서 const 한정자(qualifier)를 제거해요. 빌트인 선언은 이렇게 생겼어요.

@constCast(value: anytype) DestType

value는 어떤 타입이든 받을 수 있는데(anytype), 반환 타입은 DestType으로 표기돼 있죠. 이 말은 반환 타입이 결과 위치(result location), 즉 이 빌트인이 놓인 자리의 타입에서 결정된다는 뜻이에요. const 없는 포인터가 필요한 자리에 이 빌트인을 쓰면, 그 자리의 타입에 맞춰 const가 벗겨진 포인터가 돌아오는 거예요.

실제로 쓸 때는 이런 모습이에요.

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

test "constCast" {
    var a: i32 = 5;
    const const_ptr: *const i32 = &a;
    const mut_ptr: *i32 = @constCast(const_ptr);
    mut_ptr.* += 1;
    try expectEqual(6, a);
}

&a는 원래 *i32인데 const_ptr에 넣는 순간 *const i32const가 붙어요. 그 뒤 @constCast(const_ptr)이 대상 타입 *i32에 맞춰 const를 벗겨내 mut_ptr을 만들어 주죠. 그 다음부터는 mut_ptr로 원래 변수 a를 자유롭게 수정할 수 있어요.

한 가지 짚고 넘어가야 할 점이 있어요. @constCast는 메모리나 값 자체를 건드리지 않고, 단지 타입의 const 한정자를 벗겨내는 역할만 해요. 그렇기 때문에 const가 지워졌다고 해서 원본이 실제로 상수가 아니었던 건 아니에요. 원래 const여야 할 대상을 이렇게 바꿔치기하다 보면 예상치 못한 문제가 생길 수 있으니, 정말 필요할 때만 쓰고 시그니처 경계 같은 곳에서만 아껴 쓰는 게 좋아요.

더 알아보기

  • 포인터를 다른 타입으로 바꿀 때 쓰는 비슷한 빌트인으로 @ptrCast@alignCast가 있어요.
  • const 한정자가 포인터에 어떤 의미를 갖는지, *const 포인터로 값을 수정하려 하면 어떤 일이 벌어지는지는 Zig 메뉴얼의 Pointers 섹션에서 이어서 볼 수 있어요.