타입 변환(Type conversion) - 1부
지금까지 Rust가 정수 타입에 대해 암시적인 자동 변환을 해주지 않는다는 점을 여러 번 강조해 왔습니다. 그렇다면 개발자가 직접 하는 명시적 변환은 어떻게 할 수 있을까요?
as 연산자
as 키워드를 사용하여 정수 타입 간의 타입을 변환할 수 있습니다. 이 as 변환은 절대로 실패하지 않습니다.
예를 들어 다음과 같습니다.
let a: u32 = 10;
// `a`를 `u64` 타입으로 변환 let b = a as u64;
// 컴파일러가 타입을 충분히 추론할 수 있는 상황이라면
// 대상 타입 자리에 `_`를 쓸 수도 있습니다.
let c: u64 = a as _;
이 변환은 예상한 대로 안전하게 동작합니다. 모든 u32 값은 문제없이 u64의 범위 안에 포함되기 때문이죠.
값 잘림(Truncation)
하지만 변환 방향을 반대로 바꾸면 재미있는 상황이 벌어집니다.
// `u8`에 담기에는 너무 큰 숫자 let a: u16 = 255 + 1; // 256
let b = a as u8;
앞서 말했듯 as 변환은 실패하지 않으므로, 이 코드는 문제없이 실행됩니다. 그렇다면 b의 값은 무엇이 될까요? 더 큰 타입에서 작은 타입으로 변환할 때, Rust 컴파일러는 **값 잘림(Truncation)**을 수행합니다.
이게 무슨 뜻인지 이해하기 위해 256u16이 메모리에서 비트 시퀀스로 어떻게 저장되는지 봅시다.
0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0
| | |
+---------------+---------------+
상위 8비트 하위 8비트
이를 u8로 변환할 때, Rust 컴파일러는 상위 비트들을 버리고 마지막 하위 8비트만 남깁니다.
0 0 0 0 0 0 0 0
| |
+---------------+
하위 8비트
따라서 256 as u8은 0이 됩니다. 대부분의 경우 이런 결과는 우리가 의도한 상황이 아니겠죠. 실제로 Rust 컴파일러는 리터럴 값을 캐스팅할 때 명백한 잘림이 발생할 것 같으면 다음과 같이 경고하며 막으려고 노력합니다.
error: literal out of range for `i8`
|
4 | let a = 255 as i8;
| ^^^
|
= note: the literal `255` does not fit into the type `i8`
whose range is `-128..=127`
= help: consider using the type `u8` instead
= note: `#[deny(overflowing_literals)]` on by default
권장 사항
일반적으로 as 캐스팅은 매우 주의해서 사용해야 합니다. 가급적 더 작은 타입에서 큰 타입으로 변환할 때만 사용하세요. 만약 더 큰 타입에서 작은 타입으로 변환해야 한다면, 나중에 살펴볼 실패 가능한 변환 메커니즘을 활용하는 것이 훨씬 안전합니다.
한계
as 캐스팅의 단점은 예상치 못한 동작뿐만이 아닙니다. 이 방식은 상당히 제한적이어서 기본 타입이나 아주 특수한 몇몇 경우에만 사용할 수 있습니다. 앞으로 다룰 복합 타입이나 사용자 정의 타입을 다룰 때는 실패 가능한 변환이나 안전한 변환과 같은 다른 메커니즘을 주로 사용하게 될 것입니다.
더 읽어보기
- 각 타입 조합에 따른
as캐스팅의 상세 동작과 허용되는 모든 변환 목록은 Rust 공식 레퍼런스에서 확인할 수 있습니다.
Exercise
The exercise for this section is located in 02_basic_calculator/10_as_casting