러스트 데이타 타입
목차
러스트는 정적타이핑(statically typed language) 언어이기 때문에 컴파일 시점에 타입이 확정됩니다. 러스트를 학습한다면 동적타이핑 언어는 런타임에서 타입이 확정된다 정도는 알고 있을 겁니다.
그런데 let 키워드를 사용하면 굳이 타입을 명시하지 않아도 컴파일이 됩니다. 그것은 컴파일러의 추론 기능 때문인데요. 현대의 컴파일러는 정적타이핑이라도 어느 정도는 데이터 타입의 추론이 가능합니다.
이 포스팅에서는 정확한 데이터 타입을 지정하는 방법에 대해서 알아봅니다. 원래 이런 시스템 프로그래밍이 가능한 언어류에서 보면 컴파일러가 타입을 추론할 수 있더라도 타입에 대해 프로그래머가 100% 컨트롤 할 수 있어야 합니다.
컴파일러를 제작하는 사람들, 즉 언어를 개발하는 사람들은 밑바닥인 어셈블리어에서 시작합니다. 어셈블리어로 프로그래밍 하지 않더라도 (대부분 C언어로 해결이 되니까) 그 원리를 모르면 컴파일러를 만들 수 없는데 어셈블리어는 모든 데이터 타입을 정교하게 컨트롤 해야 하고 그 중요성을 알고 있습니다.
러스트의 데이터 타입을 보면 매우 기계적입니다. C언어는 그래도 8비트를 char (문자형), 16비트 short(짧은 정수) 이런 식으로 의미를 부여하려 했는데 보면 알겠지만 비트로 돌아갔습니다. u8은 unsigned 8 bit -> 부호없는 8비트 – 이런 식으로 원초적인 기계의 특성을 나타냅니다. 벌써 여기서 러스트가 무엇을 지향하는지 C와의 차별성이 느껴집니다.
다음의 예제는 러스트의 u – unsigned(부호없는) 자료형입니다. 128비트까지 지원합니다. 사실 64비트를 넘어가면 숫자를 읽는 것도 쉽지않습니다.
fn main() { let var_u8: u8 = 255; let var_u16: u16 = 65535; let var_u32: u32 = 4294967295; let var_u64: u64 = 1844674407370959999; let var_u128: u128 = 340282366920938463463374607431768211455; println!("var_u8 : {}", var_u8); println!("var_u16 : {}", var_u16); println!("var_u32 : {}", var_u32); println!("var_u64 : {}", var_u64); println!("var_u128 : {}", var_u128); }
let 키워드 만으로도 컴파일러가 추론할 수 있겠지만 타입을 명시하면 컴파일러 입장에서는 일이 쉽습니다. 데이터 타입을 학습할 때는 에러가 나는 값을 넣어 보는 것이 좋습니다. 컴퓨터 프로그래밍의 학습에서 중요한 것은 프로그램이 돌아가는 것도 있겠지만 대게는 안돌아가는 것, 오류에서 많은 것을 배웁니다. 이런 방법은 많은 네임드 프로그래머들이 설파하는 내용이기도 한데 그 이유는 간단합니다. 안되는 것을 알면 되는게 뭔지 알게 됩니다. 한계를 알고 범위를 설정하는 것 이지요.
계속 강조하는데 러스트를 배울 정도의 단계라면 이미 다수의 프로그래밍 언어를 접했을 거라 생각합니다. 정적인 언어로는 C계열이나 자바 동적 언어로는 자바스크립트 파이썬 등이 있겠지요. (예전에는 비주얼 스튜디오도 많이 사용했음)
그들은 비트수로 표시하는 방식은 사용하지 않아서 좀 거부감이 있을 수 있습니다만, 좀 근본적으로 봐야합니다. 보통의 사람은 32비트가 42억의 숫자를 나타내는 것으로 이해할 수 있어도 64비트, 128비트는 인식의 범위를 벗어납니다. 이런 부분이 러스트를 더 어렵게 만든다고 볼 수도 있습니다. (컴퓨터 프로그래밍 자체를)
다음 signed(부호있는) 코드를 실행해 보면 오류가 납니다. 오류 메시지는 다음과 같습니다.
the literal 128
does not fit into the type i8
whose range is -128..=127
리터럴 128은 i8 타입에 맞지 않는다. i8의 범위는 -128.. = 127 이다 라고 친절하게 설명해줍니다. 이 바이트의 원리를 이해하면 16비트 32비트 … 나머지는 다 알 수 있습니다.
let var_i8: i8 = 128; println!("var_i8 : {}", var_i8);
데이터 타입의 한계가 어디인지 명확히 파악하는 것은 대단히 중요하고 또 타입을 설정할 때 중요합니다. 간단한 예로 사람의 나이는 어떤 타입을 써야할까요? 간단한 질문이지요. 가장 오래산 사람은 120 정도를 산다고 합니다. 그런데 나이에 마이너스는 없으니가 바이트를 사용하면 됩니다. 0~255의 256개 수로 충분합니다. 그런데 나이가 아니라 연도라면 바이트는 맞지 않습니다. 예를 들어 아인슈타인의 사후 얼마나 지났는가? 라는 질문에는 바이트는 맞지 않습니다. 왜냐하면 바이트는 255년까지 계산이 되는데 앞으로 시간이 지나면 아인슈타인 사후 255년도 오기 때문입니다. 이런 포인트를 이해하는게 핵심입니다. 데이터 타입의 설정은 말이 되야 합니다. 간단한 포인트인데 이런 것은 잘 안가르쳐 주니까요. (아인슈타인은 1955년 사망함)
러스트 데이터 타입에서 범위의 계산은 다음과 같이 합니다. (^는 제곱)
iN : N비트 부호있음. -(2^(N-1)) ~ 2^(N-1)-1
uN : N비트 부호없음. 0 ~ 2^N -1
i8이면 -(2^(8-1)) ~ 2^(8-1)-1 즉 -128~127 입니다.
오버플로우(overflow)
오버플로우는 위로 넘치다는 뜻 입니다. u8은 max 값이 255입니다. 여기에 256을 넣으면 오버플로우 오류가 납니다. 디버그 모드에서는 그렇게 되는데 –release 옵션으로 컴파일한 실전용에서는 오버플로우를 내버려둡니다. CPU가 오버플로우를 처리하는 방식은 간단한데 그냥 다음으로 넘깁니다. 255 -> 0 이 됩니다. 255+1 하면 자리올림으로 9비트가 되는데 8비트만 가져오면 0입니다. 1 0000 0000 이렇게 됩니다. 오버플로우를 런타임에서 처리하려면 기본 숫자 타입에 대한 라이브러리를 사용해야 합니다.
부동소수점 타입
부동소수점 타입(Floating-Point Types) 은 언어 교재에서는 잘 안가르쳐 줍니다. IEEE Standard 754 를 직접 계산해도 보통은 써먹을 일이 없어서 인데요. 궁굼한 사람은 Decimal to IEEE 754 Floating Point Representation – YouTube 유튜브 영상을 참고하길 바랍니다.
러스트에는 f32, f64 두개가 있습니다. 기본 타입은 f64입니다. 모든 부동소수점 타입은 부호가 있습니다.
let float_64 = 9.9; let float_32: f32 = 1.5; println!("default float: {} ", float_64); println!("float 32 bit: {} ", float_32);
let 하고 소수점 값을 할당하면 f64 로 처리합니다.
불 타입(The Boolean Type)
불 타입 혹은 불린 타입은 true와 false가 있습니다. 이거는 어렵지 않습니다.
let yourChoice = true; let myChoice: bool = false;
문자형(The Character Type)
문자형은 let myChar = ‘A’; 처럼 ‘ 따옴표 하나짜리를 사용합니다. C언어와 비슷합니다. 하지만 러스트의 문자형은 4바이트 유니코드를 차지한다는 차이점이 있습니다.
요약
여기까지 러스트의 기본 데이터 타입을 알아봤습니다. 다른 정적인 언어과 비슷합니다. 디테일한 부분에서 차이가 있겠지만요. 무슨 언어를 배우던 초반에는 데이터 형, 변수를 배우는데 너무 조급해 할 필요는 없습니다. 원래 프로그래밍 언어는 초반부가 많이 지루합니다. 근데 나중에 가면 다 반복이라서 설명할게 점점 줄어들고 스스로 깨닫는 부분이 많습니다.
참고 사이트:
Data Types – The Rust Programming Language (rust-lang.org)
러스트에 대해서 전혀 몰랐다가 러스트로 작성되었다는 런타임이나 개발 도구들이 자주 보여서 최근에 관심이 생겼습니다~
근데 바쁘다는 핑계로 러스트에 대해서 전혀 보지 못했는데, 설명을 쉽게 잘 해주셔서 글이 부담없이 읽히네요!
다음 글도 기대하겠습니다!