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 표시, 계산)에서 바로 쓰기 편하도록 만든 것입니다.

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

모방은 창조의 어머니인가요? claude.ai의 도움을 받아 만든 “Yahoo Finace API를 이용해서 주식 데이터를 가져오는 프로그램”을 살펴보겠습니다. Rust는 먼저 Cargo.toml 파일에서 가져올 크레이트(라이브러리)를 정의하고, main.rs에서 실행 코드를 구현합니다.

1. Cargo.toml

[dependencies]
reqwest = { version = "0.11", features = ["json"] }
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
tokio = { version = "1.0", features = ["full"] }

dependencies 섹션에 reqwest, serde, serde_json, tokio 크레이트(crate, library)를 버전과 features를 이용해 지정합니다.

가. reqwest

request가 맞는 단어인데, reqwest로 약간 다른 점 주의해야 합니다.
reqwest 크레이트는 편리하고 높은 수준의 HTTP 클라이언트를 제공합니다.

version은 크레이트의 버전을 지정하는 것은 알겠는데, features는 크레이트의 특정 기능(선택적 기능)을 활성화하거나 비활성화할 때 사용되며, 조건부 컴파일을 가능하게 하여, 필요한 기능만 컴파일하도록 하는 것입니다.

features = [“json”]은 “json” 기능을 켜서 JSON 직렬화/역직렬화 기능을 사용할 수 있게 하는 것입니다.

아래와 같이 버전 목록이 표시되는데, 맨 위에 0.12.23이 있으므로 클릭합니다.

그러면 버전이 자동으로 변경되고, X표시가 없어지고, 녹색 체크 표시로 바뀝니다.

나. serde

데이터 직렬화(Serialize) / 역직렬화(Deserialize) 라이브러리입니다.
JSON, TOML, YAML 등 다양한 포맷과 Rust 구조체를 변환할 때 사용하며,

features = [“derive”]는

[derive(Serialize, Deserialize)] 어트리뷰트를 쓸 수 있게 해주는 것입니다.

다. serde_json

serde의 JSON 전용 확장판으로서, Rust 데이터와 JSON 문자열간에 변환을 가능하게 해줍니다.

let user = User { name: "Kim".into(), age: 30 };
let json_str = serde_json::to_string(&user)?; // 구조체 → JSON 문자열
let parsed: User = serde_json::from_str(&json_str)?; // JSON → 구조체

라. tokio

Rust의 비동기 런타임 (async/await 동작을 실제로 수행하는 엔진)으로서,
features = [“full”]은 모든 기능(네트워킹, 파일 I/O, 타이머 등)을 한 번에 활성화하는 것이며,
#[tokio::main] 매크로로 main 함수를 비동기로 만들 수 있습니다.

2. main.rs

가. 코드 1

아래 코드를 src 폴더의 main.rs를 연 후 Ctrl + A해서 전체를 선택한 후 Ctrl + V를 하면 기존 내용에 덮어씌워집니다.

use reqwest;
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
use tokio;

#[derive(Debug, Deserialize, Serialize)]
struct YahooResponse {
    chart: Chart,
}

#[derive(Debug, Deserialize, Serialize)]
struct Chart {
    result: Vec<ChartResult>,
    error: Option<serde_json::Value>,
}

#[derive(Debug, Deserialize, Serialize)]
struct ChartResult {
    meta: Meta,
}

#[derive(Debug, Deserialize, Serialize)]
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>,
}

#[derive(Debug, Clone)]
struct StockData {
    symbol: String,
    long_name: String,
    regular_market_price: f64,
    currency: String,
    regular_market_time: i64,
}

async fn fetch_stock_data(symbol: &str) -> Result<StockData, Box<dyn std::error::Error + Send + Sync>> {
    let url = format!("https://query1.finance.yahoo.com/v8/finance/chart/{}", symbol);
    
    let client = reqwest::Client::new();
    let response = client
        .get(&url)
        .header("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36")
        .send()
        .await?;

    let text = response.text().await?;
    let yahoo_response: YahooResponse = serde_json::from_str(&text)?;

    if let Some(result) = yahoo_response.chart.result.first() {
        let meta = &result.meta;
        
        Ok(StockData {
            symbol: symbol.to_string(),
            long_name: meta.long_name.clone().unwrap_or_else(|| "N/A".to_string()),
            regular_market_price: meta.regular_market_price.unwrap_or(0.0),
            currency: meta.currency.clone(),
            regular_market_time: meta.regular_market_time.unwrap_or(0),
        })
    } else {
        Err(format!("No data found for symbol: {}", symbol).into())
    }
}

async fn fetch_multiple_stocks(symbols: &[&str]) -> Vec<Result<StockData, Box<dyn std::error::Error + Send + Sync>>> {
    let mut handles = Vec::new();
    
    for &symbol in symbols {
        let symbol_owned = symbol.to_string();
        let handle = tokio::spawn(async move {
            fetch_stock_data(&symbol_owned).await
        });
        handles.push(handle);
    }
    
    let mut results = Vec::new();
    for handle in handles {
        match handle.await {
            Ok(result) => results.push(result),
            Err(e) => results.push(Err(e.into())),
        }
    }
    
    results
}

fn format_timestamp(timestamp: i64) -> String {
    use std::time::{UNIX_EPOCH, Duration};
    
    let datetime = UNIX_EPOCH + Duration::from_secs(timestamp as u64);
    
    // 간단한 포맷팅 (실제로는 chrono 크레이트 사용 권장)
    format!("Unix timestamp: {}", timestamp)
}

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
    let symbols = [
        "005930.KS", // 삼성전자
        "AAPL",      // Apple
        "TSLA",      // Tesla
        "LIT",       // Global X Lithium & Battery Tech ETF
        "BRK-B",     // Berkshire Hathaway Class B
        "AMZN",      // Amazon
        "O",         // Realty Income Corporation
        "TQQQ",      // ProShares UltraPro QQQ
        "XOM",       // Exxon Mobil
        "WMT",       // Walmart
    ];

    println!("Fetching stock data...\n");
    
    let results = fetch_multiple_stocks(&symbols).await;
    
    for (i, result) in results.iter().enumerate() {
        match result {
            Ok(stock) => {
                println!("Symbol: {}", stock.symbol);
                println!("Long Name: {}", stock.long_name);
                println!("Regular Market Price: {:.2} {}", stock.regular_market_price, stock.currency);
                println!("Currency: {}", stock.currency);
                println!("Regular Market Time: {}", format_timestamp(stock.regular_market_time));
                println!("---");
            }
            Err(e) => {
                println!("Error fetching data for {}: {}", symbols[i], e);
                println!("---");
            }
        }
    }

    Ok(())
}

cargo run을 하면 compile과 build를 한 후

Run을 하는데, 주식 정보 조회 결과를 주식별로 하나씩 보여주고, 시간이 Unix timestampt로 보여줘서 날짜와 시간을 알 수 없습니다.

나 코드 2

그래서 엑셀 처럼 표 형태로 보여주고, Unix time을 년월일시로 바꿔달라고 했더니 아래 코드가 되는데,

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

#[derive(Debug, Deserialize, Serialize)]
struct YahooResponse {
    chart: Chart,
}

#[derive(Debug, Deserialize, Serialize)]
struct Chart {
    result: Vec<ChartResult>,
    error: Option<serde_json::Value>,
}

#[derive(Debug, Deserialize, Serialize)]
struct ChartResult {
    meta: Meta,
}

#[derive(Debug, Deserialize, Serialize)]
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>,
}

#[derive(Debug, Clone)]
struct StockData {
    symbol: String,
    long_name: String,
    regular_market_price: f64,
    currency: String,
    regular_market_time: i64,
}

async fn fetch_stock_data(symbol: &str) -> Result<StockData, Box<dyn std::error::Error + Send + Sync>> {
    let url = format!("https://query1.finance.yahoo.com/v8/finance/chart/{}", symbol);
    
    let client = reqwest::Client::new();
    let response = client
        .get(&url)
        .header("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36")
        .send()
        .await?;

    let text = response.text().await?;
    let yahoo_response: YahooResponse = serde_json::from_str(&text)?;

    if let Some(result) = yahoo_response.chart.result.first() {
        let meta = &result.meta;
        
        Ok(StockData {
            symbol: symbol.to_string(),
            long_name: meta.long_name.clone().unwrap_or_else(|| "N/A".to_string()),
            regular_market_price: meta.regular_market_price.unwrap_or(0.0),
            currency: meta.currency.clone(),
            regular_market_time: meta.regular_market_time.unwrap_or(0),
        })
    } else {
        Err(format!("No data found for symbol: {}", symbol).into())
    }
}

async fn fetch_multiple_stocks(symbols: &[&str]) -> Vec<Result<StockData, Box<dyn std::error::Error + Send + Sync>>> {
    let mut handles = Vec::new();
    
    for &symbol in symbols {
        let symbol_owned = symbol.to_string();
        let handle = tokio::spawn(async move {
            fetch_stock_data(&symbol_owned).await
        });
        handles.push(handle);
    }
    
    let mut results = Vec::new();
    for handle in handles {
        match handle.await {
            Ok(result) => results.push(result),
            Err(e) => results.push(Err(e.into())),
        }
    }
    
    results
}

fn format_timestamp(timestamp: i64) -> String {
    match Utc.timestamp_opt(timestamp, 0) {
        chrono::LocalResult::Single(datetime) => datetime.format("%Y-%m-%d %H:%M:%S").to_string(),
        _ => "Invalid timestamp".to_string(),
    }
}

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
    let symbols = [
        "005930.KS", // 삼성전자
        "AAPL",      // Apple
        "TSLA",      // Tesla
        "LIT",       // Global X Lithium & Battery Tech ETF
        "BRK-B",     // Berkshire Hathaway Class B
        "AMZN",      // Amazon
        "O",         // Realty Income Corporation
        "TQQQ",      // ProShares UltraPro QQQ
        "XOM",       // Exxon Mobil
        "WMT",       // Walmart
    ];

    println!("Fetching stock data...\n");
    
    let results = fetch_multiple_stocks(&symbols).await;
    
    // 테이블 헤더 출력
    println!("{:<3} {:<10} {:<35} {:<15} {:<8} {:<20}", 
             "No.", "Symbol", "Long Name", "Regular Price", "Currency", "Regular Market Time");
    println!("{}", "-".repeat(95));
    
    for (i, result) in results.iter().enumerate() {
        let row_number = i + 1;
        match result {
            Ok(stock) => {
                println!("{:<3} {:<10} {:<35} {:<15.2} {:<8} {:<20}", 
                         row_number,
                         stock.symbol,
                         if stock.long_name.len() > 35 {
                             format!("{}...", &stock.long_name[..32])
                         } else {
                             stock.long_name.clone()
                         },
                         stock.regular_market_price,
                         stock.currency,
                         format_timestamp(stock.regular_market_time)
                );
            }
            Err(e) => {
                println!("{:<3} {:<10} {:<35} {:<15} {:<8} {:<20}", 
                         row_number,
                         symbols[i],
                         "Error fetching data",
                         "N/A",
                         "N/A",
                         "N/A"
                );
            }
        }
    }

    Ok(())
}

앞 부분에 use chrono::{DateTime, Utc, TimeZone};이 있어서

먼저 Cargo.toml에 chrono 크레이트를 추가해야 합니다.

chrono 버전에 커서를 갖다대보니 버전이 0.4.41로 표시되는데, 0.4 버전대이므로 그대로 둬도 문제 없습니다.

Cargo.toml과 main.rs를 저장하고, 실행하면

표 형태로 잘 표시되고, 날짜도 연월일 시분초로 잘 표시됩니다.

다음 편에서는 main.rs 코드를 하나씩 살펴보겠습니다.

외부 라이브러리 사용법 (Crate 활용)

Rust는 Crate(크레이트) 단위로 코드와 라이브러리를 구성합니다.
크레이트는 Rust의 패키지 시스템에서 가장 작은 단위로, 우리가 Cargo.toml에 추가해서 사용하는 외부 라이브러리들도 모두 크레이트입니다.

크레이트의 기본 개념과 사용법, 외부 라이브러리를 프로젝트에 추가하고 사용하는 법, 그리고 버전 관리와 의존성에 대해 알아보겠습니다.


1. Crate란?

  • 크레이트(Crate)는 하나의 컴파일 단위입니다.
  • 두 가지로 나뉩니다:
    Binary Crate: main() 함수를 포함하고, 실행 가능한 프로그램이 됨
    Library Crate: 다른 크레이트에서 가져와 사용할 수 있는 재사용 가능한 코드 집합

예를 들어 우리가 사용하는 tokio, serde, rand 등은 모두 라이브러리 크레이트입니다.


2. Cargo로 외부 크레이트 사용하기

Rust 프로젝트는 Cargo라는 빌드 도구를 중심으로 관리됩니다.

가. Cargo.toml에 의존성(dependency) 추가

예를 들어 난수를 생성하는 rand 크레이트를 사용하려면, 프로젝트 루트의 Cargo.toml 파일의 dependencies 절에 다음과 같이 작성합니다.

처음에 버전을 0.8.0을 적었더니 버전이 0.9.1까지 있으니 여기서 선택하라고 화면이 표시됩니다.

dependency  더 높은 버전 제시
[dependencies]
rand = "0.9.1"

이제 cargo build 또는 cargo run을 실행하면 자동으로 해당 크레이트가 다운로드되고 프로젝트에 포함됩니다.

나. 코드에서 사용하기

use rand::Rng;

fn main() {
let mut rng = rand::rng();
let n: u8 = rng.random_range(1..=10);
println!("1부터 10 사이의 무작위 수: {}", n);
}
use rand::Rng;
  • Rng는 random_range() 같은 메서드를 제공하는 트레잇(trait)입니다.
  • 이걸 use해야 random_range(…) 같은 메서드를 쓸 수 있습니다.

let mut rng = rand::rng();
  • 최신 rand에서는 rand::rng()로 난수 생성기를 가져옵니다.
  • 이전에는 rand::thread_rng()를 사용했지만, 그건 deprecated 되었고 이제는 rng()로 대체됩니다. rand 버전을 0.8로 하면 아래와 같은 에러가 발생합니다.
crate 버전이 낮아 오류 발생
  • 반환 타입은 여전히 내부적으로 ThreadRng입니다.

📝 의미:

“현재 스레드에서 사용할 난수 생성기를 가져와서 rng에 저장하라”


let n: u8 = rng.random_range(1..=10);
  • random_range(1..=10)은 1부터 10까지의 정수 중 무작위 값을 생성합니다.
  • inclusive range (..=)를 사용했으므로, 10도 포함됩니다.
  • 반환되는 값은 u8 타입으로 명시적으로 지정했습니다.
  • random_range는 gen_range() 대신 사용되는 최신 방식입니다.

println!(“1부터 10 사이의 무작위 수: {}”, n);
  • 생성된 난수 n을 콘솔에 출력합니다.
  • 실행할 때마다 1~10 사이의 숫자가 무작위로 나옵니다.


3. 크레이트 문서 확인하기

모든 주요 크레이트는 문서를 잘 갖추고 있습니다.
https://docs.rs에서 크레이트 이름으로 검색하면 API 문서를 확인할 수 있습니다.

예: https://docs.rs/rand

문서에는 모듈 구조, 사용 예제, Trait 설명 등이 포함되어 있어 매우 유용합니다.


4. 크레이트 버전 지정

Cargo는 Semantic Versioning을 따릅니다.

  • rand = “0.9” → 0.9.x까지 자동 업데이트 (1.0은 제외)
  • rand = “=0.9.1” → 정확한 버전
  • rand = “>=0.9, <10.0” → 범위 지정

보통은 “0.9”와 같이 호환 가능한 최신 버전으로 표시하는 것이 일반적입니다.


5. 여러 크레이트 함께 사용하기

Rust 프로젝트는 여러 외부 크레이트를 함께 사용할 수 있습니다.

예:serde와 serde_json을 함께 사용하여 JSON을 파싱:

[Cargo.toml]

[dependencies]
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"

[main.rs]

use serde::{Deserialize, Serialize};
use serde_json::Result;

#[derive(Serialize, Deserialize, Debug)]
struct Person {
name: String,
age: u8,
}

fn main() -> Result<()> {
let data = r#"{"name": "홍길동", "age": 30}"#;
let p: Person = serde_json::from_str(data)?;
println!("{:?}", p);
Ok(())
}

가. use 키워드

use serde::{Deserialize, Serialize};
use serde_json::Result;
  • Rust의 use 키워드는 코드에서 다른 모듈이나 라이브러리의 특정 항목(함수, 구조체, 열거형 등)을 현재 스코프(범위)로 가져와 사용하기 위해 사용됩니다. 이를 통해 코드를 더 간결하고 읽기 쉽게 만들 수 있습니다. 
  • serde::{Deserialize, Serialize}: 구조체를 직렬화(Serialize) 또는 역직렬화(Deserialize) 하려면 이 트레잇이 필요합니다.
  • serde_json::Result: serde_json이 제공하는 Result 타입(Enum)을 사용합니다. 에러 처리를 쉽게 하기 위해 사용됩니다.

나. 구조체 정의

#[derive(Serialize, Deserialize, Debug)]
struct Person {
name: String,
age: u8,
}
  • [derive(…)]: 구조체에 자동으로 트레잇 구현을 추가합니다.
    – Serialize: 구조체를 JSON으로 변환할 수 있게 함.
    – Deserialize: JSON을 구조체로 변환할 수 있게 함.
    – Debug: println!(“{:?}”, …)으로 구조체 내용을 출력할 수 있게 함.
  • Person 구조체 정의
    – name: 문자열 타입
    – age: u8(0~255 정수 타입)

다. main 함수

fn main() -> Result<()> {
  • 반환 타입이Result<()>인 이유는 serde_json::from_str가 실패할 수 있기 때문입니다. 실패하면 에러를 리턴하고, 성공하면 Ok(())를 반환합니다.

라. JSON 문자열 → 구조체

let data = r#"{"name": "홍길동", "age": 30}"#;
  • r#”…#”는 raw string(원시 문자열) 문법입니다. 문자열 내부에 따옴표가 있어도 별도로 이스케이프할 필요가 없어 편리합니다.
  • {“name”: “홍길동”, “age”: 30}는 JSON 포맷 문자열입니다.
let p: Person = serde_json::from_str(data)?;
  • serde_json::from_str는 JSON 문자열을 파싱해서 Person 구조체로 변환합니다.
  • ? 연산자는 실패 시 에러를 리턴하고, 성공 시 결과값을 반환합니다.

마. 구조체 출력

println!("{:?}", p);
  • 구조체 p를 Debug 포맷으로 출력합니다. 결과는 예를 들어 다음과 같이 나옵니다.
    Person { name: “홍길동”, age: 30 }

6. Crate.io에서 크레이트 찾기

외부 라이브러리는 모두 https://crates.io에서 검색할 수 있습니다.

  • 인기 순, 다운로드 수, 최근 업데이트 순으로 정렬 가능
  • 사용자 리뷰, 문서 링크, GitHub 코드도 확인 가능

예:

  • 웹 요청 라이브러리: reqwest
  • 비동기 실행기: tokio
  • CLI 도구 만들기: clap

7. 크레이트 네임스페이스(namespace)와 모듈(module)

크레이트를 임포트할 때는 보통 최상위 네임스페이스부터 시작합니다.

use regex::Regex; // regex 크레이트 내 Regex 타입 사용

내부 모듈에 접근할 땐 점(::)을 따라 구조를 확인합니다.

예:

use tokio::time::{sleep, Duration};

8. 주의사항

  • 크레이트마다 버전이 다르면 충돌이 생길 수 있음 → Cargo가 자동으로 중복 조율
  • 사용하지 않는 크레이트는 지워주는 게 좋음
  • 일부 크레이트는 feature flag를 통해 기능을 선택적으로 활성화함

9. 정리

항목설명
CrateRust 코드의 재사용 단위, 외부 라이브러리
Cargo.toml의존성을 정의하는 설정 파일
crates.io외부 크레이트 검색/다운로드 플랫폼
docs.rs모든 크레이트의 공식 문서 모음 사이트
use크레이트 또는 모듈에서 항목을 가져오는(import) 키워드

10. 마무리

Rust의 크레이트 생태계는 매우 강력하고 체계적입니다.
외부 라이브러리를 잘 활용하면, 코드의 양을 줄이고 품질을 높일 수 있습니다.
이제는 필요한 기능이 있다면 직접 구현하기보다 crates.io에서 검색해보는 것이 먼저입니다.