Rust/Rust 강좌

간단하게 정리해본 Rust 강좌 3 : 열거형, 제네릭 데이터 타입, Option, Result, unwrap, vector

webnautes 2023. 10. 28. 08:33
반응형

간단하게 정리해본 Rust 강좌입니다. 

 

다음 문서를 기반으로 작성했습니다.

 

Tour of Rust

https://tourofrust.com/00_ko.html 

 

2022. 09. 08 최초작성

2023. 03. 27 최종작성

 

Rust 개발 환경 만드는 방법은 아래 포스트를 참고하세요.

https://webnautes.tistory.com/2110




메모리

Rust 프로그램에는 데이터가 저장되는 다음 세 가지의 메모리 영역이 있습니다.

 

  • 데이터 메모리(data memory)

            크기가 고정되어 있으며 프로그램이 실행되는 동안 사용 가능한 영역입니다. 예) 큰따옴표(“)로 둘러쌓인 문자열

 

  • 스택 메모리(stack memory)

함수 내에서 변수가 선언될때 사용되는 영역입니다.  이 메모리에 저장된 것들은  함수가 호출된 동안에만 유지됩니다. 즉, 함수가 종료되는 순간 메모리에서 사라집니다.

 

  • 힙 메모리(heap memory) - 프로그램이 실행되는 동안 사용되는 영역입니다. 이 영역의 데이터는 추가/이동/제거/크기 변경 등이 가능합니다. 




열거형(enumeration)

enum 키워드를 사용하여 열거형 데이터 타입을  생성할 수 있습니다. 열거형 데이터 타입에 속한 원소를 match 또는 if 등의 조건으로 사용할 수 있습니다. 

#![allow(dead_code)] // 이 줄은 컴파일러 경고를 방지해줍니다.


// enum 데이터 타입을 생성합니다.
enum Species {
    Crab,
    Octopus,
    Fish,
    Clam,
}


fn main() {

    // enum 데이터 타입의 원소 중 하나를 변수 ferris에 저장합니다. 
    let ferris = Species::Crab;


    // 변수 ferris에 저장된 것을 match 문으로 비교하여 해당 문자열을 화면에 출력하도록 합니다.
    match ferris {
        Species::Crab => println!("게"),
        Species::Octopus => println!("문어"),
        Species::Fish => println!("물고기"),
        Species::Clam => println!("조개"),
    }
}

 

실행해보면 게가 출력됩니다. 



if문을 사용하여 열거형 데이터 타입을 비교하는 예제입니다.

#![allow(dead_code)] // 이 줄은 컴파일러 경고를 방지해줍니다.
#[derive(Eq, PartialEq)] // ==을 열거형 데이터 타입에서 사용할수 있게 해줍니다.


// enum 데이터 타입을 생성합니다.
enum Species {
    Crab,
    Octopus,
    Fish,
    Clam,
}


fn main() {


    // enum 데이터 타입의 원소 중 하나를 변수 ferris에 저장합니다.
    let ferris = Species::Crab;


    // 변수 ferris에 저장된 값이 enum 데이터 타입의 원소 Crab과 일치하면 문자열을 화면에 출력하도록 합니다.
    if ferris == Species::Crab{
        println!("게");
    }
}

 

실행해보면 게가 출력됩니다. 




제네릭 데이터 타입(Generic Data Type)

Rust가 데이터 타입을 유추할 수 있지만 제네릭 데이터 타입을 사용하여 컴파일러가 컴파일 타임에 struct나 enum의 데이터 타입을 지정할 수 있도록 합니다.  이때  ::<T> 연산자를 사용하여 데이터 타입을 지정합니다. 

#[derive(Debug)] //println!에서 구조체를 출력하기 위해 필요합니다.

// 구조체의 제네릭 데이터 타입 버전입니다.
struct BagOfHolding<T> {
    item: T, // T에 따라 item 필드의 데이터 타입이 결정됩니다.
}


fn main() {

    // 컴파일될때 사용할 구조체 필드의 데이터 타입을 지정합니다.
    let i32_bag = BagOfHolding::<i32> { item: 42 };   // i32 데이터 타입을 사용합니다.
    let bool_bag = BagOfHolding::<bool> { item: true }; //bool 데이터 타입을 사용합니다.


    // 구조체 필드의 데이터 타입을 지정하지 않아도 Rust는 데이터 타입을 유추할 수 있습니다.
    let float_bag = BagOfHolding { item: 3.14 };



    // 구조체 안에 구조체를 정의합니다.
    let bag_in_bag = BagOfHolding {
        item: BagOfHolding { item: "쾅!" },
    };

    println!("{:?}", i32_bag);
    println!("{:?}", bool_bag);
    println!("{:?}", float_bag);
    println!("{:?}", bag_in_bag);

}

실행결과



값이 없음을 표현하기

Rust에는 값이 없음을 나타내기 위해 사용되는 null이 없습니다. 대신 필요시 None을 사용합니다. 



Option

Rust에는 null을 쓰지 않고도 값이 없음을 표현할 수 있는 Option이라고 불리는 내장된 generic enum이 있습니다.

Option을 사용하면 값이 있는 경우 Some으로 표현되며 값이 없는 경우에는 None으로 표현됩니다.

pub enum Option<T> {
    None,
    Some(T),
}



Option을 사용하는 예제 코드입니다. 

#[derive(Debug)]

struct BagOfHolding<T> {
    item: Option<T>,
}


fn main() {

    // 값이 없는 None이라 하더라도 데이터 타입을 지정해줘야 합니다.
    let i32_bag_1 = BagOfHolding::<i32> { item: None };


    // if 문을 사용하여 값이 있는지 체크합니다.
    if i32_bag_1.item.is_none() {
        println!("가방에서 {:?}를 찾았다!", i32_bag_1.item)
    }

    // 값이 있는 경우에는 Some을 사용합니다.
    let i32_bag_2 = BagOfHolding::<i32> { item: Some(42) };


    // if 문을 사용하여 값이 있는지 체크합니다.
    if i32_bag_2.item.is_some() {
        println!("가방에서 {:?}를 찾았다!", i32_bag_2.item)
    }

    // match를 사용하여 Option 타입의 변수에 값이 있는지 체크합니다.
    match i32_bag_1.item {
        Some(v) => println!("가방에서 {}를 찾았다!", v),
        None => println!("아무 것도 찾지 못했다"),
    }

    // match를 사용하여 Option 타입의 변수에 값이 있는지 체크합니다.
    match i32_bag_2.item {
        Some(v) => println!("가방에서 {}를 찾았다!", v),
        None => println!("아무 것도 찾지 못했다"),
    }
}



실행결과

 

가방에서 None를 찾았다!

가방에서 Some(42)를 찾았다!

아무 것도 찾지 못했다

가방에서 42를 찾았다!



Result

Result는 Rust에서 오류 처리를 하는 데 사용되는 관용적인 방법입니다.

enum Result<T, E> {
  Ok(T),
  Err(E),
}



Result 타입을 사용하면 함수에서 리턴값으로  Ok 또는 Err을 사용할 수 있습니다. 

// 호출한 곳으로 전달할 값을 Ok 또는 Err을 사용했냐에 따라 Result에 지정된 두가지 타입중 하나로 리턴됩니다. 
fn do_something_that_might_fail(i: i32) -> Result<i32, (String, i32)> {

    if i == 42 {
        Ok(i)  // 값 하나만 리턴합니다.
    } else {
        Err((String::from("맞는 숫자가 아닙니다"), i)) // 튜플을 사용하여 두 개의 값을 리턴합니다.
    }
}


fn main() {
    let result = do_something_that_might_fail(12);

    match result {
        Ok(i) => println!("발견: {}는 맞는 숫자입니다.", i),
        Err((e, i)) => println!("오류: {}는 {}", i, e),
    }



    let result = do_something_that_might_fail(42);

    match result {
        Ok(i) => println!("발견: {}는 맞는 숫자입니다.", i),
        Err((e, i)) => println!("오류: {}는 {}", i, e),
    }
}

 

실행결과



우아한 오류 처리

Result와 함께 연산자 ?를 사용할 수 있습니다. 코드가 많이 간단해집니다. 

fn do_something_that_might_fail(i: i32) -> Result<i32, (String, i32)> {

    if i == 42 {
        Ok(i)  // 값 하나만 리턴합니다.
    } else {
        Err((String::from("맞는 숫자가 아닙니다"), i)) // 튜플을 사용하여 두 개의 값을 리턴합니다.
    }
}


fn test(i: i32) -> Result<i32, (String, i32)>{

    let v = do_something_that_might_fail(i)?;
    Ok(v)
}

fn main() {

    let ret = test(42);
    println!("{:?}", ret);


    let ret = test(11);
    println!("{:?}", ret);
}

 

실행결과



Option, Result과 함께 unwrap를 사용

unwrap를 사용하면 Option/Result 내부의 값을 꺼내옵니다. 이때 enum이 None 또는 Err인 경우에는 panic이 발생합니다. 

 

my_option.unwrap() 는 아래 코드와 동일하게 동작합니다.

 

match my_option {

    Some(v) => v,

    None => panic!("some error message generated by Rust!"),

}



my_result.unwrap()는 아래 코드와 동일하게 동작합니다. 

 

match my_result {

    Ok(v) => v,

    Err(e) => panic!("some error message generated by Rust!"),

}



panic는 매크로로 호출된 경우 오류 메시지 및 오류난 파일 이름/줄 번호를 함께 출력하며 프로그램을 중지합니다.



Result에 대해 unwrap를 사용한 예제 코드입니다. 

fn do_something_that_might_fail(i: i32) -> Result<i32, (String, i32)> {

    if i == 42 {
        Ok(i)  // 값 하나만 리턴합니다.
    } else {
        Err((String::from("맞는 숫자가 아닙니다"), i)) // 튜를을 사용하여 두 개의 값을 리턴합니다.
    }
}


fn test(i: i32) -> i32{

    let result = do_something_that_might_fail(i);

   
    result.unwrap()

    // result.unwrap()는 match를 사용하여 구현한 아래 코드와 동일하게 동작합니다.
    // match result {
    //     Ok(i) => i,
    //     Err((e, i)) => panic!("some error message generated by Rust!"),
    // }
   
}

fn main() {

    let ret = test(42); // 42가 줄력됩니다.
    println!("{}", ret);

    let ret = test(11); // 패닉이 발생합니다.
    println!("{}", ret);

}

 

실행결과




Option에 대해 unwrap를 사용한 예제입니다.

fn do_something_that_might_fail(i: i32) -> Option<i32> {

    if i == 42 {
        Some(i)
    } else {
        None
    }
}


fn test(i: i32) -> i32{

    let result = do_something_that_might_fail(i);

   
    result.unwrap()

    // result.unwrap()는 match를 사용하여 구현한 아래 코드와 동일하게 동작합니다.
    // match result {
    //     Some(i) => i,
    //     None => panic!("some error message generated by Rust!"),
    // }
   
}

fn main() {

    let ret = test(42); // 42가 줄력됩니다.
    println!("{}", ret);

    let ret = test(11); // 패닉이 발생합니다.
    println!("{}", ret);

}

 

실행결과



벡터(vector)

vector는 Vec struct로 표현되는 가변 크기의 리스트입니다.

vec! 매크로를 사용하면 vector를 수동으로 일일이 만드는 대신, 손쉽게 생성할 수 있게 해줍니다.

Vec는 vector를 for 반복문에 손쉽게 넣을 수 있도록 vector로부터 반복자를 생성할 수 있는 iter() 메소드를 갖고 있습니다.

 

Vec은 구조체이지만 내부적으로는 heap에 있는 고정 리스트에 대한 참조를 포함하고 있습니다.

vector는 기본 크기를 갖고 시작하는데, 주어진 크기보다 많은 원소가 추가될 경우, 큰 크기를 가지는 새로운 고정 리스트를 위해 heap에 데이터를 재할당합니다.

 

fn main() {
    // 데이터 타입을 명시하여 벡터를 생성합니다.
    let mut i32_vec = Vec::<i32>::new();

    // 벡터에 값을 삽입합니다.
    i32_vec.push(1);
    i32_vec.push(2);
    i32_vec.push(3);


    // 벡터의 값을 출력합니다.
    for num in i32_vec.iter() {
        print!("{} ", num);
    }

    println!("");


    // 데이터 타입을 명시하지 않아도 됩니다.
    let mut float_vec = Vec::new();

    float_vec.push(1.3);
    float_vec.push(2.3);
    float_vec.push(3.4);


    // 벡터의 값을 출력합니다.
    for num in float_vec.iter() {
        print!("{} ", num);
    }

    println!("");



    // vec! 매크로를 사용하면 벡터에 값을 하나씩씩 삽입하지 않고 다음처럼 할 수 있습니다. 
    let string_vec = vec![String::from("Hello"), String::from("World")];


    // 벡터의 값을 출력합니다.
    for word in string_vec.iter() {
        println!("{}", word);
    }
}

 

실행결과



간단하게 정리해본 Rust 강좌 1 : 변수, 상수, 함수, 튜플, 배열, 데이터타입, println

https://webnautes.tistory.com/2194

 

간단하게 정리해본 Rust 강좌 2 : if-else, loop, while, for, match, struct, method

https://webnautes.tistory.com/2195

 

간단하게 정리해본 Rust 강좌 3 : 열거형, 제네릭 데이터 타입, Option, Result, unwrap, vector

https://webnautes.tistory.com/2196

 

간단하게 정리해본 Rust 강좌 4 : 소유권, 참조, 역참조, 생명주기

https://webnautes.tistory.com/2197

 

간단하게 정리해본 Rust 강좌 5 : 문자열, utf-8

https://webnautes.tistory.com/2198

 

간단하게 정리해본 Rust 강좌 6 : 모듈

https://webnautes.tistory.com/2199

 

간단하게 정리해본 Rust 강좌 7 : &self, &mut self, trait, 동적 디스패치, 정적 디스패치, Generic 메서드, Box

https://webnautes.tistory.com/2200

 

간단하게 정리해본 Rust 강좌 8 : 참조자, 댕글링 참조, 원시 포인터

https://webnautes.tistory.com/2203

 

간단하게 정리해본 Rust 강좌 9 : Box, 재귀적 데이터타입, Deref 트레잇

https://webnautes.tistory.com/2202





반응형

천천히 하지만 꾸준히 공부한 내용을 블로그에 공유하는 것이 제 취미생활입니다.
블로그를 시작하게 된 계기는 내가 알게된 내용을 정리하려고 였는데 이왕이면 다른 사람에게도 공유하자였지요.

다루어 주었으면 하는 분야나 궁금한 점이 있으면 댓글로 남겨주세요. 최대한 노력해볼게요.

블로그 내용을 진행해보다가 문제 발생시 지나치지 말고 댓글로 알려주세요. 그래야 다음에 해당 글을 읽는 분에게 도움이 됩니다.

해본 결과를 바탕으로 포스트를 작성하지만 시간이 경과하면
똑같이해도 동작안할 수 있습니다.



글이 많이 유익하셨다면 토스아이디로 후원해주세요. 토스아이디



제가 쓴 책도 한번 검토해보세요 ^^