반응형

간단하게 정리해본 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





반응형

문제 발생시 지나치지 마시고 댓글 남겨주시면 가능한 빨리 답장드립니다.

도움이 되셨다면 토스아이디로 후원해주세요.
https://toss.me/momo2024


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

+ Recent posts