課題17: 非同期プログラミング (Async Programming)
マンダトリー要件
問題1: 非同期HTTPクライアント(30点)
reqwestを使って非同期HTTPクライアントを実装しなさい。
# Cargo.toml に追加
[dependencies]
tokio = { version = "1", features = ["full"] }
reqwest = { version = "0.11", features = ["json"] }
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
use reqwest;
use serde::Deserialize;
#[derive(Debug, Deserialize)]
struct User {
login: String,
id: u64,
node_id: String,
}
// 指定されたURLからJSONを取得
async fn fetch_json<T: for<'de> Deserialize<'de>>(url: &str) -> Result<T, reqwest::Error> {
// TODO: 実装 (10点)
}
// 複数のURLを並列に取得
async fn fetch_multiple(urls: Vec<&str>) -> Vec<Result<String, reqwest::Error>> {
// TODO: 実装 (10点)
}
// タイムアウト付きリクエスト
async fn fetch_with_timeout(
url: &str,
timeout_secs: u64,
) -> Result<String, Box<dyn std::error::Error>> {
// TODO: 実装 (10点)
}
#[tokio::main]
async fn main() {
// テスト1: JSONのフェッチ
let user: User = fetch_json("https://api.github.com/users/rust-lang")
.await
.unwrap();
println!("User: {:?}", user);
// テスト2: 並列フェッチ
let urls = vec![
"https://example.com",
"https://rust-lang.org",
];
let results = fetch_multiple(urls).await;
println!("Fetched {} URLs", results.len());
// テスト3: タイムアウト
match fetch_with_timeout("https://httpbin.org/delay/5", 2).await {
Ok(_) => println!("Success"),
Err(e) => println!("Timeout or error: {}", e),
}
println!("Problem 1 passed!");
}
評価基準:
fetch_jsonの実装: 10点fetch_multipleの実装: 10点fetch_with_timeoutの実装: 10点
---
問題2: 非同期タスクスケジューラー(30点)
複数の非同期タスクを管理するスケジューラーを実装しなさい。
use tokio::time::{sleep, Duration};
struct Task {
id: usize,
duration_ms: u64,
}
async fn execute_task(task: Task) -> usize {
// TODO: 実装 (10点)
// タスクをシミュレート(sleepで待機)
// タスクIDを返す
}
async fn run_tasks_sequentially(tasks: Vec<Task>) -> Vec<usize> {
// TODO: 実装 (10点)
// タスクを順番に実行
}
async fn run_tasks_concurrently(tasks: Vec<Task>) -> Vec<usize> {
// TODO: 実装 (10点)
// タスクを並行に実行
}
#[tokio::main]
async fn main() {
let tasks = vec![
Task { id: 1, duration_ms: 100 },
Task { id: 2, duration_ms: 200 },
Task { id: 3, duration_ms: 150 },
];
// 順次実行
let start = std::time::Instant::now();
let results = run_tasks_sequentially(tasks.clone()).await;
let elapsed = start.elapsed();
println!("Sequential: {:?} in {:?}", results, elapsed);
// 並行実行
let start = std::time::Instant::now();
let results = run_tasks_concurrently(tasks).await;
let elapsed = start.elapsed();
println!("Concurrent: {:?} in {:?}", results, elapsed);
println!("Problem 2 passed!");
}
評価基準:
execute_taskの実装: 10点run_tasks_sequentiallyの実装: 10点run_tasks_concurrentlyの実装: 10点
---
問題3: 非同期チャネル(20点)
tokioのチャネルを使ってプロデューサー・コンシューマーパターンを実装しなさい。
use tokio::sync::mpsc;
use tokio::time::{sleep, Duration};
async fn producer(tx: mpsc::Sender<i32>, id: usize, count: usize) {
// TODO: 実装 (10点)
// count個のメッセージを送信
// 各メッセージは100msの間隔
}
async fn consumer(mut rx: mpsc::Receiver<i32>) -> Vec<i32> {
// TODO: 実装 (10点)
// すべてのメッセージを受信して返す
}
#[tokio::main]
async fn main() {
let (tx, rx) = mpsc::channel(100);
// 複数のプロデューサーを起動
let mut producers = vec![];
for i in 0..3 {
let tx = tx.clone();
producers.push(tokio::spawn(async move {
producer(tx, i, 5).await;
}));
}
drop(tx); // オリジナルのsenderをドロップ
// コンシューマー
let consumer_handle = tokio::spawn(async move {
consumer(rx).await
});
// すべてのプロデューサーが完了するのを待つ
for handle in producers {
handle.await.unwrap();
}
let messages = consumer_handle.await.unwrap();
println!("Received {} messages", messages.len());
assert_eq!(messages.len(), 15);
println!("Problem 3 passed!");
}
評価基準:
producerの実装: 10点consumerの実装: 10点
---
ボーナス課題
ボーナス1: レートリミッター(10点)
非同期版のレートリミッターを実装しなさい。
use std::sync::Arc;
use tokio::sync::Mutex;
use tokio::time::{Duration, Instant};
struct AsyncRateLimiter {
max_requests: usize,
window: Duration,
requests: Arc<Mutex<Vec<Instant>>>,
}
impl AsyncRateLimiter {
fn new(max_requests: usize, window: Duration) -> Self {
// TODO: 実装
}
async fn allow(&self) -> bool {
// TODO: 実装
// 時間窓内のリクエスト数をチェック
}
fn clone_limiter(&self) -> Self {
// TODO: 実装
}
}
#[tokio::main]
async fn main() {
let limiter = AsyncRateLimiter::new(5, Duration::from_secs(1));
let mut tasks = vec![];
for i in 0..10 {
let limiter = limiter.clone_limiter();
tasks.push(tokio::spawn(async move {
if limiter.allow().await {
println!("Request {} allowed", i);
} else {
println!("Request {} rate limited", i);
}
}));
}
for task in tasks {
task.await.unwrap();
}
println!("Bonus 1 passed!");
}
評価基準:
- 基本的な実装: 5点
- 非同期ロックの正しい使用: 5点
---
ボーナス2: Webスクレイパー(10点)
複数のURLをクロールして情報を抽出するスクレイパーを実装しなさい。
use reqwest;
use tokio;
struct Page {
url: String,
title: String,
content_length: usize,
}
async fn scrape_page(url: &str) -> Result<Page, Box<dyn std::error::Error>> {
// TODO: 実装
// URLからHTMLを取得
// タイトルとコンテンツサイズを抽出
}
async fn scrape_multiple(urls: Vec<&str>) -> Vec<Result<Page, Box<dyn std::error::Error>>> {
// TODO: 実装
// 複数のURLを並列にスクレイプ
}
#[tokio::main]
async fn main() {
let urls = vec![
"https://example.com",
"https://rust-lang.org",
];
let results = scrape_multiple(urls).await;
for result in results {
match result {
Ok(page) => {
println!("URL: {}", page.url);
println!("Title: {}", page.title);
println!("Size: {} bytes", page.content_length);
}
Err(e) => println!("Error: {}", e),
}
}
println!("Bonus 2 passed!");
}
評価基準:
scrape_pageの実装: 5点scrape_multipleの実装: 5点
---
ボーナス3: カスタムFutureの実装(10点)
遅延評価を行うカスタムFutureを実装しなさい。
use std::future::Future;
use std::pin::Pin;
use std::task::{Context, Poll};
struct DelayedValue<T> {
value: Option<T>,
ready: bool,
}
impl<T> DelayedValue<T> {
fn new(value: T) -> Self {
// TODO: 実装
}
}
impl<T: Unpin> Future for DelayedValue<T> {
type Output = T;
fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
// TODO: 実装
// 最初の呼び出しではPending、2回目以降でReady
}
}
#[tokio::main]
async fn main() {
let future = DelayedValue::new(42);
let result = future.await;
println!("Result: {}", result);
assert_eq!(result, 42);
println!("Bonus 3 passed!");
}
評価基準:
- Futureの基本実装: 5点
- 遅延評価のロジック: 5点
---
評価基準
マンダトリー部分(80点)
| 項目 | 配点 | 評価ポイント |
|---|---|---|
| 問題1:HTTPクライアント | 30点 | reqwestの正しい使用 |
| 問題2:タスクスケジューラー | 30点 | 並行/順次実行の理解 |
| 問題3:非同期チャネル | 20点 | mpscの使用 |
ボーナス部分(20点)
| 項目 | 配点 | 評価ポイント |
|---|---|---|
| ボーナス1:レートリミッター | 10点 | 非同期ロックの理解 |
| ボーナス2:Webスクレイパー | 10点 | 実用的なアプリケーション |
| ボーナス3:カスタムFuture | 10点 | Futureの内部理解 |
注: ボーナスは最大20点まで加算されます。
---
提出方法
ファイル構成
rust-foundations-17/
├── src/
│ ├── problem1.rs
│ ├── problem2.rs
│ ├── problem3.rs
│ ├── bonus1.rs # オプション
│ ├── bonus2.rs # オプション
│ └── bonus3.rs # オプション
├── Cargo.toml
└── README.md
テスト
cargo test --release
cargo run --bin problem1
提出期限
---
ヒント
問題1のヒント
// タイムアウト
use tokio::time::timeout;
let result = timeout(
Duration::from_secs(timeout_secs),
reqwest::get(url)
).await??;
問題2のヒント
// 並行実行
let futures: Vec<_> = tasks
.into_iter()
.map(|task| execute_task(task))
.collect();
futures::future::join_all(futures).await
問題3のヒント
// プロデューサー
for i in 0..count {
tx.send(i as i32).await.unwrap();
sleep(Duration::from_millis(100)).await;
}
---
学習の確認
この課題を通じて、以下を理解できたか確認してください:
次の章では、unsafe Rustについて学びます。これがRust Foundationsコースの最終章です!