記載概要
言語を学ぶ上で、文法やメソッドを覚えたら、
次覚えるべきひとまとまりの処理群のめも
意図
処理群覚えてないようじゃプロとしては無理か、
処理群はね、覚えておかないとね、引き出し多くないとプロは
(といっても2025/4時点では著者はrust初学者)
やったこと
一つ一つ、写経した。写経しただけで、エラー出るが、基本的なことが勉強になった
なぜRustの勉強に至ったか。
そもそもなぜRust至の勉強に至ったかというと、処理が早い、が結局正義。いままでのプロジェクトで、速度問題にぶち当たらなかったことがなかった。結局、メモリ管理が全てを解決する。物理こそ正義。そのため、Rustの勉強を開始した。
初学者ではあるが、ここまでにやったこと
Rustがなぜ早いか、ガベージコレクション採用していないことのよさ、型定義で何しているか(=型定義で、使うメモリ領域のサイズ決めてる)など、Rustの意図をchatgptに聞きまくった。
基本で勉強になったこと
- Vecの表示はprintln!("{:?}")の理由({}はエラー)
→VecはDebugトレイトは実装アリだが、Displayトレイトは実装されていない、 - Displayトレイトは、「人間向けの表現」を提供する意図。Vecの中身は安全のためDisplayトレイトを実装していない
ほんきじの処理群
🔁 非同期・時間・並列処理
- 一定時間ごとに繰り返し処理(interval)
- 非同期タスクを直列で順に実行(await順次処理)
- 非同期タスクを並列に実行してすべて完了を待つ(Promise.all / join_all)
- バックグラウンド処理とUI更新を分離
- リトライ付きAPI呼び出し(指数バックオフ付き)
- 処理時間の計測(ベンチマーク用)
📦 ファイル/データ/入出力
- ファイルの存在確認・作成・削除
- ログファイルに時刻付きで書き出す
- 巨大ファイルのストリーム処理(読み込み・書き出し)
- CSVデータの読み込み・整形・再出力
- JSONファイルから構造体への変換・保存
- 設定ファイル(.envや.toml)の読み込みと反映
🔁 データ処理系(集合・変換)
- AとBのリストの差分(追加・削除された要素)を抽出
- 2つのマップをマージ(競合時の優先ルール付き)
- マップの中で特定の条件を満たすものだけ抽出
- 日付順などでグループ化する(groupBy系処理)
- データをキー別にまとめ直す(indexBy)
- カウント(集計)処理(要素ごとの出現回数など)
🧠 アルゴリズム系(軽めの応用)
- バブル・挿入・マージソートなどの実装
- 線形探索と二分探索の汎用化
- Fibonacciや階乗などの動的計画法メモ化
- バックトラッキング探索(例:迷路探索)
- グラフ構造の隣接リスト構築と探索(BFS / DFS)
📈 実務系ユースケース
- CLI引数を受け取り、処理を分岐する
- ログレベルで出力を切り替える(debug / info / error)
- エラーメッセージを読みやすく整形して出す
- 構造体やオブジェクトの中身をきれいに出力(debug出力)
- UUIDやランダムIDの生成とバリデーション
- 簡易的なキャッシュ処理(関数結果を保存して再利用)
- データを圧縮して保存・解凍して読み込み(zip/gzip)
- spawn を使ったリアルなユースケース(例えば非同期ファイルIOやAPI呼び出しの並列実行)
中身 RustとTypescript
🔁 非同期・時間・並列処理
1. 一定時間ごと繰返処理(interval)
use tokio::time::{interval, Duration};
#[tokio::main]
async fn main() {
let mut ticker = interval(Duration::from_secs(2));
for _ in 0..5 {
ticker.tick().await;
println!("Tick at {:?}", chrono::Utc::now());
}
}
/*
・Duration::from_secs(2)):secから時間を表す構造体オブジェクトDurationの作成
・interval():一定間隔で「tick」を発行するストリーム(タイマー)
・.tick().await を呼ぶと、「次の tick が来るまで待つ」動作をします。
・ticker.tick().await によって、2秒ごとに待ち、
・chrono::Utc::now() で現在の時刻を取得して出力しています。
・を5回ループ
*/
このコードは Tokio ランタイムを使って、2秒ごとに何かを繰り返す処理を非同期で実行するシンプルな例です。
処理内容
- Tokioの非同期ランタイムを使って
- 2秒おきに現在時刻を出力する処理を
- 5回繰り返す
🧠 まとめ
構文 | 意味 |
---|---|
Duration::from_secs(2) |
2秒間の長さを表すオブジェクト |
interval(Duration) |
指定した間隔で tick を発行する非同期タイマー |
ticker.tick().await |
次の tick まで待機 |
#[tokio::main] async fn |
Tokio の非同期エントリーポイント |
TypeScript(setInterval
)
let count = 0;
const intervalId = setInterval(() => {
console.log("Tick at", new Date().toISOString());
count++;
if (count >= 5) clearInterval(intervalId);
}, 2000);
非同期タスクを直列で順に実行(await順次処理)
Rust(await
を順に)
use tokio::task;
async fn my_task_prc(n: u32) {
println!("Start my_task_prc {}", n);
tokio::time::sleep(std::time::Duration::from_secs(1)).await;
println!("End my_task_prc {}", n);
}
#[tokio::main]
async fn main() {
for i in 1..=3 {
my_task_prc(i).await;
}
}
[dependencies]
tokio = { version = "1", features = ["rt-multi-thread", "macros", "time"] }
[dependencies]
tokio = { version = "1", features = ["full"] }
chrono = "0.4"
feature名 | 意味 |
---|---|
rt |
単スレッドのランタイム |
rt-multi-thread |
マルチスレッドのランタイム |
macros |
#[tokio::main] や #[tokio::test] 用 |
time |
sleep , interval などの時間操作 |
spawn |
明示しなくても rt に含まれることが多い |
補足:async fn を使って複数タスクを並列で走らせる例
async fn my_task_prc(n: u32) {
println!("Start my_task_prc {}", n);
tokio::time::sleep(std::time::Duration::from_secs(1)).await;
println!("End my_task_prc {}", n);
}
#[tokio::main]
async fn main() {
let handles: Vec<_> = (1..=3)
.map(|i| tokio::spawn(my_task_prc(i)))
.collect();
for handle in handles {
handle.await.unwrap();
}
}
//実行結果
//先に同時出力
Start my_task_prc 1
Start my_task_prc 2
Start my_task_prc 3
//同時出力
End my_task_prc 3
End my_task_prc 2
End my_task_prc 1
解説
構文 | 意味 |
---|---|
tokio::spawn(...) |
非同期処理をランタイムに渡して「同時実行」する |
JoinHandle<T> |
.await することで完了待ちできるオブジェクト |
handle.await.unwrap() |
エラーがないかチェックしつつ完了を待つ |
spawn
の特徴と注意点
特徴 | 説明 |
---|---|
並列実行 |
await せずに進められるため、他のタスクと同時に進行できる |
JoinHandle を使わないと「投げっぱなし」 |
エラーハンドリングや終了を確認したい場合は、必ず .await するべき |
Send + 'static 制約 |
spawn 内部で使う値は基本的にスレッド間で安全に共有できる Send 値であることが必要 |
🚫 spawn
に async fn
の戻り値をそのまま渡すとエラーの例
async fn my_task() -> String {
"done".to_string()
}
#[tokio::main]
async fn main() {
let handle = tokio::spawn(my_task()); // OK
let result = handle.await.unwrap(); // result: String
println!("Result: {}", result);
}
-
戻り値の型が
'static
な値(所有権が完全に移る型)である必要がある点に注意。
TypeScript
async function task(n: number) {
console.log(`Start task ${n}`);
await new Promise(res => setTimeout(res, 1000));
console.log(`End task ${n}`);
}
async function run() {
for (let i = 1; i <= 3; i++) {
await task(i);
}
}
run();
非同期タスクを並列に実行してすべて完了を待つ
//Rust(`futures::future::join_all`)
use futures::future::join_all;
async fn my_task_prc(n: u32) {
println!("Start my_task_prc {}", n);
tokio::time::sleep(std::time::Duration::from_secs(1)).await;
println!("End my_task_prc {}", n);
}
#[tokio::main]
async fn main() {
let handles = (1..=3).map(my_task_prc);
join_all(handles).await;
}
//TypeScript(`Promise.all`)
async function task(n: number) {
console.log(`Start task ${n}`);
await new Promise(res => setTimeout(res, 1000));
console.log(`End task ${n}`);
}
async function run() {
await Promise.all([1, 2, 3].map(task));
}
run();
4.バックグラウンド処理とUI更新を分離(擬似的に)
//Rust(バックグラウンド処理 → メッセージ送信)
use tokio::{sync::mpsc, time::Duration};
#[tokio::main]
async fn main() {
let (tx, mut rx) = mpsc::channel(10);
// Background task
tokio::spawn(async move {
tokio::time::sleep(Duration::from_secs(2)).await;
tx.send("Data ready").await.unwrap();
});
// "UI" waiting
while let Some(msg) = rx.recv().await {
println!("UI received: {}", msg);
}
}
//TypeScript(擬似的に setTimeout と DOM 更新)
function backgroundProcess(callback: (msg: string) => void) {
setTimeout(() => {
callback("Data ready");
}, 2000);
}
backgroundProcess(msg => {
console.log("UI received:", msg);
});
5.リトライ付きAPI呼び出し(指数バックオフ付き)
// Rust(指数バックオフ)
use tokio::time::{sleep, Duration};
async fn fetch_with_retry() {
let mut retries = 0;
while retries < 5 {
let success = rand::random::<bool>(); // ダミー
if success {
println!("Success on try {}", retries + 1);
return;
} else {
let wait = 2u64.pow(retries);
println!("Retrying in {}s...", wait);
sleep(Duration::from_secs(wait)).await;
retries += 1;
}
}
println!("Failed after 5 retries");
}
#[tokio::main]
async fn main() {
fetch_with_retry().await;
}
async function fetchWithRetry() {
for (let i = 0; i < 5; i++) {
const success = Math.random() > 0.5; // ダミー
if (success) {
console.log(`Success on try ${i + 1}`);
return;
} else {
const delay = Math.pow(2, i) * 1000;
console.log(`Retrying in ${delay / 1000}s...`);
await new Promise(res => setTimeout(res, delay));
}
}
console.log("Failed after 5 retries");
}
fetchWithRetry();
6. 処理時間の計測(ベンチマーク用)
//Rust(`std::time::Instant`)
use std::time::Instant;
fn main() {
let start = Instant::now();
let sum: u64 = (1..=1_000_000).sum();
let duration = start.elapsed();
println!("Sum = {}, elapsed = {:?}", sum, duration);
}
//TypeScript(`performance.now()` or `Date.now()`)
const start = performance.now();
const sum = Array.from({ length: 1_000_000 }, (_, i) => i + 1).reduce((a, b) => a + b, 0);
const end = performance.now();
console.log(`Sum = ${sum}, elapsed = ${(end - start).toFixed(2)} ms`);
📦 ファイル/データ/入出力
1. ファイルの存在確認・作成・削除
use std::fs;
use std::path::Path;
fn main() {
let path = "example.txt";
if Path::new(path).exists() {
println!("ファイルは存在します");
fs::remove_file(path).expect("削除失敗");
} else {
fs::write(path, "初期内容").expect("作成失敗");
println!("ファイルを作成しました");
}
}
import { existsSync, writeFileSync, unlinkSync } from 'fs';
const path = 'example.txt';
if (existsSync(path)) {
console.log('ファイルは存在します');
unlinkSync(path);
} else {
writeFileSync(path, '初期内容');
console.log('ファイルを作成しました');
}
2. ログファイルに時刻付きで書き出す
use std::fs::OpenOptions;
use std::io::Write;
use chrono::Local;
fn main() {
let now = Local::now();
let log = format!("[{}] アクション発生\n", now.format("%Y-%m-%d %H:%M:%S"));
let mut file = OpenOptions::new()
.append(true)
.create(true)
.open("log.txt")
.expect("ログファイルオープン失敗");
file.write_all(log.as_bytes()).expect("書き込み失敗");
}
import { appendFileSync } from 'fs';
const now = new Date().toISOString();
const log = `[${now}] アクション発生\n`;
appendFileSync('log.txt', log);
3. 巨大ファイルのストリーム処理(読み込み・書き出し)
//Rust(1行ずつ読む)
use std::fs::File;
use std::io::{BufReader, BufWriter, BufRead};
fn main() -> std::io::Result<()> {
let input = File::open("big_input.txt")?;
let output = File::create("big_output.txt")?;
let reader = BufReader::new(input);
let mut writer = BufWriter::new(output);
for line in reader.lines() {
let line = line?;
writeln!(writer, "{}", line.to_uppercase())?;
}
Ok(())
}
//TypeScript(Node.jsストリーム)
import { createReadStream, createWriteStream } from 'fs';
import * as readline from 'readline';
const input = createReadStream('big_input.txt');
const output = createWriteStream('big_output.txt');
const rl = readline.createInterface({ input });
rl.on('line', line => {
output.write(line.toUpperCase() + '\n');
});
rl.on('close', () => {
output.end();
});
4. CSVデータの読み込み・整形・再出力
// Rust(`csv` crate使用)
# Cargo.toml
[dependencies]
csv = "1.2"
serde = { version = "1.0", features = ["derive"] }
use serde::{Deserialize, Serialize};
use std::error::Error;
#[derive(Debug, Deserialize, Serialize)]
struct Record {
name: String,
age: u32,
}
fn main() -> Result<(), Box<dyn Error>> {
let mut rdr = csv::Reader::from_path("input.csv")?;
let mut wtr = csv::Writer::from_path("output.csv")?;
for result in rdr.deserialize() {
let mut record: Record = result?;
record.age += 1;
wtr.serialize(record)?;
}
wtr.flush()?;
Ok(())
}
//TypeScript(`csv-parser` & `fs`)
import fs from 'fs';
import csv from 'csv-parser';
const results: { name: string; age: number }[] = [];
fs.createReadStream('input.csv')
.pipe(csv())
.on('data', (data) => results.push({ ...data, age: Number(data.age) + 1 }))
.on('end', () => {
const output = results.map(r => `${r.name},${r.age}`).join('\n');
fs.writeFileSync('output.csv', output);
});
5. JSONファイルから構造体への変換・保存
use serde::{Deserialize, Serialize};
use std::fs;
#[derive(Debug, Serialize, Deserialize)]
struct Config {
username: String,
age: u32,
}
fn main() -> Result<(), Box<dyn std::error::Error>> {
let json = fs::read_to_string("config.json")?;
let mut config: Config = serde_json::from_str(&json)?;
config.age += 1;
let updated = serde_json::to_string_pretty(&config)?;
fs::write("config_updated.json", updated)?;
Ok(())
}
import fs from 'fs';
type Config = { username: string; age: number };
const json = fs.readFileSync('config.json', 'utf-8');
const config: Config = JSON.parse(json);
config.age += 1;
fs.writeFileSync('config_updated.json', JSON.stringify(config, null, 2));
6. 設定ファイル(.envや.toml)の読み込みと反映
# Cargo.toml
[dependencies]
dotenv = "0.15"
config = "0.13"
serde = { version = "1.0", features = ["derive"] }
//Rust(`.env` or `.toml`)
use dotenv::dotenv;
use std::env;
use config::{Config, File};
use serde::Deserialize;
#[derive(Debug, Deserialize)]
struct Settings {
api_key: String,
}
fn main() {
dotenv().ok();
println!("ENV from .env: {}", env::var("API_KEY").unwrap_or_default());
let settings: Settings = Config::builder()
.add_source(File::with_name("Settings"))
.build()
.unwrap()
.try_deserialize()
.unwrap();
println!("TOML設定: {:?}", settings);
}
.env
API_KEY=secret123
Settings.toml
api_key = "abc123"
//dotenv
import dotenv from 'dotenv';
dotenv.config();
console.log("API_KEY:", process.env.API_KEY);
.env
API_KEY=secret123
🔁 データ処理系(集合・変換)
1. AとBのリストの差分(追加・削除された要素)
use std::collections::HashSet;
fn main() {
let a: HashSet<_> = vec!["apple", "banana", "cherry"].into_iter().collect();
let b: HashSet<_> = vec!["banana", "dragonfruit"].into_iter().collect();
let added: Vec<_> = b.difference(&a).cloned().collect();
let removed: Vec<_> = a.difference(&b).cloned().collect();
println!("追加された: {:?}", added);
println!("削除された: {:?}", removed);
}
const a = ["apple", "banana", "cherry"];
const b = ["banana", "dragonfruit"];
const added = b.filter(x => !a.includes(x));
const removed = a.filter(x => !b.includes(x));
console.log("追加された:", added);
console.log("削除された:", removed);
2. 2つのマップをマージ(競合時の優先ルール付き)
use std::collections::HashMap;
fn main() {
let mut map1 = HashMap::from([("a", 1), ("b", 2)]);
let map2 = HashMap::from([("b", 99), ("c", 3)]);
for (k, v) in map2 {
map1.entry(k).or_insert(v); // map1の値が優先される
}
println!("{:?}", map1); // => {"a":1, "b":2, "c":3}
}
const map1 = { a: 1, b: 2 };
const map2 = { b: 99, c: 3 };
const merged = { ...map2, ...map1 }; // map1の値が優先
console.log(merged); // { b: 2, c: 3, a: 1 }
3. マップの中で特定の条件を満たすものだけ抽出
use std::collections::HashMap;
fn main() {
let map = HashMap::from([("apple", 3), ("banana", 1), ("cherry", 5)]);
let filtered: HashMap<_, _> = map
.into_iter()
.filter(|(_, v)| *v >= 3)
.collect();
println!("{:?}", filtered); // {"apple": 3, "cherry": 5}
}
const map = { apple: 3, banana: 1, cherry: 5 };
const filtered = Object.fromEntries(
Object.entries(map).filter(([_, v]) => v >= 3)
);
console.log(filtered); // { apple: 3, cherry: 5 }
4. 日付順などでグループ化する(groupBy)
//Rust(カスタム groupBy)
use std::collections::HashMap;
#[derive(Debug)]
struct Task {
date: &'static str,
title: &'static str,
}
fn main() {
let tasks = vec![
Task { date: "2024-01-01", title: "A" },
Task { date: "2024-01-02", title: "B" },
Task { date: "2024-01-01", title: "C" },
];
let mut grouped: HashMap<&str, Vec<&Task>> = HashMap::new();
for task in &tasks {
grouped.entry(task.date).or_default().push(task);
}
println!("{:#?}", grouped);
}
type Task = { date: string; title: string };
const tasks: Task[] = [
{ date: "2024-01-01", title: "A" },
{ date: "2024-01-02", title: "B" },
{ date: "2024-01-01", title: "C" },
];
const grouped = tasks.reduce((acc, task) => {
(acc[task.date] ||= []).push(task);
return acc;
}, {} as Record<string, Task[]>);
console.log(grouped);
5. データをキー別にまとめ直す(indexBy)
use std::collections::HashMap;
#[derive(Debug)]
struct User {
id: u32,
name: &'static str,
}
fn main() {
let users = vec![
User { id: 1, name: "Alice" },
User { id: 2, name: "Bob" },
];
let indexed: HashMap<u32, &User> = users.iter().map(|u| (u.id, u)).collect();
println!("{:#?}", indexed);
}
type User = { id: number; name: string };
const users: User[] = [
{ id: 1, name: "Alice" },
{ id: 2, name: "Bob" },
];
const indexed = Object.fromEntries(users.map(user => [user.id, user]));
console.log(indexed);
6. カウント(要素ごとの出現回数など)
use std::collections::HashMap;
fn main() {
let fruits = vec!["apple", "banana", "apple", "orange", "banana"];
let mut count = HashMap::new();
for fruit in fruits {
*count.entry(fruit).or_insert(0) += 1;
}
println!("{:?}", count); // {"apple": 2, "banana": 2, "orange": 1}
}
const fruits = ["apple", "banana", "apple", "orange", "banana"];
const count: Record<string, number> = {};
for (const fruit of fruits) {
count[fruit] = (count[fruit] || 0) + 1;
}
console.log(count); // { apple: 2, banana: 2, orange: 1 }
以下rustのみ
✅ アルゴリズム系
1. バブル・挿入・マージソート
// バブルソート
fn bubble_sort(arr: &mut [i32]) {
let len = arr.len();
for i in 0..len {
for j in 0..len - i - 1 {
if arr[j] > arr[j + 1] {
arr.swap(j, j + 1);
}
}
}
}
// 挿入ソート
fn insertion_sort(arr: &mut [i32]) {
for i in 1..arr.len() {
let key = arr[i];
let mut j = i;
while j > 0 && arr[j - 1] > key {
arr[j] = arr[j - 1];
j -= 1;
}
arr[j] = key;
}
}
// マージソート(再帰)
fn merge_sort(arr: &mut [i32]) {
let mid = arr.len() / 2;
if mid == 0 { return; }
let mut left = arr[..mid].to_vec();
let mut right = arr[mid..].to_vec();
merge_sort(&mut left);
merge_sort(&mut right);
let mut i = 0;
while !left.is_empty() && !right.is_empty() {
if left[0] < right[0] {
arr[i] = left.remove(0);
} else {
arr[i] = right.remove(0);
}
i += 1;
}
for x in left.into_iter().chain(right) {
arr[i] = x;
i += 1;
}
}
2. 線形探索と二分探索(汎用)
fn linear_search<T: PartialEq>(arr: &[T], target: T) -> Option<usize> {
arr.iter().position(|x| *x == target)
}
fn binary_search<T: Ord>(arr: &[T], target: T) -> Option<usize> {
let mut low = 0;
let mut high = arr.len();
while low < high {
let mid = (low + high) / 2;
if arr[mid] == target {
return Some(mid);
} else if arr[mid] < target {
low = mid + 1;
} else {
high = mid;
}
}
None
}
3. Fibonacci / 階乗(メモ化付き動的計画法)
use std::collections::HashMap;
fn fib(n: u32, memo: &mut HashMap<u32, u64>) -> u64 {
if n <= 1 { return n as u64; }
if let Some(&v) = memo.get(&n) {
return v;
}
let v = fib(n - 1, memo) + fib(n - 2, memo);
memo.insert(n, v);
v
}
4. バックトラッキング(迷路探索)
fn dfs(maze: &[Vec<char>], x: usize, y: usize, visited: &mut Vec<Vec<bool>>) -> bool {
if maze[y][x] == 'G' { return true; }
visited[y][x] = true;
let dirs = [(0,1), (1,0), (0,-1), (-1,0)];
for (dx, dy) in dirs.iter() {
let nx = x as isize + dx;
let ny = y as isize + dy;
if nx >= 0 && ny >= 0 && ny < maze.len() as isize && nx < maze[0].len() as isize {
let (nx, ny) = (nx as usize, ny as usize);
if maze[ny][nx] != '#' && !visited[ny][nx] {
if dfs(maze, nx, ny, visited) {
return true;
}
}
}
}
false
}
5. グラフ探索(BFS / DFS)
use std::collections::{HashMap, VecDeque};
fn bfs(graph: &HashMap<&str, Vec<&str>>, start: &str) {
let mut visited = HashMap::new();
let mut queue = VecDeque::new();
queue.push_back(start);
while let Some(node) = queue.pop_front() {
if visited.get(node).is_some() { continue; }
visited.insert(node, true);
println!("Visited {}", node);
if let Some(neighbors) = graph.get(node) {
for &n in neighbors {
queue.push_back(n);
}
}
}
}
✅ 実用系ユーティリティ
6. CLI引数を受け取り、処理を分岐
use std::env;
fn main() {
let args: Vec<String> = env::args().collect();
match args.get(1).map(|s| s.as_str()) {
Some("hello") => println!("Hello mode"),
Some("debug") => println!("Debug mode"),
_ => println!("Usage: program [hello|debug]"),
}
}
7. ログレベルで出力を切り替える
use log::{info, debug, error};
fn main() {
env_logger::init();
info!("This is info");
debug!("This is debug");
error!("This is error");
}
8. エラーの整形出力
fn might_fail(x: i32) -> Result<i32, String> {
if x == 0 { Err("Zero not allowed".into()) } else { Ok(100 / x) }
}
fn main() {
match might_fail(0) {
Ok(v) => println!("Success: {}", v),
Err(e) => eprintln!("Error occurred: {}", e),
}
}
9. 構造体のデバッグ出力
#[derive(Debug)]
struct User {
name: String,
age: u32,
}
fn main() {
let user = User { name: "Alice".into(), age: 30 };
println!("{:#?}", user); // pretty print
}
10. UUID生成・バリデーション
# Cargo.toml
uuid = { version = "1", features = ["v4"] }
use uuid::Uuid;
fn main() {
let id = Uuid::new_v4();
println!("Generated UUID: {}", id);
}
11. 関数結果の簡易キャッシュ(クロージャ+HashMap)
use std::collections::HashMap;
fn memoized_square() -> impl FnMut(i32) -> i32 {
let mut cache = HashMap::new();
move |x| {
*cache.entry(x).or_insert_with(|| {
println!("Computing for {}", x);
x * x
})
}
}
fn main() {
let mut square = memoized_square();
println!("{}", square(4));
println!("{}", square(4)); // キャッシュ使用
}
12. zip/gzip 圧縮と解凍
zip圧縮
zip = "0.6"
use std::fs::File;
use zip::write::FileOptions;
use std::io::{Write, Seek};
fn main() {
let path = "example.zip";
let file = File::create(path).unwrap();
let mut zip = zip::ZipWriter::new(file);
let options = FileOptions::default().compression_method(zip::CompressionMethod::Stored);
zip.start_file("hello.txt", options).unwrap();
zip.write_all(b"Hello, world!").unwrap();
zip.finish().unwrap();
}
13.spawn を使ったリアルなユースケース(例えば非同期ファイルIOやAPI呼び出しの並列実行)
- ✅ API呼び出しの並列実行
- ✅ 非同期ファイルI/O(読み込み処理)
- ✅ CPU負荷の高い処理との併用(
spawn_blocking
)
✅ ユースケース①:API呼び出しを並列で行う(reqwest
)
複数のURLに同時アクセスして、レスポンスを収集する例:
[dependencies]
tokio = { version = "1", features = ["full"] }
reqwest = { version = "0.11", features = ["json"] }
use tokio::task;
use reqwest::Client;
#[tokio::main]
async fn main() {
let urls = vec![
"https://jsonplaceholder.typicode.com/posts/1",
"https://jsonplaceholder.typicode.com/posts/2",
"https://jsonplaceholder.typicode.com/posts/3",
];
let client = Client::new();
let mut handles = vec![];
for url in urls {
let client = client.clone();
let url = url.to_string();
let handle = task::spawn(async move {
let res = client.get(&url).send().await.unwrap();
let body = res.text().await.unwrap();
println!("Got response from {}: {} chars", url, body.len());
});
handles.push(handle);
}
for handle in handles {
handle.await.unwrap();
}
}
解説
-
client.clone()
を使ってreqwest::Client
をスレッド間共有 - 各
spawn
で独立して API 呼び出し -
.await.unwrap()
で全てのレスポンス待機
✅ ユースケース②:非同期ファイル読み込み(tokio::fs
)
[dependencies]
tokio = { version = "1", features = ["full"] }
use tokio::fs;
use tokio::task;
#[tokio::main]
async fn main() {
let files = vec!["file1.txt", "file2.txt", "file3.txt"];
let mut handles = vec![];
for file in files {
let path = file.to_string();
let handle = task::spawn(async move {
match fs::read_to_string(&path).await {
Ok(content) => println!("{}: {} bytes", path, content.len()),
Err(e) => println!("{}: failed to read: {}", path, e),
}
});
handles.push(handle);
}
for handle in handles {
handle.await.unwrap();
}
}
解説
- 各ファイル読み込みを
spawn
で並列化 -
tokio::fs::read_to_string()
で非同期読み取り - 読み込みが重くても並列で進むので高速化できる
✅ ユースケース③:重い処理を spawn_blocking
で逃がす
use tokio::task;
#[tokio::main]
async fn main() {
let handle = task::spawn_blocking(|| {
// ブロッキングな重い処理(例:ファイル変換や圧縮)
std::thread::sleep(std::time::Duration::from_secs(2));
println!("Heavy processing done!");
});
println!("Non-blocking logic continues...");
handle.await.unwrap();
}
解説
-
spawn_blocking
は CPUバウンド or ブロッキングIO 向け - メインスレッドを止めずに処理できる
🧠 総まとめ:spawn のユースケース早見表
ユースケース例 | メリット |
---|---|
複数API呼び出し | まとめて非同期実行 → 応答時間の短縮 |
複数ファイル非同期読み込み | 並列で読み込み → 合計待ち時間を減らせる |
圧縮・変換などの重処理 |
spawn_blocking → メイン処理の遅延を防げる |
他にも、例えば:
- 並列スクレイピング
- 並列DBクエリ
- 並列画像変換やサムネ生成
など、非同期性やCPU負荷の分離が必要な場面では spawn
が威力を発揮します。