RustとOpenWeatherで天気を取得する

PythonからのステップアップでRustを選択する人が多いのではないでしょうか?

学習の進め方としては、Pythonなどの言語からの書き換えで覚えていくとやりやすいと思います。

例えばRustで天気情報を取得する簡単なプロジェクトに挑戦するのは非常に有意義です。

この記事では、OpenWeather APIを使用して神戸市の天気情報を取得するRustプログラムを作成しながら、各コードの詳細な解説を行います。

必要な準備

このプロジェクトを始めるにあたり、以下のものが必要です。

  • Rustの開発環境: まだ整っていない方は、環境構築の記事を参照してください。
  • Visual Studio Code(VS Code): コードエディタとして使用します。
  • OpenWeather APIのAPIキー: 天気情報を取得するために必要です。
  • インターネット接続: APIリクエストを送信するために必要です。

OpenWeather APIの登録とAPIキーの取得

OpenWeather APIを利用するには、無料アカウントを作成し、APIキーを取得する必要があります。

  1. OpenWeatherのサイトにアクセス: OpenWeatherに移動します。
  2. アカウントの作成:
    • 右上の「Sign Up」ボタンをクリックしてアカウントを作成します。
    • 必要な情報を入力して登録を完了させます。
  3. APIキーの取得:
    • ログイン後、ダッシュボードに移動します。
    • 「API keys」セクションで「Generate」ボタンをクリックして新しいAPIキーを生成します。
    • 生成されたAPIキーをコピーしておきます。このキーは後でRustプログラム内で使用します。

注意: APIキーは個人情報と同様に扱い、公開しないように注意してください。

プロジェクトの作成と依存関係の設定

まず、新しいRustプロジェクトを作成します。

PowerShellまたはVS Codeの統合ターミナルを開き、以下のコマンドを実行します。

cargo new weather_app
cd weather_app

これで、weather_appという名前のプロジェクトフォルダが作成され、移動します。

依存関係の設定

Rustでは、外部の機能を利用するために**クレート(crate)**というパッケージを使用します。

今回のプロジェクトでは、以下のクレートを使用します。

  • reqwest: HTTPリクエストを送信するためのクレート。
  • serdeserde_json: JSONデータの解析・シリアライズのためのクレート。
  • tokio: 非同期プログラミングをサポートするクレート。

Cargo.tomlの編集

プロジェクトのルートディレクトリにあるCargo.tomlファイルを開き、以下のように編集します。

[package]
name = "weather_app"
version = "0.1.0"
edition = "2021"

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

説明:

  • reqwest: HTTPリクエストを簡単に行うためのクレート。features = ["json"]はJSONのサポートを有効にします。
  • serdeserde_json: JSONデータをRustの構造体に変換するために使用します。
  • tokio: 非同期処理をサポートするランタイム。reqwestは非同期で動作するため、必要です。

Rustコードの作成

srcフォルダ内のmain.rsファイルを開き、以下のコードを記述します。

各セクションごとに詳細な解説を行います。

必要なクレートのインポートします。

use reqwest::Error;
use serde::Deserialize;

解説:

  • reqwest::Error: HTTPリクエスト中に発生する可能性のあるエラーを処理するために使用します。
  • serde::Deserialize: JSONデータをRustの構造体に変換するために必要です。

レスポンスデータをマッピングする構造体の定義

OpenWeather APIから返されるJSONデータをRustの構造体にマッピングします。必要なフィールドだけを取り出します。

#[derive(Deserialize, Debug)]
struct WeatherResponse {
    weather: Vec<Weather>,
    main: Main,
    name: String,
}

#[derive(Deserialize, Debug)]
struct Weather {
    description: String,
}

#[derive(Deserialize, Debug)]
struct Main {
    temp: f64,
    feels_like: f64,
    temp_min: f64,
    temp_max: f64,
    pressure: u32,
    humidity: u32,
}

解説:

  • WeatherResponse: APIレスポンス全体を表す構造体。weather, main, nameフィールドを含みます。
  • Weather: 天気の詳細を含む構造体。ここではdescriptionのみを取り出しています。
  • Main: 気温や湿度などの主要な気象情報を含む構造体。

APIリクエストの送信

非同期関数を使って、APIにリクエストを送り、レスポンスを取得します。

async fn fetch_weather(api_key: &str, city: &str) -> Result<WeatherResponse, Error> {
    let url = format!(
        "https://api.openweathermap.org/data/2.5/weather?q={}&appid={}&units=metric&lang=ja",
        city, api_key
    );

    let response = reqwest::get(&url).await?;
    let weather = response.json::<WeatherResponse>().await?;
    Ok(weather)
}

解説:

  • async fn fetch_weather: 非同期関数として定義しています。asyncキーワードにより、関数が非同期で動作することを示します。
  • api_keycity: APIキーと取得したい都市名を引数として受け取ります。
  • format!: URLを組み立てます。units=metricで摂氏温度を、lang=jaで日本語の天気説明を取得します。
  • reqwest::get(&url).await?: 非同期でHTTP GETリクエストを送信します。?演算子はエラーが発生した場合に関数から即座に返します。
  • response.json::<WeatherResponse>().await?: レスポンスのJSONデータをWeatherResponse構造体にデシリアライズします。

メイン関数の実装

#[tokio::main]
async fn main() {
    // APIキーと取得したい都市名を設定
    let api_key = "YOUR_API_KEY"; // ここに取得したAPIキーを入力
    let city = "Kobe";

    match fetch_weather(api_key, city).await {
        Ok(weather) => {
            println!("都市: {}", weather.name);
            println!("天気: {}", weather.weather[0].description);
            println!("温度: {}°C", weather.main.temp);
            println!("体感温度: {}°C", weather.main.feels_like);
            println!("最低温度: {}°C", weather.main.temp_min);
            println!("最高温度: {}°C", weather.main.temp_max);
            println!("気圧: {} hPa", weather.main.pressure);
            println!("湿度: {}%", weather.main.humidity);
        }
        Err(e) => {
            eprintln!("天気情報の取得に失敗しました: {}", e);
        }
    }
}

解説:

  • #[tokio::main]: Tokioランタイムを使用して非同期関数を実行するためのマクロ。main関数を非同期にします。
  • api_keycity: ここでAPIキーと都市名を設定します。YOUR_API_KEYを実際に取得したAPIキーに置き換えてください。
  • fetch_weather(api_key, city).await: 非同期関数fetch_weatherを呼び出し、天気情報を取得します。
  • match: 結果がOkErrかを判定し、適切に処理します。
  • println!: 取得した天気情報をコンソールに表示します。
  • eprintln!: エラーが発生した場合にエラーメッセージを表示します。

完成したmain.rsの全体像

use reqwest::Error;
use serde::Deserialize;

#[derive(Deserialize, Debug)]
struct WeatherResponse {
    weather: Vec<Weather>,
    main: Main,
    name: String,
}

#[derive(Deserialize, Debug)]
struct Weather {
    description: String,
}

#[derive(Deserialize, Debug)]
struct Main {
    temp: f64,
    feels_like: f64,
    temp_min: f64,
    temp_max: f64,
    pressure: u32,
    humidity: u32,
}

async fn fetch_weather(api_key: &str, city: &str) -> Result<WeatherResponse, Error> {
    let url = format!(
        "https://api.openweathermap.org/data/2.5/weather?q={}&appid={}&units=metric&lang=ja",
        city, api_key
    );

    let response = reqwest::get(&url).await?;
    let weather = response.json::<WeatherResponse>().await?;
    Ok(weather)
}

#[tokio::main]
async fn main() {
    // APIキーと取得したい都市名を設定
    let api_key = "YOUR_API_KEY"; // ここに取得したAPIキーを入力
    let city = "Kobe";

    match fetch_weather(api_key, city).await {
        Ok(weather) => {
            println!("都市: {}", weather.name);
            println!("天気: {}", weather.weather[0].description);
            println!("温度: {}°C", weather.main.temp);
            println!("体感温度: {}°C", weather.main.feels_like);
            println!("最低温度: {}°C", weather.main.temp_min);
            println!("最高温度: {}°C", weather.main.temp_max);
            println!("気圧: {} hPa", weather.main.pressure);
            println!("湿度: {}%", weather.main.humidity);
        }
        Err(e) => {
            eprintln!("天気情報の取得に失敗しました: {}", e);
        }
    }
}

注意: 上記コードのYOUR_API_KEYを、取得した実際のAPIキーに置き換えてください。

プログラムの実行

main.rsファイル内のapi_key変数に、取得したAPIキーを入力します。

let api_key = "YOUR_ACTUAL_API_KEY"; // ここに取得したAPIキーを入力

プログラムのビルドと実行

VS Codeの統合ターミナル(`Ctrl + “)を開き、以下のコマンドを実行します。

cargo run

説明:

  • cargo run: プログラムをビルドし、実行します。初回実行時には依存関係のクレートがダウンロードされるため、少し時間がかかる場合があります。

出力の確認

プログラムが正しく動作すると、以下のような出力が表示されます(実際の天気情報は異なります)。

都市: Kobe
天気: 晴れ
温度: 25°C
体感温度: 27°C
最低温度: 20°C
最高温度: 30°C
気圧: 1012 hPa
湿度: 60%

エラー時の対応:

  • APIキーが間違っている場合や、ネットワークに問題がある場合、エラーメッセージが表示されます。エラーメッセージを確認し、必要に応じて設定を見直してください。

まとめ

この記事では、以下のステップを通じて、RustでOpenWeather APIを使用して神戸市の天気情報を取得するプログラムを作成しました。

  1. 必要な準備: Rust開発環境とAPIキーの取得。
  2. プロジェクトの作成と依存関係の設定: cargo newでプロジェクトを作成し、Cargo.tomlで必要なクレートを追加。
  3. Rustコードの作成: クレートのインポート、構造体の定義、APIリクエストの送信、レスポンスの解析、メイン関数の実装。
  4. プログラムの実行: コードのビルドと実行、出力の確認。

Rustは強力で高速なシステムプログラミング言語であり、今回のようなAPIとの連携も簡単に行えます。さらに学習を進めるために、以下のトピックを検討してみてください。

  • エラーハンドリングの強化: より詳細なエラーメッセージの表示やリトライ機能の追加。
  • 環境変数の使用: APIキーをコード内にハードコーディングせず、環境変数から読み込む方法。
  • 非同期処理の理解: tokioを使ったより高度な非同期処理の実装。
  • 他のAPIとの連携: 複数のAPIを組み合わせて、より複雑なアプリケーションを作成。

Rustでの開発を楽しみながら、さらに多くのプロジェクトに挑戦してください。