Rust의 Casting
Rust는 Implicit Casting, 즉 암묵적 형 변환을 허용하지 않습니다.
먼저 C, C++의 예시를 보겠습니다.
#include <iostream>
#include <typeinfo>
using namespace std;
int main(){
int int32 = 26;
unsigned int uni32 = 46;
auto sum = int32 + uni32;
cout << sum << '\n';
cout << "type: " << typeid(sum).name() << '\n';
return 0;
}
부호 정수 32bit 의 타입형에 26 리터럴을,
부호 없는 정수형 32bit 타입형에 46 리터럴값을 넣습니다.
이후 두 값을 더한 sum이라는 타입형을 출력해 보겠습니다.
문제없이 두 값을 더한 72와,
unsigned int에 해당하는 문자열이 출력된 모습입니다.
참고로, 타입형에 따른 문자열의 매핑은 다음과 같습니다.
- i: int
- j: unsigned int
- l: long
- m: unsigned long
- f: float
- d: double
- c: char
- h: unsigned char
- s: short
- t: unsigned short
- b: bool
- v: void
기본적으로 프로그램 내부적으론 2의 보수의 값만 가지되,
정수형인지, 부호 없는 정수형인지 구분을 가지진 않는 겁니다.
이후에 출력 단계에서 둘을 구분한다, 정도면 충분하고요.
Rust는 같은 2의 보수형으로 표현될 수 있어도,
서로 다른 타입형의 연산을 방지합니다.
fn main(){
// let int32 :i32 = 26i32;
// let uni32 :u32 = 52u32;
// let sum = int32 + uni32;
// println!("{}", sum);
}
위와 같은 코드는 다음과 같은 에러를 발생합니다.
같은 크기의 타입형일지언정,
부호 정수와, 부호 없는 정수형의 연산을 방지합니다.
대신 Rust는 Explicit Casting,
"as" 예약어를 통한 명시적 형 변환은 허용합니다.
let decimal = 65.4321_f32;
let integer = decimal as u8;
let character = integer as char;
println!("decimal: {}", decimal);
println!("integer: {}", integer);
println!("character: {}", character);
println!("Casting: {} -> {} -> {}", decimal, integer, character);
특히 char형의 경우 Rust에선 기존의 ASCII 코드 문자들 외에,
다른 문자도 포함하기에 1byte이 아닌 4byte으로 표현된다고 말씀드렸습니다.
[Rust] 기본 골자
Rust의 타입형들 Rust learn by example Primitives - Rust By ExampleRust provides access to a wide variety of primitives. A sample includes: Signed integers: i8, i16, i32, i64, i128 and isize (pointer size) Unsigned integers: u8, u16, u32, u64, u128
swc0317.tistory.com
그럼에도 int to char 같은 타입 변환의 경우 1byte 크기, 즉 u8, i8 같은 크기만 허용합니다.
let decimal = 65.4321_f32;
let integer = decimal as u16;
let character = integer as char;
println!("decimal: {}", decimal);
println!("integer: {}", integer);
println!("character: {}", character);
println!("Casting: {} -> {} -> {}", decimal, integer, character);
integer 식별자를 u16으로 바꾼 뒤 char로 캐스팅하려 하면,
let num: u32 = 65;
if let Some(character) = std::char::from_u32(num) {
println!("{}", character); // 출력: A
} else {
println!("유효하지 않은 유니코드 값입니다.");
}
u32부터 형변환을 실행하는 함수가 존재하긴 합니다.
Casting Domain
기존의 C, C++에선 캐스팅 간 값의 범위를 신경 쓰지 않습니다. 예를 들어
64bit에서 32bit로 형 변환 시 상위 32bit에 해당하는 데이터가 사라집니다.
#include <iostream>
using namespace std;
int main(){
long long int64 = 0x7777777711111111;
printf("int64 in hex: %llx\n", int64);
printf("int64 in dig: %lld\n", int64);
int int32 = int64;
printf("int32 in hex: %llx\n", int32);
printf("int32 in dig: %lld\n", int32);
return 0;
}
64bit에 상위 32bit엔 0x7777 7777이, 하위 32bit엔 0x1111 1111이 담겨있습니다.
이의 출력 결과는 다음과 같습니다.
Rust의 경우
let uni16: u16 = 1000;
let uni8: u8 = uni16; // 서로 다른 타입의 식별자 할당
println!("1000 in u8 is : {}", uni8);
println!("1000 as a u16 is: {}", 1000 as u16);
println!("1000 as a u8 is : {}", 1000 as u8); // u8의 표현 범위 밖
먼저 서로 다른 타입의 식별자 간 할당을 허용하지 않습니다.
두 번째로, 담으려는 식별자의 값이 현재 타입형의 범위를 넘으면 마찬가지로 불가능합니다.
u8, unsigned int 8bit가 2의 보수 표현법에서 표현할 수 있는 최대 크기의 양의 정수는
2^0 + 2^1 + 2^2 + 2^3 + 2^4 + 2^5 + 2^6 + 2^7 = 2^8 - 1 = 255 입니다.
즉 0 ~ 255 범위의 수만 표현 가능한데,
1000 이라는 리터럴 값은 해당 범위 밖의 범위이므로 불가능합니다.
int main(){
long long int64 = 0x7777777711111111;
printf("int64 in hex: %llx\n", int64);
printf("int64 in dig: %lld\n", int64);
int int32 = int64;
printf("int32 in hex: %llx\n", int32);
printf("int32 in dig: %lld\n", int32);
int ano_int32 = 0x7777777711111111;
printf("ano_int32 in hex: %llx\n", ano_int32);
printf("ano_int32 in dig: %lld\n", ano_int32);
return 0;
}
C++의 타입형의 range를 넘어가더라도
넘어간 값은 자르고 나머지 값을 할당하는 저장법과는 많이 다르죠.
또 다른 Rust의 타입형의 표현 가능한 domain을 넘어가는
리터럴의 할당을 보여드리면,
println!(" 128 as a i16 is: {:b}", 128 as i16);
// In boundary case 128 value in 8-bit two's complement representation is -128
println!(" 128 as a i8 is : {:b}", 128 as i8);
보시는 바와 같이 i16에서 128을 이진수 형태로 표현하면
8개의 bit와, 8번째 bit가 1인 표현형을 가집니다.
이를 i8로 줄이면 2의 보수 표현법에서
8번째 비트는 128이 아닌 -128의 값을 가지는 데,
이 타입형에선 128을 표현할 수 없기에 에러가 발생합니다.
소수 Casting
Safe Rust와 UnSafe Rust의 두 가지 경우 다른 소수부 Casting 방법을 보입니다.
Safe Rust, Clamping
Safe Rust란 지금 우리가 사용하는 아무 명시 없는 Rust입니다.
println!(" 300.0 as u8 is : {}", 300.0_f32 as u8);
// -100.0 as u8 is 0
println!("-100.0 as u8 is : {}", -100.0_f32 as u8);
// nan as u8 is 0
println!(" nan as u8 is : {}", f32::NAN as u8);
u8에서 표현 가능한 정수 범위는 위에서 말했듯이 0 ~ 255인데,
이 범위보다 큰 값을 최댓값인 255로, 작은 값을 최솟값인 0으로 "제한"하는 Casting 법이
"Clamping" 입니다.
UnSafe Rust, Wrap Around
Wrap Around란, 범위 밖의 값을 현재 범위로 표현하기 위해, 현재 범위에서 표현 가능한 bit로
모듈러 연산하는 것입니다. 그러니까, 더 쉽게 설명하자면
64bit의 값을 32bit의 표현형에 맞게 담으려면,
33번째 비트부터 그냥 "삭제" 하는 겁니다. 그러기 위한 연산이, 33번째 비트에 해당하는 값인 약 42억,
즉
0x 7777 7777 1111 1111 mod 2^32 = 0x 1111 1111 입니다.
unsafe {
// 300.0 as u8 is 44
println!(" 300.0 as u8 is : {}", 300.0_f32.to_int_unchecked::<u8>());
// -100.0 as u8 is 156
println!("-100.0 as u8 is : {}", (-100.0_f32).to_int_unchecked::<u8>());
// nan as u8 is 0
println!(" nan as u8 is : {}", f32::NAN.to_int_unchecked::<u8>());
}
300을 256으로 나눈 나머지 값인 44,
-100을 0 ~ 255로 표현하기 위해 256을 더한 156,
nan은 그냥 0으로 표현합니다.
'프로그래밍 언어 > [Rust]' 카테고리의 다른 글
[Rust] Expression (0) | 2025.01.07 |
---|---|
[Rust] Type Conversion (0) | 2025.01.07 |
[Rust] Variable의 특성 (0) | 2024.12.30 |
[Rust] enum (0) | 2024.12.30 |
[Rust] 구조체 (0) | 2024.12.30 |