← 가이드 목록으로 ← Back to guides

Rust 비동기 프로그래밍과 Tokio 완벽 가이드 Rust Async Programming & Tokio Complete Guide

Rust의 소유권과 라이프타임을 익혔다면, 이제 비동기 프로그래밍을 배울 차례입니다. Tokio 런타임과 async/await로 고성능 동시성 코드를 작성하세요.

After learning Rust's ownership and lifetimes, it's time to learn async programming. Write high-performance concurrent code with Tokio runtime and async/await.

async/await 기초 async/await Basics

// async 함수 정의
async fn fetch_data(url: &str) -> Result<String, Error> {
  let response = reqwest::get(url).await?;
  let body = response.text().await?;
  Ok(body)
}

// Tokio 런타임에서 실행
#[tokio::main]
async fn main() {
  let data = fetch_data("https://api.example.com").await;
  println!("{:?}", data);
}
// Define async function
async fn fetch_data(url: &str) -> Result<String, Error> {
  let response = reqwest::get(url).await?;
  let body = response.text().await?;
  Ok(body)
}

// Run in Tokio runtime
#[tokio::main]
async fn main() {
  let data = fetch_data("https://api.example.com").await;
  println!("{:?}", data);
}

동시 실행 (Concurrent Execution) Concurrent Execution

use tokio::join;

async fn fetch_all() -> Result<(), Error> {
  // 동시에 실행
  let (users, posts, comments) = join!(
    fetch_users(),
    fetch_posts(),
    fetch_comments()
  );

  println!("Users: {:?}", users?);
  println!("Posts: {:?}", posts?);
  Ok(())
}

// 여러 Future 중 하나만 필요할 때
use tokio::select;

async fn race() {
  select! {
    result = fast_api() => println!("Fast: {:?}", result),
    result = slow_api() => println!("Slow: {:?}", result),
  }
}
use tokio::join;

async fn fetch_all() -> Result<(), Error> {
  // Run concurrently
  let (users, posts, comments) = join!(
    fetch_users(),
    fetch_posts(),
    fetch_comments()
  );

  println!("Users: {:?}", users?);
  println!("Posts: {:?}", posts?);
  Ok(())
}

// When you need only one of multiple Futures
use tokio::select;

async fn race() {
  select! {
    result = fast_api() => println!("Fast: {:?}", result),
    result = slow_api() => println!("Slow: {:?}", result),
  }
}

Tokio 채널 Tokio Channels

use tokio::sync::mpsc;

#[tokio::main]
async fn main() {
  let (tx, mut rx) = mpsc::channel(32);

  // 송신자 태스크
  tokio::spawn(async move {
    for i in 0..10 {
      tx.send(i).await.unwrap();
    }
  });

  // 수신자
  while let Some(msg) = rx.recv().await {
    println!("Received: {}", msg);
  }
}
use tokio::sync::mpsc;

#[tokio::main]
async fn main() {
  let (tx, mut rx) = mpsc::channel(32);

  // Sender task
  tokio::spawn(async move {
    for i in 0..10 {
      tx.send(i).await.unwrap();
    }
  });

  // Receiver
  while let Some(msg) = rx.recv().await {
    println!("Received: {}", msg);
  }
}

에러 처리 패턴 Error Handling Patterns

use anyhow::{Result, Context};

async fn process() -> Result<()> {
  let config = load_config().await
    .context("설정 파일 로드 실패")?;

  let conn = connect_db(&config.db_url).await
    .context("데이터베이스 연결 실패")?;

  Ok(())
}

// thiserror로 커스텀 에러
use thiserror::Error;

#[derive(Error, Debug)]
enum ApiError {
  #[error("Network error: {0}")]
  Network(#[from] reqwest::Error),

  #[error("Not found: {0}")]
  NotFound(String),
}
use anyhow::{Result, Context};

async fn process() -> Result<()> {
  let config = load_config().await
    .context("Failed to load config")?;

  let conn = connect_db(&config.db_url).await
    .context("Failed to connect database")?;

  Ok(())
}

// Custom error with thiserror
use thiserror::Error;

#[derive(Error, Debug)]
enum ApiError {
  #[error("Network error: {0}")]
  Network(#[from] reqwest::Error),

  #[error("Not found: {0}")]
  NotFound(String),
}

💡 Rust 비동기 에코시스템 💡 Rust Async Ecosystem

  • tokio: 가장 널리 쓰이는 비동기 런타임
  • tokio: Most widely used async runtime
  • reqwest: HTTP 클라이언트
  • reqwest: HTTP client
  • sqlx: 비동기 SQL 쿼리
  • sqlx: Async SQL queries
  • axum: Tokio 기반 웹 프레임워크
  • axum: Tokio-based web framework

⚠️ async 블로킹 주의 ⚠️ Beware of Blocking in async

비동기 함수 안에서 std::thread::sleep 같은 블로킹 호출을 하면 안 됩니다. 대신 tokio::time::sleep을 사용하세요.

Don't use blocking calls like std::thread::sleep inside async functions. Use tokio::time::sleep instead.