ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • 보편적인 프로그래밍 개념 - 데이터 타입
    프로그래밍 기초/Rust 2022. 3. 6. 20:00
    반응형

    개발 환경

    이 문서에서 진행한 필자의 개발 환경은 다음과 같다.

    • desktop: macbook pro 13 2020
    • cpu: Intel Core i7 4core
    • memory: 32GB
    • rustup v1.24.3
    • cargo v1.58.0

     

    이 문서는 여러분이 cargo가 설치되어 있다고 가정한다. 만약 cargo를 설치하지 않았다면, 이 문서를 참고하여 설치 및 설정을 진행하길 바란다.

     

    cargo 설치가 되었다면 이번 장을 위한 프로젝트를 생성한다.

    # 프로젝트 생성
    $ cargo new --bin datatypes
    
    # 프로젝트 디렉토리로 이동
    $ cd datatypes

    데이터 타입

    rust에서의 모든 값들은 고정된 특정 "타입"을 갖는다. 이것은 rust는 컴파일 시에 모든 변수의 타입이 정해져야 함을 의미한다. 예를 들어서 main.rs를 다음과 같이 작성해보자.

     

    datatypes/src/main.rs

    fn main() {
        let guess = "42".parse().expect("Not a number!");
        println!("guess: {}", guess);
    }

     

    이제 컴파일을 해보자.

    $ cargo run
       Compiling datatypes v0.1.0 (/Users/gurumee/Workspace/today-i-learned/getting-started-rust-programming/ch03/datatypes)
    error[E0282]: type annotations needed
     --> src/main.rs:2:9
      |
    2 |     let guess = "42".parse().expect("Not a number!");
      |         ^^^^^ consider giving `guess` a type
    
    For more information about this error, try `rustc --explain E0282`.
    error: could not compile `datatypes` due to previous error

     

    이 경우, 변수 guess의 타입이 확정되지 않아서 에러가 발생한다. 때문에 아래 코드처럼 guess의 타입을 확정하게끔 main.rs를 수정해야 한다.

     

    datatypes/src/main.rs

    fn main() {
        let guess: u32 = "42".parse().expect("Not a number!");
        println!("guess: {}", guess);
    }

     

    컴파일을 실행해보면, 이전과 달리 정상적으로 수행되는 것을 확인할 수 있다.

    $ cargo run
       Compiling datatypes v0.1.0 (/Users/gurumee/Workspace/today-i-learned/getting-started-rust-programming/ch03/datatypes)
        Finished dev [unoptimized + debuginfo] target(s) in 0.29s
         Running `target/debug/datatypes`
    guess: 42

     

    rust에서 데이터 타입은 크게 2가지로 아래와 같이 나눌 수 있다.

    • 스칼라 타입
    • 복합 타입

     

    이 문서에서는 rust에서 지원하는 스칼라 타입과 간단한 복합 타입 2가지를 소개한다.

    스칼라 타입

    스칼라 타입이란 하나의 값으로 표현되는 타입을 의미한다. rust에서 지원하는 스칼라 타입은 크게 다음과 같이 4가지로 분류할 수 있다.

    • 정수형
    • 문자형
    • 실수형
    • bool

    정수형

    정수형 데이터 타입은 소수점이 없는 값들을 표현한다. 대표적으로 u32, i32등이 있다. 이들은 32비트 정수형을 의미한다. u 혹은 i 뒤의 숫자는 데이터의 크기를 의미하며 8, 16, 32, 64가 있다. 정수형은 크게 다음과 같이 분류할 수 있다.

    Length Signed Unsigned
    8 i8 u8
    16 i16 u16
    32 i32 u32
    64 i64 u64
    arch isize usize

    isize, usize는 컴퓨터 아키텍처에 알맞는 정수형 타입을 할당한다. 예를 들어 32bit 아키텍처라면 u32, i32를 64bit 아키텍처라면 u64, i64를 사용한다.

     

    iu의 차이는 부호를 갖는 정수 여부를 의미한다. i는 부호를 갖는 정수값을, u는 부호를 갖지 않는 정수값 즉 0 이상의 정수를 표현한다. 각 타입이 표현할 수 있는 값의 범위는 다음과 같다.

    Data Type Range
    i8 -(2^7) ~ (2^7 - 1)
    i16 -(2^15) ~ (2^15 - 1)
    i32 -(2^31) ~ (2^31 - 1)
    i64 -(2^63) ~ (2^63 - 1)
    u8 0 ~ (2^8 - 1)
    u16 0 ~ (2^16 - 1)
    u32 0 ~ (2^32 - 1)
    u64 0 ~ (2^64 - 1)

     

    기본적으로 다음과 같이 사용할 수 있다.

    let i: i32 = -16;
    let u: u32 = 16;

     

    정수형은 사칙 연산자를 사용할 수 있다. main.rs를 다음과 같이 수정해보자.

     

    datatypes/src/main.rs

    fn main() {
         let sum = 5 + 10;
         let sub = 5 - 10;
         let mul = 5 * 10;
         let div = 5 / 10;
         println!("sum: {}, sub: {}, mul: {}, div: {}", sum, sub, mul, div);
    }

     

    이제 컴파일 및 실행을 해보자. 결과 값을 예상할 수 있는가?

    $ cargo run
       Compiling datatypes v0.1.0 (/Users/gurumee/Workspace/today-i-learned/getting-started-rust-programming/ch03/datatypes)
        Finished dev [unoptimized + debuginfo] target(s) in 0.29s
         Running `target/debug/datatypes`
    sum: 15, sub: -5, mul: 50, div: 0

    참고! div는 왜 0이 나오나요?

    일반적으로 5 / 10 = 0.5 이다. 근데 왜 div는 0이 나왔을까? 프로그래밍 세계에서는 정수형 / 정수형 = 정수형 이기 때문이다. 실수를 표현하고 싶다면 다음 절 "실수형"을 살펴보면 된다.

    실수형

    실수형 데이터 타입은 소수점이 있는 실수를 표현한다. IEEE-754 표준에 맞게 데이터 타입이 설계되었는데 복잡한 이야기는 빼도록 하자. rust에서 실수형 데이터 타입은 f32, f64가 있다. 정수형 데이터 타입과 동일하기 각각 32bit, 64bit 크기만큼의 실수를 표현해낼 수 있다. 우리는 거의 모든 상황에서 f64를 쓰면 된다. 다음과 같이 사용할 수 있다.

    let f: f64 = 2.5;

     

    정수형과 마찬가지로 사칙 연산자를 사용할 수 있다. 이제 main.rs를 다음과 같이 수정해보자.

     

    datatypes/src/main.rs

    fn main() {
         let sum = 5.0 + 10.0;
         let sub = 5.0 - 10.0;
         let mul = 5.0 * 10.0;
         let div = 5.0 / 10.0;
         println!("sum: {}, sub: {}, mul: {}, div: {}", sum, sub, mul, div);
    }

    이제 컴파일 및 실행해보자.

    $ cargo run
       Compiling datatypes v0.1.0 (/Users/gurumee/Workspace/today-i-learned/getting-started-rust-programming/ch03/datatypes)
        Finished dev [unoptimized + debuginfo] target(s) in 0.26s
         Running `target/debug/datatypes`
    sum: 15, sub: -5, mul: 50, div: 0.5

    참고! sum, sub, mul은 왜 정수형으로 표현되었나요?
    기본적으로 rust에서는 실수형일지라도 5.0 처럼 정수로 표현할 수 있는 실수는 정수로 표현되기 때문이다.

     

    여기서 한 가지 주의할 점은 실수형과 실수형, 정수형과 정수형은 서로 사칙연산이 되지만 정수형과 실수형은 사칙연산이 불가하다. main.rs를 다음과 같이 수정해보라.

     

    datatypes/src/main.rs

    fn main() {
         let sum = 5 + 10.0;
         let sub = 5 - 10.0;
         let mul = 5 * 10.0;
         let div = 5 / 10.0;
         println!("sum: {}, sub: {}, mul: {}, div: {}", sum, sub, mul, div);
    }

     

    이제 정말 실행이 안되는지 컴파일 및 실행해보자.

    $ cargo run
       Compiling datatypes v0.1.0 (/Users/gurumee/Workspace/today-i-learned/getting-started-rust-programming/ch03/datatypes)
    error[E0277]: cannot add a float to an integer
     --> src/main.rs:2:18
      |
    2 |      let sum = 5 + 10.0;
      |                  ^ no implementation for `{integer} + {float}`
      |
      = help: the trait `Add<{float}>` is not implemented for `{integer}`
    
    error[E0277]: cannot subtract `{float}` from `{integer}`
     --> src/main.rs:3:18
      |
    3 |      let sub = 5 - 10.0;
      |                  ^ no implementation for `{integer} - {float}`
      |
      = help: the trait `Sub<{float}>` is not implemented for `{integer}`
    
    error[E0277]: cannot multiply `{integer}` by `{float}`
     --> src/main.rs:4:18
      |
    4 |      let mul = 5 * 10.0;
      |                  ^ no implementation for `{integer} * {float}`
      |
      = help: the trait `Mul<{float}>` is not implemented for `{integer}`
    
    error[E0277]: cannot divide `{integer}` by `{float}`
     --> src/main.rs:5:18
      |
    5 |      let div = 5 / 10.0;
      |                  ^ no implementation for `{integer} / {float}`
      |
      = help: the trait `Div<{float}>` is not implemented for `{integer}`
    
    For more information about this error, try `rustc --explain E0277`.
    error: could not compile `datatypes` due to 4 previous errors

     

    에러를 발생시키는 모든 구문을 확인할 수 있다. 모두 정수, 실수를 섞어서 사칙연산을 시도할 수 없다고 에러 문구를 출력하고 있다. 이럴 경우, 모두 실수, 정수로 맞춰주거나 강제로 타입 캐스팅을 해야 한다. 타입 캐스팅이란 컴파일 시 실제 타입 말고 개발자가 원하는 다른 타입으로 바꾸는 것을 의미한다. 다음과 같이 사용할 수 있다.

     

    datatypes/src/main.rs

    fn main() {
         let sum = 5 + 10.0 as u32;
         let sub = 5 - 10.0 as i64;
         let mul = 5 as f32 * 10.0;
         let div = 5 as f64 / 10.0;
         println!("sum: {}, sub: {}, mul: {}, div: {}", sum, sub, mul, div);
    }

     

    이제 다시 컴파일 및 실행을 해보자. 정상적으로 실행되는 것을 확인할 수 있다.

    $ cargo run
       Compiling datatypes v0.1.0 (/Users/gurumee/Workspace/today-i-learned/getting-started-rust-programming/ch03/datatypes)
        Finished dev [unoptimized + debuginfo] target(s) in 0.25s
         Running `target/debug/datatypes`
    sum: 15, sub: -5, mul: 50, div: 0.5

    bool

    bool은 참, 거짓을 나타내는 스칼라 타입이다. 일반적으로 혼자 사용되는 것은 드물고 조건문 및 반복문과 함께 사용된다. 다음과 같이 사용할 수 있다.

    let b: bool = true; //false도 사용할 수 있다.

    문자형

    문자형 데이터 타입은 쉽게 생각해서 문자 한 개를 표현한다고 보면 된다. rust에서는 ASCII가 아닌 Unicode Scalar를 사용하는데, 문자형 타입인 char는 알파벳뿐 아니라 한글, 중국어, 이모티콘 등 여러 문자를 표현해낼 수 있다.

     

    기본적으로 다음과 같이 사용할 수 있다.

    let c: char = 'Z';

     

    우리가 이전에 썼던 "...."&str이라는 데이터 타입이다. 이는 문자 타입인 char와 다르다는 것을 알아두자. 추후 다른 문서에서 "문자열 타입(&str)"을 다루도록 하겠다.

    복합 타입

    복합 타입은 다른 타입의 다양한 값들을 하나의 타입으로 묶는 타입을 의미한다. rust에서 기본적으로 제공되는 타입은 다음과 같다.

    • 튜플
    • 배열
    • 구조체

     

    구조체는 다른 문서에서 다룰 것이며, 이 문서에서는 튜플과 배열만 다루도록 하겠다.

    튜플

    튜플은 다양한 데이터 타입을 나열한 것과 같다. 예를 들어 이렇게 사용될 수 있다.

    let tup: (i32, f64, bool) = (500, 6.4, true);

     

    한 번 튜플을 써보자. main.rs를 다음과 같이 수정한다.

     

    datatypes/src/main.rs

    fn main() {
        let tup: (i32, f64, bool) = (500, 6.4, true);
        let x = tup.0;
        let y = tup.1;
        let z = tup.2;
        println!("x: {}, y: {}, z: {}", x, y, z);
    }

     

    위 프로그램은 정수형, 실수형, bool 데이터 타입의 값을 갖는 튜플을 생성한 후 x, y, z에 각 값을 할당하여 출력하는 프로그램이다. 이제 컴파일 및 실행해보자.

    $ cargo run
       Compiling datatypes v0.1.0 (/Users/gurumee/Workspace/today-i-learned/getting-started-rust-programming/ch03/datatypes)
        Finished dev [unoptimized + debuginfo] target(s) in 0.25s
         Running `target/debug/datatypes`
    x: 500, y: 6.4, z: true

     

    튜플의 장점은 이런 식으로 "구조 분해"문법을 사용해서 간단하게 초기화가 가능하다는 것이다.

     

    datatypes/src/main.rs

    fn main() {
        let tup: (i32, f64, bool) = (500, 6.4, true);
        let (x, y, z) = tup; // 구조 분해 문법!
        println!("x: {}, y: {}, z: {}", x, y, z);
    }

     

    결과는 위와 동일하다.

    배열

    튜플이 다른 타입의 값들을 하나로 묶어주는 타입이라면, 배열은 같은 타입의 값들을 하나로 묶어주는 타입이다. 이런 식으로 사용이 가능하다.

    let arr = [1, 2, 3];

     

    이런 식으로 접근이 가능하다.

    let arr = [1, 2, 3];
    let x = a[0];
    let y = a[1];
    let z = a[2];

     

    첫 원소가 0으로 접근하는 것에 유의하자. 프로그래밍에서는 "인덱스로 접근한다"라고 표현하는데 첫 원소를 0부터 센다고 보면 된다. 배열 사용시 주의할 점은 배열 크기를 넘는 인덱스를 사용하면 에러가 발생한다는 것이다. main.rs를 다음과 같이 수정한다.

     

    datatypes/src/main.rs

    fn main() {
        let arr = [1, 2, 3];
        println!("index: 3, arr[3]: {}", arr[3]);
    }

     

    그 후 컴파일 및 실행해보자.

    $ cargo run
       Compiling datatypes v0.1.0 (/Users/gurumee/Workspace/today-i-learned/getting-started-rust-programming/ch03/datatypes)
    error: this operation will panic at runtime
     --> src/main.rs:3:38
      |
    3 |     println!("index: 3, arr[3]: {}", arr[3]);
      |                                      ^^^^^^ index out of bounds: the length is 3 but the index is 3
      |
      = note: `#[deny(unconditional_panic)]` on by default
    
    error: could not compile `datatypes` due to previous error

     

    그럼 에러 문구에서 배열 길이는 3인데 인덱스는 3으로 접근하면 안된다는 에러가 출력된다. 위에서 언급했던것 처럼 인덱스는 0부터 세므로 길이가 3이면, 접근할 수 있덱스는 0, 1, 2 중 하나여야 한다. 이 중 하나의 값으로 수정하면 정상적으로 컴파일 및 실행되는 것을 확인할 수 있다.

     

    또한 배열은 강제로 런타임에러를 발생시킬 수 있다는 점을 기억하자. 런타임 에러란 컴파일에서는 문제가 없지만 프로그램 실행 시 발생하는 에러를 의미한다. main.rs를 다음과 같이 수정한다.

     

    datatypes/src/main.rs

    use std::io;
    
    fn main() {
        let a = [1, 2, 3, 4, 5];
        println!("Please enter an array index.");
    
        let mut index = String::new();
        io::stdin()
            .read_line(&mut index)
            .expect("Failed to read line");
    
        let index: usize = index
            .trim()
            .parse()
            .expect("Index entered was not number");
    
        let elem = a[index];
        println!("The value of the elem at index {} is: {}", index, elem);
    }

    위 프로그램은 배열을 초기화한 후 사용자의 숫자 입력을 받는다. 해당 숫자의 인덱스로 배열을 접근하여 elem을 초기화한 후 출력하는 프로그램이다. 컴파일 및 실행하여, 10을 입력해보자.

    $ cargo run
       Compiling datatypes v0.1.0 (/Users/gurumee/Workspace/today-i-learned/getting-started-rust-programming/ch03/datatypes)
        Finished dev [unoptimized + debuginfo] target(s) in 0.40s
         Running `target/debug/datatypes`
    Please enter an array index.
    10 # 10입력 후 엔터
    thread 'main' panicked at 'index out of bounds: the len is 5 but the index is 10', src/main.rs:17:16
    note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace

     

    컴파일은 정상적으로 수행되어 프로그램이 시작된다. 그러나 10을 입력 받으면 배열 길이를 초과하여 인덱스를 접근했다고 에러가 발생하며 프로그램이 강제 종료되는 것을 확인할 수 있다. 0 ~ 4 이내에 값을 입력하면 정상적으로 종료되는 것을 확인할 수 있다.

    $ cargo run
        Finished dev [unoptimized + debuginfo] target(s) in 0.00s
         Running `target/debug/datatypes`
    Please enter an array index.
    3
    The value of the elem at index 3 is: 4

    '프로그래밍 기초 > Rust' 카테고리의 다른 글

    보편적인 프로그래밍 개념 - 변수와 상수  (0) 2022.02.26
    cargo 맛보기  (0) 2022.02.20
    rust 시작하기  (0) 2022.02.19
Designed by Tistory.