Yahoo Finance에서 주식 정보 가져오기 (2)

https://overmt.com/yahoo-finance에서-주식-정보-가져오기-1/
의 코드 중 코드 2를 기준으로 use(임포트)와 struct(구조체) 부분의 코드에 대해 알아보겠습니다.

1. use

use reqwest;
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
use tokio;
use chrono::{DateTime, Utc, TimeZone};

가. use reqwest;

HTTP 클라이언트 라이브러리를 불러오는(import) 기능으로, Yahoo Finance API에서 주식 데이터를 가져오는 HTTP를 요청하기 위해 사용합니다.
예시: reqwest::get(“https://api.example.com”)

Cargo.toml에서는 dependency를 선언한 것이고, 사용하려면 다시 use를 해야 합니다.

나. use serde::{Deserialize, Serialize};

직렬화/역직렬화 라이브러리인 serde를 import 하는 구문으로, ::{Deserialize, Serialize}는 라이브러리의 전체를 가져오는 것이 아니라 Deserialize와 Serialize 트레이트(trait)만 가져오는 것입니다.

JSON 데이터를 Rust 구조체로 변환하거나, 그 반대 역확을 하며, 본 코드에서는 Yahoo API의 JSON 응답을 StockData 구조체로 변환하는데 사용합니다.

dependencies의 serde와 main.rs의 serde의 차이점을 알아보면 아래와 같습니다.

[Cargo.toml의 dependencies]
serde = { version = "1.0", features = ["derive"] }는 serde 크레이트의 추가 기능인 derive를 활성화하는 것으로 #[derive(Serialize, Deserialize)] 매크로를 사용 가능하게 합니다.

[main.rs의 use]
use serde::{Deserialize, Serialize};는 실제로 구조체에 적용할 트레이트인 Deserialize, Serialize 트레이트를 사용하기 위한 것입니다.

[main.rs의 struct 구문]
// derive 기능이 활성화되어서 이 매크로 사용 가능
#[derive(Debug, Deserialize, Serialize)]
struct StockData {
symbol: String,
price: f64,
}

[3단계 구조]
features = ["derive"]로 derive 기능을 활성화하고,
use serde::{...}로 Deserialize, Serialize 트레이트를 가져오고,
#[derive(...)]로 구조체에 실제 매크로를 적용하는 3단계 구조입니다.

다. use std::collections::HashMap;

키-값 쌍을 저장하는 해시맵을 import 하는 것인데, 현재 코드에서는 사용하지 않습니다. 지우고 실행해보니 문제없습니다.

claude.ai가 필요없으니 삭제를 해야 하는데, 삭제를 하지 않았네요.

cargo run을 하니 unused가 HashMap뿐만 아니라 DateTime, e도 있습니다.

use std::collections::HashMap;은 한 줄을 지우고,

use chrono::{DateTime, Utc, TimeZone};에서는 DateTime만 지우고,

error를 의미하는 e는 위 화면의 제안에 따라 _e로 바꾸고 실행하니 문제없이 깔끔하게 실행됩니다.

라. use tokio;

tokio라는 비동기 런타임 라이브러리를 임포트하는 구문으로 async/await를 이용해 여러 주식의 정보를 동시에 병렬로 가져오기가 가능해집니다. 따라서, 10개 주식을 순차적으로 가져오면 10초가 걸리는데, 병렬로 가져오면 1초뿐이 안걸립니다.

마. use chrono::{Utc, TimeZone};

chrono는 날짜/시간 처리 라이브러리로서 Unix 타임스탬프를 사람이 읽기 쉬운 날짜로 변환해줍니다. 예를 들어 1692345600를 “2023-08-18 12:00:00″로 바꿔줍니다.

Unix 타임 스탬프는 UTC 기준으로 1970.1.1부터의 누적된 초이며, UTC는 협정 세계시(Coordinated Universal Time의 약어)로서 그리니치 표준시 (GMT)의 후속 표준이라고 합니다.

2. struct

이 코드는 Yahoo Finance API에서 주식 정보를 받아오기 위한 **데이터 구조(Struct)**를 정의한 것입니다.

serde 라이브러리를 이용해 JSON 데이터 → Rust 구조체 변환(Deserialize)과 반대로 변환(Serialize)을 하기 위해 설계되어 있습니다.

postman 사이트에서 https://query1.finance.yahoo.com/v8/finance/chart/AAPL를 열어보면 아래와 같이 깔끔한 JSON 포맷의 데이터를 보여줍니다.

중괄호 안에 chart(key)가 있고, 그 안에 result와 대괄호(배열)가 있고, 다시 중괄호 다음에 meta가 있으며, 그 안에 우리가 얻고자 하는 currency, symbol, regularMarketPrice 등이 key: Value 쌍으로 담겨져 있습니다. 이에 따라 단계별로 struct를 만듭니다.

가. YahooResponse

#[derive(Debug, Deserialize, Serialize)]
struct YahooResponse {
chart: Chart,
}
  • API 응답 전체를 감싸는 최상위 구조체로서,
  • Yahoo API가 반환하는JSON 최상단에 있는 “chart” 필드를 받기 위해 사용하는데, 데이터형식은 아래 ‘나. Chart’입니다.

나. Chart

struct Chart {
result: Vec<ChartResult>,
error: Option<serde_json::Value>,
}
  • result: 실제 주식 데이터가 담긴 배열이므로 Vec 타입이고, ChartResult 형식의 데이터를 담습니다.
  • error: 에러가 있을 경우 그 내용을 담는 필드로서, 값이 없을 수도 있으니 Option 열거형이며, T값은 serde_json::Value입니다.

다. ChartResult

struct ChartResult {
meta: Meta,
}
  • meta에는 해당 종목의 기본 정보(symbol, regularMartketPrice, currency 등)가 들어 있으며, 데이터 타입은 ‘라. Meta’입니다.

라. Meta

struct Meta {
currency: String,
symbol: String,
#[serde(rename = "longName")]
long_name: Option<String>,
#[serde(rename = "regularMarketPrice")]
regular_market_price: Option<f64>,
#[serde(rename = "regularMarketTime")]
regular_market_time: Option<i64>,
}
  • 개별 종목의 메타데이터입니다.
  • #[serde(rename = “longName”)]  속성(Attribute)은 JSON의 필드 이름인 long_name을 Rust 필드 이름인 longName으로 바꾸는 역할을 하며,
  • Option을 쓰는 이유는 해당 값이 API 응답에서 없을 수 있기 때문입니다.

마. StockData

#[derive(Debug, Clone)]
struct StockData {
symbol: String,
long_name: String,
regular_market_price: f64,
currency: String,
regular_market_time: i64,
}
  • 실제 사용할 가공된 데이터 구조체로서, Debug와 Clone trait을 자동 구현하며,
  • 위의 Meta에서 필요한 값만 골라와서, 모두 Option 없이 바로 사용 가능한 형태로 변환한 것이며,
  • 프로그램 내부 로직(예: UI 표시, 계산)에서 바로 쓰기 편하도록 만든 것입니다.

에러 처리 (panic, Result, Option, unwrap, expect, ? 연산자)

Rust는 안전한 시스템 프로그래밍 언어답게, 명시적이고 예측 가능한 에러 처리 방식을 제공합니다. 오늘은 Rust의 주요 에러 처리 도구인 panic, Result, Option, unwrap, expect, ? 연산자에 대해 다루겠습니다.


1. 패닉(panic!)

fn main() {
    panic!("예기치 못한 오류 발생!");
}
  • panic!를 사용하면 프로그램이 즉시 종료되고, panic! 안에 있는 메시지를 보여줍니다.
  • 디버깅 중 주로 사용되며, 복구 불가능한 에러에 적합합니다.

let v = vec![1, 2, 3];
v[99]; // 존재하지 않는 인덱스 → 자동 panic!

Vector v의 요소가 3개이고, index의 최대값이 2인데, 인덱스를 99로 지정하면 “index가 경계를 넘어섰다”는 메시지를 보여주면서 프로그램이 멈춥니다.


2. Result<T, E> 타입

Resut<T, E>는 복구 가능한 에러 처리를 위한 열거형으로, 성공했을 때는 Ok(T), 에러가 발생했을 때는 Err(E)를 반환합니다.

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

[예제]

use std::fs::File;

fn main() {
    let result = File::open("hello.txt");

    match result {
        Ok(file) => println!("파일 열기 성공"),
        Err(e) => println!("에러 발생: {}", e),
    }
}
  • use std::fs::File;
    : 표준 라이브러리의 fs 모듈에 정의된 File 구조체를 현재 스코프에서 사용할 수 있도록 가져오는 구문입니다. 
  • let result = File::open(“hello.txt”);
    : hello.txt 파일을 여는데, 그 결과를 result에 저장합니다. result는 Result 열거형으로 File 구조체와 Error라는 2개의 variant(변형)를 가지고 있습니다.
  • match result {
    : match 연산자를 이용해 result의 결과값에 따라 처리합니다.
  • Ok(file) => println!(“파일 열기 성공”),
    : 파일 열기가 성공(Ok)이면 File 구조체 형식의 file을 variant로 갖습니다.
  • Err(e) => println!(“에러 발생: {}”, e),
    : 파일 열기에 실패하면 Err가 발생하는데, e라는 에러 종류를 갖습니다.

프로그램과 같은 폴더에 hello.txt가 없으면
“에러 발생: 지정된 파일을 찾을 수 없습니다. (os error 2)”가 화면에 출력되고,
파일이 있으면 “파일 열기 성공”이 출력됩니다.

Ok(file) => println!(“{:?} 파일 열기 성공”, file), 라고 코드를 수정하고 실행하면, hello.txt 파일이 있을 경우 file 구조체가 출력문(println!)에 전달되어
“File { handle: 0xb4, path: “\\?\D:\rust-practice\day12\hello.txt” } 파일 열기 성공”이 출력됩니다.


3. Option<T> 타입

값이 있을 수도 있고[Some(T)] 없을 수도 있음(None)을 나타냅니다. Result의 경우는 Ok(T), Err(E)인 것과 대비됩니다.

fn main() {
    let some_number = Some(10);
    // let some_number: Option<i32> = None;

    match some_number {
        Some(n) => println!("Some: {n}"),
        None => println!("None"),
    }
}
  • let some_number = Some(10);인 상태에서 위 코드를 실행하면
    Match 제어 흐름 연산자에서 Some(n)에 해당되므로 “Some: 10″이 화면에 출력되고,
  • 첫번째 줄 let some_number = Some(10);을 주석 처리하고, 두번째 줄의 주석을 제거하면 None과 매칭되어 “None”이 하면에 출력됩니다.

4. unwrap, expect

let f = File::open("hello.txt").unwrap(); // 실패 시 panic!
let f = File::open("hello.txt").expect("파일 열기 실패"); // 사용자 메시지 포함
  • unwrap과 expect는 Some(T)이거나, Ok(T)인 경우 내부의 T를 꺼내는(unwrap) 기능을 하고, None이거나, Err(E)인 경우는 빠르게 실패(fail fast)하고 싶을 때 사용하는데, expect는 unwrap와 달리 에러 메시지를 제공합니다.

  • hello.txt가 있을 경우 println!(“{:?} 파일 열기 성공”, f}를 이용해 f를 출력해보면 둘 다 아래와 같이 f를 출력합니다.
    File { handle: 0xb4, path: “\\?\D:\rust-practice\day12\hello.txt” } 파일 열기 성공
    File { handle: 0xb8, path: “\\?\D:\rust-practice\day12\hello.txt” } 파일 열기 성공

  • 그러나, 파일이 없으면 둘다 패닉이 발생하고, 에러 메시지를 표출하는데,
    let f = File::open(“hello.txt”).unwrap();은 사용자 메시지가 없고,

  • let f = File::open(“hello.txt”).expect(“파일 열기 실패”);은 에러 메시지 전에 “파일 읽기 실패”라는 사용자 메시지를 표시하는 것만 다릅니다.

5. ? 연산자(에러 전파)

use std::fs::File;
use std::io::{self, Read};

fn read_file() -> Result<String, io::Error> {
    let mut f = File::open("hello.txt");
    let mut content = String::from("This is a test file.\n");
    f.read_to_string(&mut content)?;
    Ok(content)
}

fn main() {
    match read_file() {
        Ok(content) => println!("파일 내용:\n{}", content),
        Err(e) => eprintln!("파일 읽기 오류: {}", e),
    }
}
  • Err이면, 다시 말해 hello.txt가 없을 경우, ?는 panic을 발생시키지 않고 즉시 리턴을 해서 main문의 Err 분기를 처리합니다.
  • 그러나, ?가 없으면 다음 줄의 f.read_to_string(&mut content)?;으로 넘어가는데, read_to_string 메소드가 없다고 하면서 컴파일 에러가 발생합니다.
  • Result 타입에서만 사용 가능합니다.


6. Option<T>와 Result<T, E> 비교

타입의미실패 시
Option<T>값이 있을 수도[Some(T)], 없을 수도 있음(None)None
Result<T, E>성공[Ok(T)], 또는 실패[Err(E)] 결과 포함Err(E)

둘 다 match, if let, unwrap 등을 통해 사용가능합니다.


7. 요약

도구용도설명
panic!치명적 에러프로그램 즉시 종료
Result<T, E>복구 가능한 에러성공과 실패 구분
unwrap/expect빠르게 실패간결하지만 안전하지 않음
? 연산자에러 전파Result를 간단히 처리
Option<T>존재 여부 표현값이 있을 수도 없을 수도 있음

패턴 매칭과 제어 흐름 심화 (match, if let, while let)

Rust는 패턴 매칭을 통해 다양한 값 구조를 해체하고 조건에 따라 분기할 수 있도록 지원합니다. match, if let과 while let은 이를 위해 자주 사용하는 문법입니다.


1. 복습: match 표현식

enum Coin {
    Penny,
    Nickel,
    Dime,
    Quarter,
}

fn value_in_cents(coin: Coin) -> u8 {
    match coin {
        Coin::Penny => 1,
        Coin::Nickel => 5,
        Coin::Dime => 10,
        Coin::Quarter => 25,
    }
}
  • enum은 Coin의 종류를 열거하고 있습니다.
  • match는 모든 경우를 명시해야 합니다. 위 경우 Coin의 종류 4개를 모두 다루고 있습니다.
  • 컴파일러가 exhaustiveness(완전성 – 모든 경우를 다룸)을 검사합니다.
  • 함수에서 coin을 인수로 받는데, type은 Coin 열거형이고, 반환 type은 u8로 0과 양수입니다.
  • 열거형을 분기할 때 열거형의 이름에 variant(변형)을 ::으로 연결하며,
    반환 값은 => 다음에 적고, 각각의 경우를 ,로 구분합니다.

위 코드를 실행하려면 main 함수가 아래와 같이 필요합니다.

fn main() {
    let coin1 = Coin::Penny;
    println!("{} 센트", value_in_cents(coin1));
}

Coin의 종류를 Coin:: 다음에 입력하면 value_in_cents 함수에 의해 해당하는 센트를 표시합니다.

{}가 값이 출력될 위치(placeholder)이고, 값은 value_in_cents(coin1)으로 구합니다.


2. 패턴 바인딩

enum Message {
    Hello { id: i32 },
}

fn main() {
    let msg = Message::Hello { id: 42 };

    match msg {
        Message::Hello { id: 1 } => println!("ID는 1"),
        Message::Hello { id } => println!("다른 ID: {}", id),
    }
}
  • Message 열거형은 Hello 구조체를 변형으로 갖으므로 id에 해당하는 정수를 값으로 받습니다.
  • let msg = Message::Hello { id: 42 };
    : Message 열거형으로 Hello 구조체를 만드는데 id는 42입니다.
  • 이 패턴은 Hello { id: i32 }에 매칭되며, id 값을 꺼내서 변수 id에 바인딩합니다.
  • 따라서, 두번째 분기만 있어도 되는데, 1인 경우를 별도의 경우로 빼낸 것입니다.
    다시 말해, 특정 값 비교와 변수 추출을 동시에 할 수 있습니다.

3. 와일드카드와 _, |, .., ..=

fn main() {
    let x = 7;

    match x {
        1 => println!("하나"),
        2 | 3 => println!("둘 또는 셋"),
        4..=6 => println!("4에서 6 사이"),
        _ => println!("기타"),
    }
}
  • |: 여러 패턴 매칭(or).
    위 경우 2 | 3은 2또는 3인 경우가 됩니다.
  • ..=6: 범위 매칭(=은 포함, 없으면 미만).
    위 경우 4..=6은 4이상 6이하가 되며, 4..6은 4이상 6미만이므로 4와 5만 해당됩니다.
  • _: 나머지 모든 경우 (wildcard, else)
  • x의 데이터 형식은 정수이므로 i32가 돼서 부호있는 32비트 정수이므로 -2^31 (약 -21억)부터 2^31 – 1 (약 21억)까지의 값을 표현 가능
  • 위 코드를 실행하면 x가 7이므로 _에 해당되어 “기타”가 출력됩니다.

4. if let 표현식

match가 너무 장황할 때 if let을 사용해 간단히 표현할 수 있습니다.

let some_value = Some(5);

if let Some(x) = some_value {
    println!("값은 {}", x);
} else {
    println!("값이 없습니다");
}
  • Some은 Option enum의 variant중 하나로서, if let을 이용해서 match의 단일 분기를 간단히 표현한 것이며,
    match 연산자를 이용하면 아래와 같이 해야 됩니다.
    match some_value {
        Some(x) => println!("값은 {}", x),
        None => println!("값이 없습니다"),
    }
  • if let Some(x) = some_value는 “x가 5라면”이 되므로, “값은 5″가 출력됩니다.
  • match 연산자의 경우는 None을 사용하는데, if 제어문이라 else를 사용했는데 없어도 됩니다.

5. while let 반복문

let mut stack = vec![1, 2, 3];

while let Some(top) = stack.pop() {
    println!("꺼낸 값: {}", top);
}
  • vec은 아직 설명하지 않았는데, array가 크기가 고정되어 있다면 vec은 가변 길이의 배열입니다.
  • while let을 이용하면 값이 존재하는 동안 반복합니다.
  • Option, Result와 같이 반복 가능한 열거형에 유용
  • 위 코드를 main 함수에 넣고 실행하면 아래와 같이 출력됩니다.

🧠 요약

문법설명
match여러 경우를 완전하게 분기
바인딩패턴 내부에서 변수에 값 대입 가능
_기타 모든 경우 처리
if let한 가지 매칭에 적합한 축약형
while let값이 남아 있는 동안 반복

열거형 (Enums)

Rust는 복잡한 데이터와 다양한 상태를 안전하게 표현할 수 있는 강력한 도구인 열거형(enum)을 제공합니다.
enum은 단순한 값 목록이 아닌, 각 변형(variant)에 고유한 데이터를 담을 수 있어 패턴 매칭(match)과 결합해 매우 유용하게 사용됩니다.


1. 기본 열거형의 정의와 사용

열거형은 여러 개의 이름 있는 변형을 정의하는 타입입니다.
enum 다음에 이름을 입력하고 중괄호 안에 variant(변형)를 입력합니다.

enum Direction {
    North,
    South,
    East,
    West,
}

아래와 같이 함수에 열거형을 사용할 때는 함수명을 적고 인수를 입력하는데, 인수의 형식은 열거형이 됩니다. 그리고, match 흐름 제어 연산자를 사용하는데, match 다음에 인수명을 기재하고, 분기(arm)를 정의하는데 열거형의 이름 다음에 ::을 추가하며, =>을 사용해 실행 코드를 지정합니다.

fn move_to(dir: Direction) {
    match dir {
        Direction::North => println!("북쪽으로 이동"),
        Direction::South => println!("남쪽으로 이동"),
        Direction::East  => println!("동쪽으로 이동"),
        Direction::West  => println!("서쪽으로 이동"),
    }
}

main 함수는 아래와 같이 let을 이용해 direction 변수에 열거형 이름::변형을 입력하고, 위 함수 move_to의 인수로 direction 변수를 입력하면 “북쪽으로 이동”이란 글자가 화면에 표시됩니다.

fn main () {
    let direction = Direction::North;
    move_to(direction); 
}

North를 달리하면 출력 결과가 달라지며, 변형에 없는 값, 아래에서는 North2를 입력하면 “Direction에서 (North2) variant가 발견되지 않는다”라는 에러 메시지가 표시되므로 정확한 입력을 보장할 수 있습니다.


2. 열거형 변형에 데이터 저장

열거형은 각 변형마다 다른 타입의 데이터를 가질 수 있습니다. 이 점이 Rust enum의 강력한 특징입니다.

enum Message {
    Quit,
    Move { x: i32, y: i32 },
    Write(String),
    ChangeColor(i32, i32, i32),
}

각 변형은 정해진 구조의 데이터를 저장할 수 있습니다:

  • Quit은 데이터 없음
  • Move는 구조체 형태(중괄호 사용)
  • Write는 문자열
  • ChangeColor는 튜플 형태(소괄호 사용)
fn process(msg: Message) {
    match msg {
        Message::Quit => println!("종료"),
        Message::Move { x, y } => println!("이동: ({}, {})", x, y),
        Message::Write(text) => println!("메시지: {}", text),
        Message::ChangeColor(r, g, b) => println!("색상 변경: {}, {}, {}", r, g, b),
    }
}

fn main() {
    let msg1 = Message::Quit;
    let msg2 = Message::Move { x: 10, y: 20 };
    let msg3 = Message::Write(String::from("안녕하세요"));
    let msg4 = Message::ChangeColor(255, 0, 0);

    process(msg1);
    process(msg2);
    process(msg3);
    process(msg4);
}

위 코드를 실행하면 아래와 같이 분기에 따라 실행 코드가 화면에 출력됩니다.

종료
이동: (10, 20)
메시지: 안녕하세요
색상 변경: 255, 0, 0

3. match 표현식

enum과 함께 가장 강력하게 사용되는 문법이 match입니다. 모든 경우를 exhaustively(빠짐없이) 처리하도록 강제되어, 안전한 분기 로직을 작성할 수 있습니다.

enum Coin {
    Penny,
    Nickel,
    Dime,
    Quarter(String),
}

fn main() {
    
    let coin = Coin::Quarter("New York".to_string());

    match coin {
        Coin::Penny => println!("1원!"),
        Coin::Nickel => println!("5원!"),
        Coin::Dime => println!("10원!"),
        Coin::Quarter(state) => println!("25원 from {:?}", state),
    }
}

  • enum Coin이 네 가지가 있으므로 match 연산자는 네 가지 분기를 모두 작성해야 합니다. 위 코드에서 match 분기의 하나를 주석 처리하고 실행하면

“모든 경우를 소진하지 않았다(none-exaustive patterns)”고 하면서 ‘Coin:Dime” not covered라고 “Dime 코인을 커버하지 않았다”고 합니다.

  • 모든 경우를 망라하기 어렵다면 _를 이용해 위를 제외한 다른 경우는 모두 이에 해당하는 실행 코드를 적용하도록 할 수 있습니다.
    match coin {
        Coin::Penny => println!("1원!"),
        Coin::Nickel => println!("5원!"),
        // Coin::Dime => println!("10원!"),
        // Coin::Quarter(state) => println!("25원 from {:?}", state),
        _ => println!("기타 동전!"),
    }

4. if let 표현식

단일 패턴만 확인하고 나머지는 무시하고 싶을 때는 if let 구문을 이용해 더 간결하게 처리할 수 있습니다.

    let coin = Coin::Penny;
    if let Coin::Penny = coin {
        println!("1원!");
    }

if 문의 내용이 “같다면”인데 let 문이므로 ==이 아니라 =를 사용했다는 것, 그리고 Coin::Penny가 앞에 왔다는 점을 주의해야 합니다. 위 코드를 실행하면 “1원!”가 화면에 출력됩니다.

if let coin = Coin::Penny 이라고 순서를 바꿔 표시하면 에러는 나지 않는데 coin 변수를 사용하지 않았으므로 _coin으로 변수명을 바꾸라는 제안을 합니다.

match보다 간단하지만, 나머지 경우는 무시되므로 사용에 주의가 필요합니다.


5. 열거형은 메서드도 가질 수 있다

열거형도 구조체처럼 impl 블록을 통해 메서드를 정의할 수 있습니다.

enum Status {
    Ready,
    Waiting,
    Error(i32),
}

impl Status {
    fn print(&self) {
        match self {
            Status::Ready => println!("준비 완료"),
            Status::Waiting => println!("대기 중"),
            Status::Error(code) => println!("에러 코드: {}", code),
        }
    }
}
fn main() {
    let status1 = Status::Ready;
    let status2 = Status::Waiting;
    let status3 = Status::Error(404);

    status1.print();
    status2.print();
    status3.print();
}

위 코드를 실행하면 아래와 같이 화면에 표시됩니다.

준비 완료
대기 중
에러 코드: 404

6. Option 열거형

Rust 표준 라이브러리에는 매우 자주 쓰이는 열거형인 Option이있습니다. 이는 값이 있을 수도 있고, 없을 수도 있다는 개념을 타입 시스템으로 안전하게 표현합니다.

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

예시:

let some_number = Some(5);
let no_number: Option<i32> = None;

이 방식은 null을 사용하지 않고도 안전하게 결측 값을 표현할 수 있게 해줍니다.

fn plus_one(x: Option<i32>) -> Option<i32> {
    match x {
        Some(n) => Some(n + 1),
        None => None,
    }
}

위 함수 plus_one은 인수 x가 정수(i32)라면 +1을 하고, None이라면 None을 반환하라는 의미인데, Option Enum이라 정수를 그냥 입력하면 안되고, Some 괄호 안에 입력해야 합니다.

fn main() {
    let num1 = Some(5);
    let num2 = None;

    println!("Result 1: {:?}", plus_one(num1)); // Result 1: Some(6)
    println!("Result 2: {:?}", plus_one(num2)); // Result 2: None
}

위와 같이 main 함수를 만들어 실행하면 숫자 5가 Some(5)라고 입력되면 Some(6)이 반환되고, None이 입력되면 None이 반환됩니다.

Rust는 기존의 사고 방식을 모두 바꿔버리니 적응하기 어렵습니다.


마무리

Rust의 열거형은 단순한 열거 상수를 넘어서 다양한 형태의 상태를 표현하는 강력한 수단입니다.특히 Option, Result, match와 결합하면 null, 에러, 상태 관리 등의 문제를 컴파일 타임에 안전하게 해결할 수 있습니다.