[{"data":1,"prerenderedAt":816},["ShallowReactive",2],{"/ja-jp/blog/learn-advanced-rust-programming-with-a-little-help-from-ai-code-suggestions":3,"navigation-ja-jp":44,"banner-ja-jp":455,"footer-ja-jp":465,"blog-post-authors-ja-jp-Michael Friedrich":699,"blog-related-posts-ja-jp-learn-advanced-rust-programming-with-a-little-help-from-ai-code-suggestions":713,"blog-promotions-ja-jp":755,"next-steps-ja-jp":807},{"id":4,"title":5,"authorSlugs":6,"authors":8,"body":10,"category":11,"categorySlug":11,"config":12,"content":16,"date":20,"description":17,"extension":28,"externalUrl":29,"featured":14,"heroImage":19,"isFeatured":14,"meta":30,"navigation":31,"path":32,"publishedDate":20,"rawbody":33,"seo":34,"slug":13,"stem":38,"tagSlugs":39,"tags":42,"template":15,"updatedDate":27,"__hash__":43},"blogPosts/ja-jp/blog/learn-advanced-rust-programming-with-a-little-help-from-ai-code-suggestions.yml","AIを活用して学ぶ、Rustの高度なプログラミング",[7],"michael-friedrich",[9],"Michael Friedrich","20年以上前に新しいプログラミング言語を学び始めたとき、私たちは6枚のCD-ROMからインストールしたVisual Studio 6のMSDNライブラリにアクセスしていました。ペンと紙でアルゴリズムを記録し、設計パターンの本を読み漁り、MSDNで正しい型を調べていましたが、こうした作業に時間がかかることが多々ありました。しかし、リモートコラボレーションや人工知能（AI）の時代が到来し、プログラミング言語の学び方は根本的に変わりました。今では[リモート開発環境](https://about.gitlab.com/blog/quick-start-guide-for-gitlab-workspaces/)をすばやく立ち上げて画面を共有し、グループでのプログラミングセッションを行えるようになりました。[GitLab Duoのコード提案](https://about.gitlab.com/ja-jp/gitlab-duo-agent-platform/)を使用すれば、AIというインテリジェントなパートナーにいつでも頼ることができます。コード提案機能は、ユーザーのプログラミングのスタイルと経験に基づいて学習します。この機能は、インプットとコンテキストさえあれば、最も効率的な提案を提供してくれるのです。\n\nこのチュートリアルでは、[入門編のブログ記事](/blog/learning-rust-with-a-little-help-from-ai-code-suggestions-getting-started/)（英語）からさらに一歩踏み込み、シンプルなフィードリーダーアプリケーションの設計と作成に取り組みます。\n\n- 準備\n    - コード提案\n- Rustの学習の継続\n    - 「Hello, Reader!」アプリ\n    - プロジェクトの初期化\n    - RSSフィードURLの定義\n- モジュール\n    - main() 関数によるモジュール関数の呼び出し\n- クレート\n    - feed-rs：XMLフィードの解析\n- ランタイム設定：プログラム引数\n    - ユーザー入力のエラーハンドリング\n- 永続性とデータ保存\n- 最適化\n    - 非同期実行\n    - スレッドの生成\n    - 関数スコープ、スレッド、クロージャ\n- フィードのXMLの解析およびオブジェクト型への変換\n    - 汎用的なフィードデータ型のマッピング\n    - Option::unwrap()によるエラーハンドリング\n- ベンチマーク\n    - 逐次実行と並列実行のベンチマークの比較\n    - Rustのキャッシュを使用したCI/CD\n- 次のステップ\n    - 非同期学習の演習\n    - フィードバックの共有\n\n## 準備\nソースコードを参照する前に、[VS Code](/blog/learning-rust-with-a-little-help-from-ai-code-suggestions-getting-started/#vs-code)と[Rustの開発環境](/blog/learning-rust-with-a-little-help-from-ai-code-suggestions-getting-started/#development-environment-for-rust)をセットアップしてください。\n\n### コード提案\n実際に提案機能を検証する前に、まずはこの機能の使い方を理解しましょう。GitLab Duoのコード提案は、特定のキーボードショートカットを必要とせず、キーを入力するだけで使用できます。たとえば、コード提案を受け入れるには、`Tab` キーを押します。また、新しく作成したコードの方が、既存のコードをリファクタリングしたものよりもエラー発生率が低くなる点も覚えておきましょう。AIは非決定的であるため、一度削除したコード提案は再び同じ形で提示されない可能性があります。コード提案は現在ベータ版であり、GitLabは、同機能が生成するコンテンツの全体的な精度向上に取り組んでいます。\n\n**ヒント**：コード提案の最新リリースでは、複数行の指示文に対応しています。ニーズに合わせて指示文を調整することで、よりよい提案を得ることができます。\n\n```rust\n\n    // Create a function that iterates over the source array\n    // and fetches the data using HTTP from the RSS feed items.\n    // Store the results in a new hash map.\n    // Print the hash map to the terminal.\n\n```\n\nVS Code拡張機能のオーバーレイは、提案を提示する際に表示されます。提案された行を受け入れるには `Tab` キーを使用し、一単語だけ受け入れるには `Cmd + 右カーソル` キーを使用します。さらに、三点リーダーメニューからツールバーを常に表示するオプションを選択することも可能です。\n\n![VS CodeにおけるGitLab Duoの指示文とコード提案のオーバーレイ](https://about.gitlab.com/images/blogimages/learn-rust-with-ai-code-suggestions-advanced-programming/vs_code_code_suggestions_options_overlay_keep_toolbar.png){: .shadow}\n\n## Rustの学習の継続\nでは、引き続きRustについて学んでいきましょう。Rustは[コード提案でサポートされている言語](https://docs.gitlab.com/ja-jp/user/project/repository/code_suggestions/)のひとつです。[Rust by Example](https://doc.rust-lang.org/rust-by-example/)（英語）では、初心者に最適なチュートリアルが提供されており、公式の[Rust Book](https://doc.rust-lang.org/book/)（英語）を確認しながら進めると効果的です。どちらのリソースもこのブログの執筆の際に参考にしています。\n\n### 「Hello, Reader!」アプリ\nアプリケーションの作成やRustの学習には、さまざまなアプローチがあります。その中には、既存のRustライブラリ、いわゆる `Crates` を利用するものがあり、このブログ記事の後半でも使用します。たとえば、画像を処理して、その結果をファイルに書き込むコマンドラインアプリを作成することができます。また、昔ながらの迷路をクリアしたり、数独を解いたりするアプリケーションを作成するのも楽しいでしょうし、ゲーム開発を行うこともできます。たとえば、[Hands-on Rust](https://hands-on-rust.com/)（英語）というガイドブックでは、ダンジョンクローラー（迷宮探検）ゲームを作りながら、Rustを体系的に学習できるコースを提供しています。筆者の同僚であるFatima Sarah Khalidは、[AIを活用してC++でDragon Realmの制作](/blog/building-a-text-adventure-using-cplusplus-and-code-suggestions/)（英語）を始めたそうです。ぜひそちらもご覧ください。\n\nここで、実際の問題を解決するのに役立つ実用的なユースケースをひとつご紹介します。それは、異なるソースから重要な情報をRSSフィードに集約するというものです。RSSフィードには、セキュリティリリース、ブログ記事、およびソーシャルディスカッションフォーラム（Hacker Newsなど）の最新情報が含まれます。アップデートに含まれる特定のキーワードやバージョンを絞り込んで検索したいと考えることがよくあります。こうしたニーズを基に、アプリケーションの要件をリスト化できます。具体的には、次のような要件です。\n\n1. HTTPウェブサイト、REST API、RSSフィードなどの異なるソースからデータをフェッチ（取得）する（最初の段階ではRSSフィードを使用）。\n1. データを解析する。\n1. データをユーザーに提示する、またはディスクに書き込む。\n1. パフォーマンスを最適化する。\n\nこのブログ記事の学習ステップを完了すると、以下のサンプルアプリケーションの出力が得られます。\n\n![VS Codeのターミナルでのcargo runの実行と、形成されたフィードエントリの出力](https://about.gitlab.com/images/blogimages/learn-rust-with-ai-code-suggestions-advanced-programming/vs_code_terminal_cargo_run_formatted_output_final.png)\n\nアプリケーションはモジュール化されている必要があり、また、将来的にデータ型やフィルター、アクションをトリガーするフックを追加できる基盤も整えておく必要があります。\n\n### プロジェクトの初期化\nリマインダー：`cargo init` をプロジェクトのルートディレクトリで実行すると、 `main ()` エントリポイントを含んだファイル構造が作成されます。これを踏まえ、次のステップでは、Rustのモジュールの作成と使用方法を学びます。\n\n`learn-rust-ai-app-reader` という名前のディレクトリを新規作成し、そのディレクトリに移動したら、`cargo init` を実行します。このコマンドは、暗黙的に `git init` を実行し、新しいGitリポジトリをローカルで初期化します。残りのステップでは、Gitリモートリポジトリのパスを設定していきます。たとえば、`https://gitlab.com/gitlab-da/use-cases/ai/learn-with-ai/learn-rust-ai-app-reader` のように設定します（ご自身のネームスペースに合わせてパスを置き換えてください）。[Gitリポジトリをプッシュ](https://about.gitlab.com/ja-jp/blog/mastering-the-basics-of-git-push-tag/)すると、[GitLabで新しいプロジェクトが自動的に作成](https://docs.gitlab.com/ja-jp/user/project/#create-a-new-project-with-git-push)されます。\n\n```shell\nmkdir learn-rust-ai-app-reader\ncd learn-rust-ai-app-reader\n\ncargo init\n\ngit remote add origin https://gitlab.com/gitlab-da/use-cases/ai/learn-with-ai/learn-rust-ai-app-reader.git\ngit push --set-upstream origin main\n```\n\n新しく作成されたディレクトリからVS Codeを開きます。`code` コマンドラインインターフェース（CLI）によって、macOS上で新しいVS Codeのウィンドウが起動します。\n\n```shell\ncode .\n```\n\n### RSSフィードURLの定義\n新しいHashMapを追加して、`src/main.rs` ファイル内の `main()` 関数にRSSフィードのURLを保存します。複数行の指示コメントを入力することで、GitLab Duoのコード提案に対し、[`HashMap`](https://doc.rust-lang.org/stable/std/collections/struct.HashMap.html) オブジェクトを作成して、Hacker NewsやTechCrunchのデフォルト値で初期化するよう指示できます。注：提案に含まれるURLが正しいことを確認してください。\n\n```rust\nfn main() {##$_0A$##    // Define RSS feed URLs in the variable rss_feeds##$_0A$##    // Use a HashMap##$_0A$##    // Add Hacker News and TechCrunch##$_0A$##    // Ensure to use String as type##$_0A$####$_0A$##}\n```\n\nコードコメントでは以下の点を指示しています。\n\n1. 変数名 `rss_feeds`\n2. `HashMap` タイプ\n3. 3. 初期のseedキー/バリューペア\n4. String 型（`to_string ()` 呼び出しで確認可能）\n\n考えられるコードの例は次の通りです。\n\n```rust\nuse std::collections::HashMap;\n\nfn main() {##$_0A$##    // Define RSS feed URLs in the variable rss_feeds##$_0A$##    // Use a HashMap##$_0A$##    // Add Hacker News and TechCrunch##$_0A$##    // Ensure to use String as type##$_0A$##    let rss_feeds = HashMap::from([##$_0A$##        (\"Hacker News\".to_string(), \"https://news.ycombinator.com/rss\".to_string()),##$_0A$## (\"TechCrunch\".to_string(), \"https://techcrunch.com/feed/\".to_string()),##$_0A$## ]);##$_0A$####$_0A$##}\n```\n\n![VS Codeにおける、コード提案を活用したHacker NewsとTechCrunchのRSSフィードURLの提案](https://about.gitlab.com/images/blogimages/learn-rust-with-ai-code-suggestions-advanced-programming/vs_code_main_array_rss_feed_urls_suggested.png)\n\nVS Codeで新しいターミナルを開き（cmd + shift + p で `terminal` を検索）、`cargo build` を実行して変更をビルドします。エラーメッセージが表示され、`use std::collections::HashMap;` のインポートを追加するよう指示されます。\n\n次のステップでは、RSSフィードのURLを使用して操作を行います。[以前のブログ記事](/blog/learning-rust-with-a-little-help-from-ai-code-suggestions-getting-started/)（英語）では、コードを関数に分割する方法を解説しました。今回は、リーダーアプリケーションのコードをよりモジュール化して整理し、Rustのモジュールを使用します。\n\n## モジュール\n[モジュール](https://doc.rust-lang.org/rust-by-example/mod.html)は、 コードの整理に役立ちます。また、関数をモジュールのスコープ内に隠し、main()スコープからのアクセスを制限することも可能です。リーダーアプリケーションでは、RSSフィードのコンテンツを取得し、XMLレスポンスを解析したいため、`main()` からは、`get_feeds()` 関数のみにアクセスできるようにし、それ以外の機能はモジュール内のみで使用できるように制限します。\n\n`src/` ディレクトリに `feed_reader.rs` という名前の新しいファイルを作成します。コード提案に、`feed_reader` という名前の公開モジュールと、String HashMapをインプットとして受け取る `get_feeds()` という公開関数を作成するよう指示します。重要：[Rustのモジュール構造](https://doc.rust-lang.org/book/ch07-02-defining-modules-to-control-scope-and-privacy.html)に従って、ファイル名とモジュール名を同じにする必要があります。\n\n![コード提案：関数と入力型を使った公開モジュールの作成](https://about.gitlab.com/images/blogimages/learn-rust-with-ai-code-suggestions-advanced-programming/code_suggestions_rust_public_module_function_input.png){: .shadow}\n\nコード提案に入力変数名と型を指定すると、必要な `std::collections::HashMap` モジュールが自動的にインポートされます。ヒント：最適な結果を得られるよう、コメントを使って変数の型を調整してみましょう。また、Rustでは関数のパラメータをオブジェクト参照として渡すのがベストプラクティスとされています。以下に例を示します。\n\n```rust\n// Create public module feed_reader\n// Define get_feeds() function which takes rss_feeds as String HashMap reference as input\npub mod feed_reader {##$_0A$##    use std::collections::HashMap;##$_0A$####$_0A$##    pub fn get_feeds(rss_feeds: &HashMap\u003CString, String>) {##$_0A$##        // Do something with the RSS feeds##$_0A$##    }##$_0A$##}\n```\n\n![コード提案：`get_feeds()` 関数と提案された入力変数を含む公開モジュール](https://about.gitlab.com/images/blogimages/learn-rust-with-ai-code-suggestions-advanced-programming/code_suggestions_rust_public_module_function_input.png){: .shadow}\n\n関数内では、コード提案に以下の手順を指示します。\n\n1. `// Iterate over the RSS feed URLs` （RSSフィードURLを反復処理する）\n2. `// Fetch URL content` （URLコンテンツを取得する）\n3. `// Parse XML body` （XMLの本文を解析する）\n4. `// Print the result`  (結果を出力する)\n\n![コード提案：`get_feeds()` 関数を含む公開モジュール、ステップ1：イテレート](https://about.gitlab.com/images/blogimages/learn-rust-with-ai-code-suggestions-advanced-programming/code_suggestions_rust_module_function_01_iterate.png){: .shadow}\n\n![コード提案：`get_feeds()` 関数を含む公開モジュール、ステップ2：URLコンテンツの取得](https://about.gitlab.com/images/blogimages/learn-rust-with-ai-code-suggestions-advanced-programming/code_suggestions_rust_module_function_02_fetch_content.png){: .shadow}\n\n![コード提案：`get_feeds()` 関数を含む公開モジュール、ステップ3：XML本文の解析](https://about.gitlab.com/images/blogimages/learn-rust-with-ai-code-suggestions-advanced-programming/code_suggestions_rust_module_function_03_parse_body.png){: .shadow}\n\n![コード提案：`get_feeds()` 関数を含む公開モジュール、ステップ4：結果の出力](https://about.gitlab.com/images/blogimages/learn-rust-with-ai-code-suggestions-advanced-programming/code_suggestions_rust_module_function_04_print_result.png){: .shadow}\n\n次のコードが提案されます。\n\n```rust\n// Create public module feed_reader\n// Define get_feeds() function which takes rss_feeds as String HashMap reference as input\npub mod feed_reader {##$_0A$##    use std::collections::HashMap;##$_0A$####$_0A$##    pub fn get_feeds(rss_feeds: &HashMap\u003CString, String>) {##$_0A$##        // Iterate over the RSS feed URLs##$_0A$##        for (name, url) in rss_feeds {##$_0A$## println!(\"{}: {}\", name, url);##$_0A$####$_0A$##            // Fetch URL content##$_0A$##            let body = reqwest::blocking::get(url).unwrap().text().unwrap();##$_0A$####$_0A$## // Parse XML body##$_0A$##            let parsed_body = roxmltree::Document::parse(&body).unwrap();##$_0A$####$_0A$##            // Print the result##$_0A$##            println!(\"{:#?}\", parsed_body);##$_0A$##        }##$_0A$##    }##$_0A$##}\n```\n\nここで新しいキーワード [`unwrap()`](https://doc.rust-lang.org/rust-by-example/error/option_unwrap.html)が登場します。Rustは `null` 値をサポートしておらず、すべての値に対して [`Option` 型](https://doc.rust-lang.org/rust-by-example/std/option.html)を使用します。たとえば、`Text` や `String` といった特定のラップされた型を使用することが確定している場合、`unwrap()` メソッドを呼び出してその値を取得できます。ただし、値が `None` の場合、`unwrap()` メソッドはパニックを起こします。\n\n**注意**：コード提案は、`// Fetch URL content` のコメント指示に従って、`reqwest::blocking::get` 関数を参照します。[`reqwest`](https://docs.rs/reqwest/latest/reqwest/)というクレーと名は意図的なものであり、タイポではありません。非同期リクエストとブロッキングリクエストの処理に役立つ、優れた利便性と高レベルのHTTPクライアントの機能を提供します。\n\nXMLの本文の解析は難しく、異なる結果が得られることがあります。また、スキーマはRSSフィードURLごとに異なる可能性があります。まずは `get_feeds()` 関数を呼び出し、その後でコードの改善に取り組みましょう。\n\n### main() 関数によるモジュール関数の呼び出し\n\n現在、main() 関数は `get_feeds()` 関数を認識していないため、まずそのモジュールをインポートする必要があります。他のプログラミング言語では `include` や `import` といったキーワードを目にすることがありますが、Rustのモジュールシステムは異なります。\n\nモジュールはパスディレクトリに整理されます。今回の例では、両方のソースファイルが同じディレクトリレベルに存在しています。`feed_reader.rs` はクレートとして解釈され、その中に `feed_reader` というモジュールがあり、そのモジュールが `get_feeds()` 関数を定義しています。\n\n```text\nsrc/\n  main.rs\n  feed_reader.rs\n\n```\n\n`feed_reader.rs` ファイルの `get_feeds()` 関数にアクセスするためには、まず `main.rs` のスコープに[モジュールパスを取り込み](https://doc.rust-lang.org/book/ch07-04-bringing-paths-into-scope-with-the-use-keyword.html)、その後フルパスで関数を呼び出します。\n\n```rust\nmod feed_reader;\n\nfn main() {\n\n    feed_reader::feed_reader::get_feeds(&rss_feeds);\n\n```\n\nあるいは、`use` キーワードを使って関数のフルパスをインポートし、その後短い関数名で呼び出すこともできます。\n\n```rust\nmod feed_reader;\nuse feed_reader::feed_reader::get_feeds;\n\nfn main() {\n\n    get_feeds(&rss_feeds);\n\n```\n\n**ヒント**：Rustのモジュールシステムを視覚的によりよく理解するために、[Rustモジュールシステムについてわかりやすく説明したブログ記事](https://www.sheshbabu.com/posts/rust-module-system/)（英語）をお読みください。\n\n```diff\nfn main() {\n    // ...\n\n    // Print feed_reader get_feeds() output\n    println!(\"{}\", feed_reader::get_feeds(&rss_feeds));\n\n```\n\n```rust\nuse std::collections::HashMap;\n\nmod feed_reader;\n// Alternative: Import full function path\n//use feed_reader::feed_reader::get_feeds;\n\nfn main() {##$_0A$##    // Define RSS feed URLs in the variable rss_feeds##$_0A$##    // Use a HashMap##$_0A$##    // Add Hacker News and TechCrunch##$_0A$##    // Ensure to use String as type##$_0A$##    let rss_feeds = HashMap::from([##$_0A$##        (\"Hacker News\".to_string(), \"https://news.ycombinator.com/rss\".to_string()),##$_0A$## (\"TechCrunch\".to_string(), \"https://techcrunch.com/feed/\".to_string()),##$_0A$## ]);##$_0A$####$_0A$##    // Call get_feeds() from feed_reader module##$_0A$## feed_reader::feed_reader::get_feeds(&rss_feeds);##$_0A$##    // Alternative: Imported full path, use short path here.##$_0A$## //get_feeds(&rss_feeds);##$_0A$##}\n```\n\nターミナルで `cargo build` を再実行しコードをビルドします。\n\n```shell\ncargo build\n```\n\n以下は、HTTPリクエストやXML解析に関する一般的なコードを参照した際に発生する可能性のあるビルドエラーの例です。\n\n1. エラー： `could not find blocking in reqwest`\n解決策：`Config.toml` ファイルで `reqwest` クレートの `blocking` 機能を有効にします（`reqwest = { version = \"0.11.20\", features = [\"blocking\"] }`）\n2. エラー：`failed to resolve: use of undeclared crate or module reqwest`\n解決策：`reqwest` クレートを追加します\n3. エラー：`failed to resolve: use of undeclared crate or module roxmltree`\n解決策：`roxmltree` クレートを追加します\n\n```shell\nvim Config.toml\n\nreqwest = { version = \"0.11.20\", features = [\"blocking\"] }\n```\n\n```shell\ncargo add reqwest\ncargo add roxmltree\n```\n\n**ヒント**：エラーメッセージの文字列を `Rust \u003Cerror message>` としてブラウザで検索し、欠落しているクレートが利用可能であるかどうか確認しましょう。通常、検索結果に「crates.io」が表示され、そこから不足している依存関係を追加できます。\n\nビルドが成功したら、`cargo run` でコードを実行し、Hacker NewsのRSSフィードの出力を確認します。\n\n![VS Codeのターミナルでcargo runを実行して、Hacker NewsのXMLフィードを取得](https://about.gitlab.com/images/blogimages/learn-rust-with-ai-code-suggestions-advanced-programming/vs_code_terminal_fetch_rss_feed_output_hacker_news.png){: .shadow}\n\nXML本文を人間が読める形式に解析するにはどうすればいいでしょうか？次のセクションでは、既存の解決策とRustのクレートがどのように機能するかを説明します。\n\n## クレート\nRSSフィードは共通のプロトコルと仕様に基づいており、XMLを解析して下位のオブジェクト構造を理解するのは、例えば車輪のように、すでに存在しているものを再び深く掘り下げて再発明するかのようです。。こういったタスクに対するおすすめのアプローチは、過去に同じ問題に直面した人がいないか、また、その問題を解決するためのコードがすでに作られていないかを調べることです。\n\nRustでの再利用可能なライブラリコードは [`Crates`](https://doc.rust-lang.org/rust-by-example/crates.html)と呼ばれる単位で整理され、パッケージで提供されます。これらはcrates.ioのパッケージレジストリで利用可能です。これらの依存関係をプロジェクトに追加するには、`Config.toml` ファイルの `[dependencies]` セクションを編集するか、`cargo add \u003Cname>` コマンドを使用します。\n\nリーダーアプリケーションでは、[feed-rs クレート](https://crates.io/crates/feed-rs)を使用したいため、新しいターミナルを開き、次のコマンドを実行してください。\n\n```shell\ncargo add feed-rs\n```\n\n![VS Codeのターミナル：クレートを追加し、Config.tomlで確認](https://about.gitlab.com/images/blogimages/learn-rust-with-ai-code-suggestions-advanced-programming/vs_code_rust_crate_add_feed-rs_explained.png)\n\n### feed-rs：XMLフィードの解析\n`src/feed_reader.rs` に移動し、XML本文を解析する部分に対して修正を加えます。コード提案は、`feed-rs` クレートの `parser::parse` 関数をどのように呼び出すか理解していますが、ひとつだけ特別な点があります。`feed-rs` は文字列をrawバイトとして入力し、自らエンコーディングを判断します。ただし、コメントに指示を追加することで、期待の結果を得ることは可能です。\n\n```rust\n\n            // Parse XML body with feed_rs parser, input in bytes\n            let parsed_body = feed_rs::parser::parse(body.as_bytes()).unwrap();\n\n```\n\n![コード提案：`get_feeds()` 関数を含む公開モジュール、ステップ5：XMLパーサーをfeed-rsに変更](https://about.gitlab.com/images/blogimages/learn-rust-with-ai-code-suggestions-advanced-programming/code_suggestions_rust_module_function_05_use_feed_rs_to_parse.png){: .shadow}\n\n`feed-rs` を使用するメリットは即座に可視化できるものでなく、`cargo run` で出力を確認するとその効果が明らかになります。すべてのキーと値がそれぞれのRustオブジェクト型にマッピングされ、さらに高度な処理に利用できるようになります。\n\n![VS Codeのターミナル、cargo runを実行してHacker NewsのXMLフィードを取得](https://about.gitlab.com/images/blogimages/learn-rust-with-ai-code-suggestions-advanced-programming/vs_code_terminal_fetch_rss_feed_output_hacker_news_feed_rs.png){: .shadow}\n\n## ランタイム設定：プログラム引数\nここまで、コンパイル時にバイナリに埋め込まれ、ハードコードされたRSSフィードの値を使用してプログラムを実行してきました。次のステップでは、実行時にRSSフィードを設定できるようにします。\n\nRustでは、標準miscライブラリに[プログラム引数](https://doc.rust-lang.org/rust-by-example/std_misc/arg.html)を処理するための機能が用意されています。[プログラム引数を解析](https://doc.rust-lang.org/rust-by-example/std_misc/arg/matching.html)することで、高度なプログラム引数パーサー（たとえば[clap](https://docs.rs/clap/latest/clap/)クレート）を使用したり、プログラムパラメータを構成ファイルやフォーマット（[TOML](https://toml.io/en/)やYAML）に移したりするよりも、簡単かつ効率的に学習を進めることができます。実際にいくつかの方法を試した結果、学習効果を最大限に高めるには、この方法が最適であると判断しましたが、唯一の方法ではありません。他の方法でRSSフィードの設定を試してみる価値もあります。\n\n単純な解決策としては、コマンドパラメータを `\"name,url\"` の文字列ペアとして渡してから、`,` で分割して名前とURLの値を抽出します。コード提案に、これらの操作を実行して新しい値で `rss_feeds` ハッシュマップを拡張するようコメントで指示します。変数が変更可能でない可能性があるため、`let rss_feeds` を `let mut rss_feeds` に変更する必要がある点にご注意ください。\n\n`src/main.rs` に移動し、`rss_feeds` 変数の後に次のコードを `main()` 関数の追加します。まずはコメントでプログラム引数を定義し、提案されたコードスニペットを確認します。\n\n```rust\n\n    // Program args, format \"name,url\"\n    // Split value by , into name, url and add to rss_feeds\n\n```\n\n![プログラム引数に関するコード提案、および rss_feeds 変数のために名前とURLの値を分割するコード提案](https://about.gitlab.com/images/blogimages/learn-rust-with-ai-code-suggestions-advanced-programming/code_suggestions_rust_program_args_boring_solution.png){: .shadow}\n\nコード全体の例は次のようになります。\n\n```rust\nfn main() {##$_0A$##    // Define RSS feed URLs in the variable rss_feeds##$_0A$##    // Use a HashMap##$_0A$##    // Add Hacker News and TechCrunch##$_0A$##    // Ensure to use String as type##$_0A$##    let mut rss_feeds = HashMap::from([##$_0A$##        (\"Hacker News\".to_string(), \"https://news.ycombinator.com/rss\".to_string()),##$_0A$## (\"TechCrunch\".to_string(), \"https://techcrunch.com/feed/\".to_string()),##$_0A$## ]);##$_0A$####$_0A$##    // Program args, format \"name,url\"##$_0A$##    // Split value by , into name, url and add to rss_feeds##$_0A$##    for arg in std::env::args().skip(1) {##$_0A$##        let mut split = arg.split(\",\");##$_0A$##        let name = split.next().unwrap();##$_0A$##        let url = split.next().unwrap();##$_0A$##        rss_feeds.insert(name.to_string(), url.to_string());##$_0A$##    }##$_0A$####$_0A$##    // Call get_feeds() from feed_reader module##$_0A$## feed_reader::feed_reader::get_feeds(&rss_feeds);##$_0A$##    // Alternative: Imported full path, use short path here.##$_0A$## //get_feeds(&rss_feeds);##$_0A$##}\n```\n\nプログラムの引数を `cargo run` コマンドに直接渡すことができます。その際は引数の前に `--`をつけます。 すべての引数をダブルクォートで囲み、名前の後にカンマを付けてRSSフィードのURLを引数として渡します。引数は空白で区切ります。\n\n```shell\ncargo build\n\ncargo run -- \"GitLab Blog,https://about.gitlab.com/atom.xml\" \"CNCF,https://www.cncf.io/feed/\"\n```\n\n![VS Codeターミナル、GitLabブログのRSSフィード出力例](https://about.gitlab.com/images/blogimages/learn-rust-with-ai-code-suggestions-advanced-programming/vs_code_terminal_gitlab_blog_rss_feed_example.png){: .shadow}\n\n### ユーザー入力のエラーハンドリング\n提供されたユーザー入力がプログラムで想定される内容と異なる場合、[エラーを発生](https://doc.rust-lang.org/rust-by-example/error.html)させ、呼び出し元がプログラム引数を修正できるようにする必要があります。たとえば、不正なURL形式が渡された場合、それをランタイムエラーとして処理する必要があります。コード提案に対し、URLが無効な場合はエラーをスローするようコメントで指示します。\n\n```rust\n\n    // Ensure that URL contains a valid format, otherwise throw an error\n\n```\n\nひとつの解決策として、`url` 変数が `http://` または `https://` で始まっているかを確認し、そうでなければ [panic! マクロ](https://doc.rust-lang.org/rust-by-example/std/panic.html)を使ってエラーをスローする方法があります。コード全体の例は次のようになります。\n\n```rust\n\n    // Program args, format \"name,url\"\n    // Split value by , into name, url and add to rss_feeds\n    for arg in std::env::args().skip(1) {\n        let mut split = arg.split(\",\");\n        let name = split.next().unwrap();\n        let url = split.next().unwrap();\n\n        // Ensure that URL contains a valid format, otherwise throw an error\n        if !url.starts_with(\"http://\") && !url.starts_with(\"https://\") {\n            panic!(\"Invalid URL format: {}\", url);\n        }\n\n        rss_feeds.insert(name.to_string(), url.to_string());\n    }\n\n```\n\n任意のURL文字列から `:` を削除してエラーハンドリングをテストします。`RUST_BACKTRACE=full` 環境変数を追加すると、`panic()` 呼び出しが発生した際に詳細な出力を取得できます。\n\n```shell\nRUST_BACKTRACE=full cargo run -- \"GitLab Blog,https://about.gitlab.com/atom.xml\" \"CNCF,https//www.cncf.io/feed/\"\n```\n\n![間違ったURL形式によるパニックエラーのバックトレースが表示されたVS Codeターミナル](https://about.gitlab.com/images/blogimages/learn-rust-with-ai-code-suggestions-advanced-programming/vs_code_terminal_url_format_error_panic_backtrace.png){: .shadow}\n\n## 永続性とデータ保存\nフィードデータを保存する単純な解決策は、解析されたデータを新しいファイルに書き出すことです。コード提案に、RSSフィードの名前と現在のISO日付を含むパターンでファイルを保存するよう指示します。\n\n```rust\n\n    // Parse XML body with feed_rs parser, input in bytes\n    let parsed_body = feed_rs::parser::parse(body.as_bytes()).unwrap();\n\n    // Print the result\n    println!(\"{:#?}\", parsed_body);\n\n    // Dump the parsed body to a file, as name-current-iso-date.xml\n    let now = chrono::offset::Local::now();\n    let filename = format!(\"{}-{}.xml\", name, now.format(\"%Y-%m-%d\"));\n    let mut file = std::fs::File::create(filename).unwrap();\n    file.write_all(body.as_bytes()).unwrap();\n\n```\n\nこの処理の一例として、[chronoクレート](https://crates.io/crates/chrono)を使用する方法があります。`cargo add chrono` を実行して追加し、その後 `cargo build` と `cargo run` を再度実行します。\n\nファイルは `cargo run` が実行されたディレクトリに保存されます。バイナリを `target/debug/` ディレクトリで直接実行している場合、すべてのファイルはそのディレクトリにダンプされます。\n\n![CNCFのRSSフィード内容を含むファイルがディスクに保存された状態のVS Code](https://about.gitlab.com/images/blogimages/learn-rust-with-ai-code-suggestions-advanced-programming/vs_code_cncf_rss_feed_saved_on_disk.png)\n\n## 最適化\n`rss_feeds` 変数内のエントリは逐次実行されています。もし100件以上のURLがリストに設定されている場合、データの取得と処理にはかなりの時間がかかるでしょう。複数のフェッチリクエストを並行して実行できたらどうでしょうか？\n\n### 非同期実行\nRustでは、[スレッド](https://doc.rust-lang.org/book/ch16-01-threads.html)を使用した非同期実行が可能です。\n\n最も簡単な解決策は、各RSSフィードURLごとにスレッドを生成することです。最適化戦略については後のセクションで説明します。並行実行を開始する前に、 `time` コマンドの前に`cargo run` をつけて、逐次実行コードの実行時間を測定しましょう。\n\n```text\ntime cargo run -- \"GitLab Blog,https://about.gitlab.com/atom.xml\" \"CNCF,https://www.cncf.io/feed/\"\n\n0.21s user 0.08s system 10% cpu 2.898 total\n```\n\nなお、この演習では、手動のコーディング作業が多くなる場合があります。並列実行の影響をより効果的に比較するには、逐次実行の動作状態を新しいGitコミットとブランチ `sequential-exec` に保存してください。\n```shell\ngit commit -avm \"Sequential execution working\"\ngit checkout -b sequential-exec\ngit push -u origin sequential-exec\n\ngit checkout main\n```\n\n### スレッドの生成\n`src/feed_reader.rs` を開き、`get_feeds()` 関数をリファクタリングします。まず、現在の状態をGitコミットで保存し、その後、関数のスコープ内の内容を削除します。次に、コード提案への指示と以下のコードコメントを入力します。\n\n1. `// Store threads in vector`：スレッドのハンドルをベクターに格納し、関数呼び出しの最後にスレッドが終了するまで待機できるようにします。\n2. `// Loop over rss_feeds and spawn threads`：すべてのRSSフィードを反復処理するためのコードを作成し、新しいスレッドを作成します。\n\n`thread` モジュールと `time` モジュールを扱うには、次の `use` 文を追加します。\n\n```rust\n\n    use std::thread;\n    use std::time::Duration;\n\n```\n\nその後、forループを閉じるまでコードを書き進めます。コード提案は、自動的にスレッドハンドルを `threads` ベクター変数に追加し、関数の最後でスレッドを結合（join）するよう提案します。\n\n```rust\n\n    pub fn get_feeds(rss_feeds: &HashMap\u003CString, String>) {\n\n        // Store threads in vector\n        let mut threads: Vec\u003Cthread::JoinHandle\u003C()>> = Vec::new();\n\n        // Loop over rss_feeds and spawn threads\n        for (name, url) in rss_feeds {\n            let thread_name = name.clone();\n            let thread_url = url.clone();\n            let thread = thread::spawn(move || {\n\n            });\n            threads.push(thread);\n        }\n\n        // Join threads\n        for thread in threads {\n            thread.join().unwrap();\n        }\n    }\n\n```\n\n次に、`thread` クレートを追加し、もう一度コードをビルドし実行します。\n\n```shell\ncargo add thread\n\ncargo build\n\ncargo run -- \"GitLab Blog,https://about.gitlab.com/atom.xml\" \"CNCF,https://www.cncf.io/feed/\"\n```\n\nこの段階では、データの処理や出力は行われていません。次のステップに移る前に、ここで新たに導入されたキーワードについて解説します。\n\n### 関数スコープ、スレッド、クロージャ\n提案されるコードには新しいキーワードやデザインパターンが含まれることもあるため、これらについて理解しておくことが重要になります。スレッドハンドルは `thread::JoinHandle` という型で、スレッドが終わるのを待機するために使用します（[join()](https://doc.rust-lang.org/book/ch16-01-threads.html#waiting-for-all-threads-to-finish-using-join-handles)メソッドを使います）。\n\n`thread::spawn()` は新しいスレッドを生成し、このスレッド内では、関数オブジェクトを渡すことができます。この場合、[クロージャ](https://doc.rust-lang.org/book/ch13-01-closures.html)式が無名関数として渡されます。また、クロージャ入力は `||` 構文を使用して渡されますが、この際には[`move` クロージャ](https://doc.rust-lang.org/book/ch16-01-threads.html#using-move-closures-with-threads)が関数スコープの変数をスレッドスコープに移動します。これにより、どの変数を新しい関数やクロージャスコープに渡すかを手動で指定する必要がなくなります。\n\nただし、これには制限があります。`rss_feeds` は参照 `&` で、`get_feeds()` 関数の呼び出し元からパラメータとして渡されています。この変数は関数スコープ内でのみ有効です。このエラーを引き起こすには、次のコードスニペットを使用します。\n\n```rust\npub fn get_feeds(rss_feeds: &HashMap\u003CString, String>) {##$_0A$####$_0A$## // Store threads in vector##$_0A$##    let mut threads: Vec\u003Cthread::JoinHandle\u003C()>> = Vec::new();##$_0A$####$_0A$##    // Loop over rss_feeds and spawn threads##$_0A$##    for (key, value) in rss_feeds {##$_0A$##        let thread = thread::spawn(move || {##$_0A$## println!(\"{}\", key);##$_0A$##        });##$_0A$##    }##$_0A$##}\n```\n\n![VS Codeターミナル、参照とスレッドのmoveクロージャに関する変数スコープエラー](https://about.gitlab.com/images/blogimages/learn-rust-with-ai-code-suggestions-advanced-programming/vs_code_terminal_cargo_build_error_function_threads_variable_scopes.png){: .shadow}\n\n`key` 変数は関数スコープ内で作成されていますが、`rss_feeds` 変数を参照しているため、スレッドスコープに移動することはできません。関数パラメータ `rss_feeds` のハッシュマップからアクセスされる値は、`clone()` を使ってローカルコピーを作成する必要があります。\n\n![VS Codeターミナル、cloneを使用したスレッドの生成](https://about.gitlab.com/images/blogimages/learn-rust-with-ai-code-suggestions-advanced-programming/code_suggestions_rust_thread_spawn_clone.png){: .shadow}\n\n```rust\npub fn get_feeds(rss_feeds: &HashMap\u003CString, String>) {\n\n    // Store threads in vector\n    let mut threads: Vec\u003Cthread::JoinHandle\u003C()>> = Vec::new();\n\n    // Loop over rss_feeds and spawn threads\n    for (name, url) in rss_feeds {\n        let thread_name = name.clone();\n        let thread_url = url.clone();\n        let thread = thread::spawn(move || {\n            // Use thread_name and thread_url as values, see next chapter for instructions.\n\n```\n\n## フィードのXMLの解析およびオブジェクト型への変換\n次のステップは、スレッド内のクロージャでRSSフィードの解析手順を繰り返すことです。コード提案の指示とともに以下のコードコメントを追加します。\n\n1. `// Parse XML body with feed_rs parser, input in bytes`：コード提案に対し、RSSフィードのURLコンテンツを取得し、`feed_rs` クレートの関数で解析したいという要望を伝えます。\n2. `// Check feed_type attribute feed_rs::model::FeedType::RSS2 or Atom and print its name`：`feed_type` 属性を [`feed_rs::model::FeedType`](https://docs.rs/feed-rs/latest/feed_rs/model/enum.FeedType.html) と照合してフィードの種類を抽出します。この場合、コード提案に対して、照合の対象とする正確なEnum値を指示する必要があります。\n\n![コード提案に特定のフィードタイプと照合するよう指示](https://about.gitlab.com/images/blogimages/learn-rust-with-ai-code-suggestions-advanced-programming/code_suggestions_feed_rs_type_condition.png){: .shadow}\n\n```rust\n\n            // Parse XML body with feed_rs parser, input in bytes\n            let body = reqwest::blocking::get(thread_url).unwrap().bytes().unwrap();\n            let feed = feed_rs::parser::parse(body.as_ref()).unwrap();\n\n            // Check feed_type attribute feed_rs::model::FeedType::RSS2 or Atom and print its name\n            if feed.feed_type == feed_rs::model::FeedType::RSS2 {\n                println!(\"{} is an RSS2 feed\", thread_name);\n            } else if feed.feed_type == feed_rs::model::FeedType::Atom {\n                println!(\"{} is an Atom feed\", thread_name);\n            }\n\n```\n\nプログラムをもう一度ビルドして実行し、出力を確認します。\n\n```text\ntime cargo run -- \"GitLab Blog,https://about.gitlab.com/atom.xml\" \"CNCF,https://www.cncf.io/feed/\"\n\nCNCF is an RSS2 feed\nTechCrunch is an RSS2 feed\nGitLab Blog is an Atom feed\nHacker News is an RSS2 feed\n```\n\nフィードURLをブラウザで開くか、以前にダウンロードしたファイルを調べて、この出力を確認します。\n\nHacker News はRSS 2.0をサポートしており、`channel(title,link,description,item(title,link,pubDate,comments))` の構造を持っています。TechCrunchとCNCFブログは同様の構造をしています。\n```xml\n\u003Crss version=\"2.0\">\u003Cchannel>\u003Ctitle>Hacker News\u003C/title>\u003Clink>https://news.ycombinator.com/\u003C/link>\u003Cdescription>Links for the intellectually curious, ranked by readers.\u003C/description>\u003Citem>\u003Ctitle>Writing a debugger from scratch: Breakpoints\u003C/title>\u003Clink>https://www.timdbg.com/posts/writing-a-debugger-from-scratch-part-5/\u003C/link>\u003CpubDate>Wed, 27 Sep 2023 06:31:25 +0000\u003C/pubDate>\u003Ccomments>https://news.ycombinator.com/item?id=37670938\u003C/comments>\u003Cdescription>\u003C![CDATA[\u003Ca href=\"https://news.ycombinator.com/item?id=37670938\">Comments\u003C/a>]]>\u003C/description>\u003C/item>\u003Citem>\n```\n\nGitLabブログには[Atom](https://datatracker.ietf.org/doc/html/rfc4287)フィード形式が使用されています。RSSに類似していますが異なる解析ロジックが必要です。\n```xml\n\u003C?xml version='1.0' encoding='utf-8' ?>\n\u003Cfeed xmlns='http://www.w3.org/2005/Atom'>\n\u003C!-- / Get release posts -->\n\u003C!-- / Get blog posts -->\n\u003Ctitle>GitLab\u003C/title>\n\u003Cid>https://about.gitlab.com/blog\u003C/id>\n\u003Clink href='https://about.gitlab.com/blog/' />\n\u003Cupdated>2023-09-26T00:00:00+00:00\u003C/updated>\n\u003Cauthor>\n\u003Cname>The GitLab Team\u003C/name>\n\u003C/author>\n\u003Centry>\n\u003Ctitle>Atlassian Server ending: Goodbye disjointed toolchain, hello DevSecOps platform\u003C/title>\n\u003Clink href='https://about.gitlab.com/blog/atlassian-server-ending-move-to-a-single-devsecops-platform/' rel='alternate' />\n\u003Cid>https://about.gitlab.com/blog/atlassian-server-ending-move-to-a-single-devsecops-platform/\u003C/id>\n\u003Cpublished>2023-09-26T00:00:00+00:00\u003C/published>\n\u003Cupdated>2023-09-26T00:00:00+00:00\u003C/updated>\n\u003Cauthor>\n\u003Cname>Dave Steer, Justin Farris\u003C/name>\n\u003C/author>\n```\n\n### 汎用的なフィードデータ型のマッピング\n[`roxmltree::Document::parse`](https://docs.rs/roxmltree/latest/roxmltree/struct.Document.html) を使用する場合、XMLノードツリーとその特定のタグ名を理解する必要があります。幸い、[`feed_rs::model::Feed`](https://docs.rs/feed-rs/latest/feed_rs/model/struct.Feed.html) はRSSとAtomフィードの統合モデルを提供しているため、引き続き `feed_rs` クレートを使用して進めましょう。\n\n1. Atom：Feed->Feed、Entry->Entry\n2. RSS：Channel->Feed、Item->Entry\n\n上記のマッピングに加えて、必要な属性を抽出し、それらのデータ型をマッピングする必要があります。[feed_rs::modelのドキュメント](https://docs.rs/feed-rs/latest/feed_rs/model/index.html)を開き、構造体やそのフィールド、実装について理解しながら進めることをお勧めします。これは、`feed_rs` の実装に特有の型変換エラーやコンパイルエラーが発生するのを防ぐためです。\n\n[`Feed`](https://docs.rs/feed-rs/latest/feed_rs/model/struct.Feed.html) 構造体は `title` を提供しますが、その型は `Option\u003CText>` で、値が設定されているか、何もないかのいずれかです。[`Entry`](https://docs.rs/feed-rs/latest/feed_rs/model/struct.Entry.html) 構造体は以下の情報を提供します。\n\n1. `title`：`Option\u003CText>` 型で、[`Text`](https://docs.rs/feed-rs/latest/feed_rs/model/struct.Text.html) には `content` フィールドが `String` 型として含まれています。\n2. `published`：`Option\u003CDateTime\u003CUtc>>` 型で、[`DateTime`](https://docs.rs/chrono/latest/chrono/struct.DateTime.html) は [`format()` メソッド](https://docs.rs/chrono/latest/chrono/struct.DateTime.html#method.format)を持ちます。\n3. `summary`：`Option\u003CText>` 型で、[`Text`](https://docs.rs/feed-rs/latest/feed_rs/model/struct.Text.html) には `content` フィールドが `String` 型として含まれています。\n4. `links`：`Vec\u003CLink>` 型で、[`Link`](https://docs.rs/feed-rs/latest/feed_rs/model/struct.Link.html) 項目のベクターです。`href` 属性が生のURL文字列を提供します。\n\n1から4に従って、フィードエントリから必要なデータを抽出します。繰り返しますが、すべての `Option` 型には `unwrap()` を呼び出す必要があります。このため、コード提案に対してより直接的な指示が求められます。\n\n```rust\n\n                // https://docs.rs/feed-rs/latest/feed_rs/model/struct.Feed.html\n                // https://docs.rs/feed-rs/latest/feed_rs/model/struct.Entry.html\n                // Loop over all entries, and print\n                // title.unwrap().content\n                // published.unwrap().format\n                // summary.unwrap().content\n                // links href as joined string\n                for entry in feed.entries {\n                    println!(\"Title: {}\", entry.title.unwrap().content);\n                    println!(\"Published: {}\", entry.published.unwrap().format(\"%Y-%m-%d %H:%M:%S\"));\n                    println!(\"Summary: {}\", entry.summary.unwrap().content);\n                    println!(\"Links: {:?}\", entry.links.iter().map(|link| link.href.clone()).collect::\u003CVec\u003CString>>().join(\", \"));\n                    println!();\n                }\n\n```\n\n![特定の要件に基づいてフィードエントリの型を出力するためのコード提案](https://about.gitlab.com/images/blogimages/learn-rust-with-ai-code-suggestions-advanced-programming/code_suggestions_print_feed_entries_fields_with_rust_type_specifics.png){: .shadow}\n\n### Option::unwrap()によるエラーハンドリング\nプログラムを再ビルドして実行した後、複数行の指示を引き続き処理します。補足：`unwrap()` は空の値に遭遇すると `panic!` マクロを呼び出し、プログラムを強制終了させます。これは、フィードデータ内に `summary` のようなフィールドが設定されていない場合に発生します。\n\n```shell\nGitLab Blog is an Atom feed\nTitle: How the Colmena project uses GitLab to support citizen journalists\nPublished: 2023-09-27 00:00:00\nthread '\u003Cunnamed>' panicked at 'called `Option::unwrap()` on a `None` value', src/feed_reader.rs:40:59\n```\n\n解決策のひとつとして、[`std::Option::unwrap_or_else`](https://doc.rust-lang.org/std/option/enum.Option.html#method.unwrap_or_else) を使用し、デフォルト値として空の文字列を設定することが挙げられます。この構文には、空の `Text` 構造体のインスタンスを返すクロージャが必要です。\n\n問題を解決する上で、正しい初期化方法を見つけるために試行錯誤を重ねました。単に空の文字列を渡すだけではカスタム型ではうまく機能しませんでした。以下に、筆者が試したすべてのアプローチとリサーチの過程をまとめました。\n\n```rust\n// Problem: The `summary` attribute is not always initialized. unwrap() will panic! then.\n// Requires use mime; and use feed_rs::model::Text;\n/*\n// 1st attempt: Use unwrap() to extraxt Text from Option\u003CText> type.\nprintln!(\"Summary: {}\", entry.summary.unwrap().content);\n// 2nd attempt. Learned about unwrap_or_else, passing an empty string.\nprintln!(\"Summary: {}\", entry.summary.unwrap_or_else(|| \"\").content);\n// 3rd attempt. summary is of the Text type, pass a new struct instantiation.\nprintln!(\"Summary: {}\", entry.summary.unwrap_or_else(|| Text{}).content);\n// 4th attempt. Struct instantiation requires 3 field values.\nprintln!(\"Summary: {}\", entry.summary.unwrap_or_else(|| Text{\"\", \"\", \"\"}).content);\n// 5th attempt. Struct instantation with public fields requires key: value syntax\nprintln!(\"Summary: {}\", entry.summary.unwrap_or_else(|| Text{content_type: \"\", src: \"\", content: \"\"}).content);\n// 6th attempt. Reviewed expected Text types in https://docs.rs/feed-rs/latest/feed_rs/model/struct.Text.html and created Mime and String objects\nprintln!(\"Summary: {}\", entry.summary.unwrap_or_else(|| Text{content_type: mime::TEXT_PLAIN, src: String::new(), content: String::new()}).content);\n// 7th attempt: String and Option\u003CString> cannot be casted automagically. Compiler suggested using `Option::Some()`.\nprintln!(\"Summary: {}\", entry.summary.unwrap_or_else(|| Text{content_type: mime::TEXT_PLAIN, src: Option::Some(), content: String::new()}).content);\n*/\n\n// xth attempt: Solution. Option::Some() requires a new String object.\nprintln!(\"Summary: {}\", entry.summary.unwrap_or_else(|| Text{content_type: mime::TEXT_PLAIN, src: Option::Some(String::new()), content: String::new()}).content);\n```\n\nこのアプローチは、コードが複雑で読みにくく、さらにコード提案の支援がなかったため、手動でコーディングする必要があり、満足のいくものではありませんでした。一旦立ち止まり、アプローチを見直しました。`Option` が `none` の場合に`unwrap()` はエラーをスローするのであれば、そのエラーを処理する簡単な方法があるのではと考え、コード提案に新しいコメントで質問しました。\n\n```shell\n\n                // xth attempt: Solution. Option::Some() requires a new String object.\n                println!(\"Summary: {}\", entry.summary.unwrap_or_else(|| Text{content_type: mime::TEXT_PLAIN, src: Option::Some(String::new()), content: String::new()}).content);\n\n                // Alternatively, use Option.is_none()\n\n```\n\n![コード提案によるOptions.is_noneを使った代替案](https://about.gitlab.com/images/blogimages/learn-rust-with-ai-code-suggestions-advanced-programming/code_suggestions_after_complex_unwrap_or_else_ask_for_alternative_option.png){: .shadow}\n\n結果として、読みやすさが向上し、`unwrap()` による無駄なCPUサイクルが減りました。複雑な問題を解決することから単純な解決策を使用することまでの学習過程を大幅に短縮できました。まさにウィンウィンです。\n\n忘れないうちに、XMLデータをディスクに保存する指示を再度追加して、リーダーアプリを完成させましょう。\n\n```rust\n\n                // Dump the parsed body to a file, as name-current-iso-date.xml\n                let file_name = format!(\"{}-{}.xml\", thread_name, chrono::Local::now().format(\"%Y-%m-%d-%H-%M-%S\"));\n                let mut file = std::fs::File::create(file_name).unwrap();\n                file.write_all(body.as_ref()).unwrap();\n\n```\n\nプログラムをビルドして実行し、出力を確認します。\n\n```shell\ncargo build\n\ntime cargo run -- \"GitLab Blog,https://about.gitlab.com/atom.xml\" \"CNCF,https://www.cncf.io/feed/\"\n```\n\n![VS Codeターミナルでcargo runを実行し、フォーマットされたフィードエントリの出力](https://about.gitlab.com/images/blogimages/learn-rust-with-ai-code-suggestions-advanced-programming/vs_code_terminal_cargo_run_formatted_output_final.png)\n\n## ベンチマーク\n\n### 逐次実行と並列実行のベンチマークの比較\nそれぞれ5つのサンプルを作成して、実行時間のベンチマークを比較します。\n\n1. 逐次実行\n2. 並列実行\n\n```shell\n# Sequential\ngit checkout sequential-exec\n\ntime cargo run -- \"GitLab Blog,https://about.gitlab.com/atom.xml\" \"CNCF,https://www.cncf.io/feed/\"\n\n0.21s user 0.08s system 10% cpu 2.898 total\n0.21s user 0.08s system 11% cpu 2.585 total\n0.21s user 0.09s system 10% cpu 2.946 total\n0.19s user 0.08s system 10% cpu 2.714 total\n0.20s user 0.10s system 10% cpu 2.808 total\n```\n\n```shell\n# Parallel\ngit checkout parallel-exec\n\ntime cargo run -- \"GitLab Blog,https://about.gitlab.com/atom.xml\" \"CNCF,https://www.cncf.io/feed/\"\n\n0.19s user 0.08s system 17% cpu 1.515 total\n0.18s user 0.08s system 16% cpu 1.561 total\n0.18s user 0.07s system 17% cpu 1.414 total\n0.19s user 0.08s system 18% cpu 1.447 total\n0.17s user 0.08s system 16% cpu 1.453 total\n```\n\n4つのRSSフィードスレッドを並列実行した場合、CPU使用率は増加しましたが、逐次実行に比べて全体の実行時間はほぼ半減しました。この点を踏まえて、Rustの学習を続け、コードと機能を最適化していきます。\n\nなお、ここではCargoを使ってデバッグビルドを実行しており、最適化されたリリースビルドはまだ実行していません。また、並列実行にはいくつか注意点があります。一部のHTTPエンドポイントはレート制限を設けており、並列処理がこの制限に引っかかりやすくなる可能性があります。\n\n複数のスレッドを並列実行するシステムも過負荷になる可能性があります。これは、カーネル内でのコンテキストスイッチ（スレッド間の切り替え）を通じて各スレッドにリソースを割り当てる必要があるためです。1つのスレッドが計算リソースを使用している間、他のスレッドは待機状態になります。スレッドが多すぎると、システム全体が遅くなり、処理がスピードアップするどころか逆に遅くなることもあります。解決策としては、呼び出し元がキューにタスクを追加し、定義された数のワーカースレッドが非同期実行のためにタスクを処理する[ワークキュー](https://docs.rs/work-queue/latest/work_queue/)などの設計パターンがあります。\n\nRustでは、スレッド間のデータ同期を行う[チャンネル](https://doc.rust-lang.org/rust-by-example/std_misc/channels.html)を利用することもできます。競合状態を防ぐために、セーフロックを提供する[ミューテックス](https://doc.rust-lang.org/std/sync/struct.Mutex.html)も利用可能です。\n\n### Rustのキャッシュを使用したCI/CD\n以下のCI/CD構成を `.gitlab-ci.yml` ファイルに追加します。この `run-latest` ジョブは `cargo run` をRSSフィードURLの例とともに呼び出し、実行時間を継続的に計測します。\n\n```text\nstages:\n  - build\n  - test\n  - run\n\ndefault:\n  image: rust:latest\n  cache:\n    key: ${CI_COMMIT_REF_SLUG}\n    paths:\n      - .cargo/bin\n      - .cargo/registry/index\n      - .cargo/registry/cache\n      - target/debug/deps\n      - target/debug/build\n    policy: pull-push\n\n# Cargo data needs to be in the project directory for being cached.\nvariables:\n  CARGO_HOME: ${CI_PROJECT_DIR}/.cargo\n\nbuild-latest:\n  stage: build\n  script:\n    - cargo build --verbose\n\ntest-latest:\n  stage: build\n  script:\n    - cargo test --verbose\n\nrun-latest:\n  stage: run\n  script:\n    - time cargo run -- \"GitLab Blog,https://about.gitlab.com/atom.xml\" \"CNCF,https://www.cncf.io/feed/\"\n\n```\n\n![Rust用のGitLab CI/CDパイプライン、cargo runの出力](https://about.gitlab.com/images/blogimages/learn-rust-with-ai-code-suggestions-advanced-programming/gitlab_cicd_pipeline_rust_cargo_run_output.png){: .shadow}\n\n## 次のステップ\nこのブログ記事の執筆は、筆者自身が高度なRustプログラミング技術を学びつつ、コード提案を使って最適な学習過程を見出すという点で難しいものでした。コード提案は、単なる定型的なコードだけでなく、ローカルコンテキストを理解し、記述されたコードが多いほどアルゴリズムの目的や範囲をよりよく把握した迅速なコード生成にも大いに役立ちます。このブログ記事を読むことで、全てではないにしろ、いくつかの課題や解決策についてご理解いただけたかと思います。\n\nRSSフィードの解析は、外部HTTPリクエストや並列最適化を伴うデータ構造が関わるため、ハードルが高めです。経験豊富なRustユーザーであれば、`なぜstd::rssクレートを使わなかったのか？` と疑問に思うかもしれません。その理由は、std::rssは高度な非同期実行に最適化されており、このブログ記事でご説明したさまざまなRustの機能を示したり説明したりすることができないためです。ぜひ、非同期の演習として、[`rss` クレート](https://docs.rs/rss/latest/rss/)を使ってコードを書き直してみてください。\n\n### 非同期学習のエクササイズ\nこのブログ記事で学んだ内容は、永続的なデータ保存やデータ表示に関する理解を今後も深めていく上で基礎となります。引き続きRustを学びながら、リーダーアプリを最適化していくためのアイデアをいくつかご紹介します。\n\n1. データ保存：sqliteなどのデータベースを使用し、RSSフィードの更新履歴を追跡する。\n2. 通知：子プロセスを生成して、Telegramなどに通知を送る機能を追加する。\n3. 機能：リーダータイプを拡張して、REST APIをサポートする。\n4. 構成：RSSフィードやAPIなどの構成ファイルのサポートを追加する。\n5. 効率性：フィルタリングやサブスクライブしたタグのサポートを追加する。\n6. デプロイ：Webサーバーを使用して、Prometheusメトリクスを収集して[Kubernetes](https://about.gitlab.com/ja-jp/blog/what-is-kubernetes/)にデプロイする。\n\n今後のブログ記事では、これらのアイデアのいくつかを取り上げ、その実装方法について説明します。既存のRSSフィードの実装を詳しく調べ、どのように既存のコードをリファクタリングしてRustのライブラリ（`crates`）を活用できるか学びましょう。\n\n### フィードバックの共有\n[GitLab Duo](https://about.gitlab.com/ja-jp/gitlab-duo-agent-platform/)のコード提案をご使用になった方は、ぜひ[ご意見をフィードバックイシューにお寄せください](https://gitlab.com/gitlab-org/gitlab/-/issues/405152)。\n\n*監修：佐々木 直晴 [@naosasaki](https://gitlab.com/naosasaki) \u003Cbr>\n（GitLab合同会社 ソリューションアーキテクト本部 シニアソリューションアーキテクト）*\n","ai-ml",{"slug":13,"featured":14,"template":15},"learn-advanced-rust-programming-with-a-little-help-from-ai-code-suggestions",false,"BlogPost",{"title":5,"description":17,"authors":18,"heroImage":19,"date":20,"body":10,"category":11,"tags":21,"updatedDate":27},"このガイド付きチュートリアルでは、AIを搭載したGitLab Duoのコード提案を活用しながら、Rustの高度なプログラミングを学ぶことができます。",[9],"https://res.cloudinary.com/about-gitlab-com/image/upload/v1749662439/Blog/Hero%20Images/codewithheart.png","2023-10-12",[22,23,24,25,26],"DevSecOps","careers","tutorial","workflow","AI/ML","2025-01-24","yml",null,{},true,"/ja-jp/blog/learn-advanced-rust-programming-with-a-little-help-from-ai-code-suggestions","seo:\n  title: AIを活用して学ぶ、Rustの高度なプログラミング\n  description: このガイド付きチュートリアルでは、AIを搭載したGitLab Duoのコード提案を活用しながら、Rustの高度なプログラミングを学ぶことができます。\n  ogTitle: AIを活用して学ぶ、Rustの高度なプログラミング\n  ogDescription: このガイド付きチュートリアルでは、AIを搭載したGitLab Duoのコード提案を活用しながら、Rustの高度なプログラミングを学ぶことができます。\n  noIndex: false\n  ogImage: >-\n    https://res.cloudinary.com/about-gitlab-com/image/upload/v1749662439/Blog/Hero%20Images/codewithheart.png\n  ogUrl: >-\n    https://about.gitlab.com/blog/learn-advanced-rust-programming-with-a-little-help-from-ai-code-suggestions\n  ogSiteName: https://about.gitlab.com\n  ogType: article\n  canonicalUrls: >-\n    https://about.gitlab.com/blog/learn-advanced-rust-programming-with-a-little-help-from-ai-code-suggestions\ncontent:\n  title: AIを活用して学ぶ、Rustの高度なプログラミング\n  description: このガイド付きチュートリアルでは、AIを搭載したGitLab Duoのコード提案を活用しながら、Rustの高度なプログラミングを学ぶことができます。\n  authors:\n    - Michael Friedrich\n  heroImage: >-\n    https://res.cloudinary.com/about-gitlab-com/image/upload/v1749662439/Blog/Hero%20Images/codewithheart.png\n  date: '2023-10-12'\n  body: >\n    20年以上前に新しいプログラミング言語を学び始めたとき、私たちは6枚のCD-ROMからインストールしたVisual Studio\n    6のMSDNライブラリにアクセスしていました。ペンと紙でアルゴリズムを記録し、設計パターンの本を読み漁り、MSDNで正しい型を調べていましたが、こうした作業に時間がかかることが多々ありました。しかし、リモートコラボレーションや人工知能（AI）の時代が到来し、プログラミング言語の学び方は根本的に変わりました。今では[リモート開発環境](https://about.gitlab.com/blog/quick-start-guide-for-gitlab-workspaces/)をすばやく立ち上げて画面を共有し、グループでのプログラミングセッションを行えるようになりました。[GitLab\n    Duoのコード提案](https://about.gitlab.com/ja-jp/gitlab-duo-agent-platform/)を使用すれば、AIというインテリジェントなパートナーにいつでも頼ることができます。コード提案機能は、ユーザーのプログラミングのスタイルと経験に基づいて学習します。この機能は、インプットとコンテキストさえあれば、最も効率的な提案を提供してくれるのです。\n\n\n    このチュートリアルでは、[入門編のブログ記事](/blog/learning-rust-with-a-little-help-from-ai-code-suggestions-getting-started/)（英語）からさらに一歩踏み込み、シンプルなフィードリーダーアプリケーションの設計と作成に取り組みます。\n\n\n    - 準備\n        - コード提案\n    - Rustの学習の継続\n        - 「Hello, Reader!」アプリ\n        - プロジェクトの初期化\n        - RSSフィードURLの定義\n    - モジュール\n        - main() 関数によるモジュール関数の呼び出し\n    - クレート\n        - feed-rs：XMLフィードの解析\n    - ランタイム設定：プログラム引数\n        - ユーザー入力のエラーハンドリング\n    - 永続性とデータ保存\n\n    - 最適化\n        - 非同期実行\n        - スレッドの生成\n        - 関数スコープ、スレッド、クロージャ\n    - フィードのXMLの解析およびオブジェクト型への変換\n        - 汎用的なフィードデータ型のマッピング\n        - Option::unwrap()によるエラーハンドリング\n    - ベンチマーク\n        - 逐次実行と並列実行のベンチマークの比較\n        - Rustのキャッシュを使用したCI/CD\n    - 次のステップ\n        - 非同期学習の演習\n        - フィードバックの共有\n\n    ## 準備\n\n    ソースコードを参照する前に、[VS\n    Code](/blog/learning-rust-with-a-little-help-from-ai-code-suggestions-getting-started/#vs-code)と[Rustの開発環境](/blog/learning-rust-with-a-little-help-from-ai-code-suggestions-getting-started/#development-environment-for-rust)をセットアップしてください。\n\n\n    ### コード提案\n\n    実際に提案機能を検証する前に、まずはこの機能の使い方を理解しましょう。GitLab\n    Duoのコード提案は、特定のキーボードショートカットを必要とせず、キーを入力するだけで使用できます。たとえば、コード提案を受け入れるには、`Tab`\n    キーを押します。また、新しく作成したコードの方が、既存のコードをリファクタリングしたものよりもエラー発生率が低くなる点も覚えておきましょう。AIは非決定的であるため、一度削除したコード提案は再び同じ形で提示されない可能性があります。コード提案は現在ベータ版であり、GitLabは、同機能が生成するコンテンツの全体的な精度向上に取り組んでいます。\n\n\n    **ヒント**：コード提案の最新リリースでは、複数行の指示文に対応しています。ニーズに合わせて指示文を調整することで、よりよい提案を得ることができます。\n\n\n    ```rust\n\n        // Create a function that iterates over the source array\n        // and fetches the data using HTTP from the RSS feed items.\n        // Store the results in a new hash map.\n        // Print the hash map to the terminal.\n\n    ```\n\n\n    VS Code拡張機能のオーバーレイは、提案を提示する際に表示されます。提案された行を受け入れるには `Tab` キーを使用し、一単語だけ受け入れるには\n    `Cmd + 右カーソル` キーを使用します。さらに、三点リーダーメニューからツールバーを常に表示するオプションを選択することも可能です。\n\n\n    ![VS CodeにおけるGitLab\n    Duoの指示文とコード提案のオーバーレイ](https://about.gitlab.com/images/blogimages/learn-rust-with-ai-code-suggestions-advanced-programming/vs_code_code_suggestions_options_overlay_keep_toolbar.png){:\n    .shadow}\n\n\n    ## Rustの学習の継続\n\n    では、引き続きRustについて学んでいきましょう。Rustは[コード提案でサポートされている言語](https://docs.gitlab.com/ja-jp/user/project/repository/code_suggestions/)のひとつです。[Rust\n    by\n    Example](https://doc.rust-lang.org/rust-by-example/)（英語）では、初心者に最適なチュートリアルが提供されており、公式の[Rust\n    Book](https://doc.rust-lang.org/book/)（英語）を確認しながら進めると効果的です。どちらのリソースもこのブログの執筆の際に参考にしています。\n\n\n    ### 「Hello, Reader!」アプリ\n\n    アプリケーションの作成やRustの学習には、さまざまなアプローチがあります。その中には、既存のRustライブラリ、いわゆる `Crates`\n    を利用するものがあり、このブログ記事の後半でも使用します。たとえば、画像を処理して、その結果をファイルに書き込むコマンドラインアプリを作成することができます。また、昔ながらの迷路をクリアしたり、数独を解いたりするアプリケーションを作成するのも楽しいでしょうし、ゲーム開発を行うこともできます。たとえば、[Hands-on\n    Rust](https://hands-on-rust.com/)（英語）というガイドブックでは、ダンジョンクローラー（迷宮探検）ゲームを作りながら、Rustを体系的に学習できるコースを提供しています。筆者の同僚であるFatima\n    Sarah Khalidは、[AIを活用してC++でDragon\n    Realmの制作](/blog/building-a-text-adventure-using-cplusplus-and-code-suggestions/)（英語）を始めたそうです。ぜひそちらもご覧ください。\n\n\n    ここで、実際の問題を解決するのに役立つ実用的なユースケースをひとつご紹介します。それは、異なるソースから重要な情報をRSSフィードに集約するというものです。RSSフィードには、セキュリティリリース、ブログ記事、およびソーシャルディスカッションフォーラム（Hacker\n    Newsなど）の最新情報が含まれます。アップデートに含まれる特定のキーワードやバージョンを絞り込んで検索したいと考えることがよくあります。こうしたニーズを基に、アプリケーションの要件をリスト化できます。具体的には、次のような要件です。\n\n\n    1. HTTPウェブサイト、REST API、RSSフィードなどの異なるソースからデータをフェッチ（取得）する（最初の段階ではRSSフィードを使用）。\n\n    1. データを解析する。\n\n    1. データをユーザーに提示する、またはディスクに書き込む。\n\n    1. パフォーマンスを最適化する。\n\n\n    このブログ記事の学習ステップを完了すると、以下のサンプルアプリケーションの出力が得られます。\n\n\n    ![VS Codeのターミナルでのcargo\n    runの実行と、形成されたフィードエントリの出力](https://about.gitlab.com/images/blogimages/learn-rust-with-ai-code-suggestions-advanced-programming/vs_code_terminal_cargo_run_formatted_output_final.png)\n\n\n    アプリケーションはモジュール化されている必要があり、また、将来的にデータ型やフィルター、アクションをトリガーするフックを追加できる基盤も整えておく必要があります。\n\n\n    ### プロジェクトの初期化\n\n    リマインダー：`cargo init` をプロジェクトのルートディレクトリで実行すると、 `main ()`\n    エントリポイントを含んだファイル構造が作成されます。これを踏まえ、次のステップでは、Rustのモジュールの作成と使用方法を学びます。\n\n\n    `learn-rust-ai-app-reader` という名前のディレクトリを新規作成し、そのディレクトリに移動したら、`cargo init`\n    を実行します。このコマンドは、暗黙的に `git init`\n    を実行し、新しいGitリポジトリをローカルで初期化します。残りのステップでは、Gitリモートリポジトリのパスを設定していきます。たとえば、`https://gitlab.com/gitlab-da/use-cases/ai/learn-with-ai/learn-rust-ai-app-reader`\n    のように設定します（ご自身のネームスペースに合わせてパスを置き換えてください）。[Gitリポジトリをプッシュ](https://about.gitlab.com/ja-jp/blog/mastering-the-basics-of-git-push-tag/)すると、[GitLabで新しいプロジェクトが自動的に作成](https://docs.gitlab.com/ja-jp/user/project/#create-a-new-project-with-git-push)されます。\n\n\n    ```shell\n\n    mkdir learn-rust-ai-app-reader\n\n    cd learn-rust-ai-app-reader\n\n\n    cargo init\n\n\n    git remote add origin\n    https://gitlab.com/gitlab-da/use-cases/ai/learn-with-ai/learn-rust-ai-app-reader.git\n\n    git push --set-upstream origin main\n\n    ```\n\n\n    新しく作成されたディレクトリからVS Codeを開きます。`code` コマンドラインインターフェース（CLI）によって、macOS上で新しいVS\n    Codeのウィンドウが起動します。\n\n\n    ```shell\n\n    code .\n\n    ```\n\n\n    ### RSSフィードURLの定義\n\n    新しいHashMapを追加して、`src/main.rs` ファイル内の `main()`\n    関数にRSSフィードのURLを保存します。複数行の指示コメントを入力することで、GitLab\n    Duoのコード提案に対し、[`HashMap`](https://doc.rust-lang.org/stable/std/collections/struct.HashMap.html)\n    オブジェクトを作成して、Hacker\n    NewsやTechCrunchのデフォルト値で初期化するよう指示できます。注：提案に含まれるURLが正しいことを確認してください。\n\n\n    ```rust\n\n    fn main() {##$_0A$##    // Define RSS feed URLs in the variable\n    rss_feeds##$_0A$##    // Use a HashMap##$_0A$##    // Add Hacker News and\n    TechCrunch##$_0A$##    // Ensure to use String as type##$_0A$####$_0A$##}\n\n    ```\n\n\n    コードコメントでは以下の点を指示しています。\n\n\n    1. 変数名 `rss_feeds`\n\n    2. `HashMap` タイプ\n\n    3. 3. 初期のseedキー/バリューペア\n\n    4. String 型（`to_string ()` 呼び出しで確認可能）\n\n\n    考えられるコードの例は次の通りです。\n\n\n    ```rust\n\n    use std::collections::HashMap;\n\n\n    fn main() {##$_0A$##    // Define RSS feed URLs in the variable\n    rss_feeds##$_0A$##    // Use a HashMap##$_0A$##    // Add Hacker News and\n    TechCrunch##$_0A$##    // Ensure to use String as type##$_0A$##    let\n    rss_feeds = HashMap::from([##$_0A$##        (\"Hacker News\".to_string(),\n    \"https://news.ycombinator.com/rss\".to_string()),##$_0A$##\n    (\"TechCrunch\".to_string(),\n    \"https://techcrunch.com/feed/\".to_string()),##$_0A$## ]);##$_0A$####$_0A$##}\n\n    ```\n\n\n    ![VS Codeにおける、コード提案を活用したHacker\n    NewsとTechCrunchのRSSフィードURLの提案](https://about.gitlab.com/images/blogimages/learn-rust-with-ai-code-suggestions-advanced-programming/vs_code_main_array_rss_feed_urls_suggested.png)\n\n\n    VS Codeで新しいターミナルを開き（cmd + shift + p で `terminal` を検索）、`cargo build`\n    を実行して変更をビルドします。エラーメッセージが表示され、`use std::collections::HashMap;`\n    のインポートを追加するよう指示されます。\n\n\n    次のステップでは、RSSフィードのURLを使用して操作を行います。[以前のブログ記事](/blog/learning-rust-with-a-little-help-from-ai-code-suggestions-getting-started/)（英語）では、コードを関数に分割する方法を解説しました。今回は、リーダーアプリケーションのコードをよりモジュール化して整理し、Rustのモジュールを使用します。\n\n\n    ## モジュール\n\n    [モジュール](https://doc.rust-lang.org/rust-by-example/mod.html)は、\n    コードの整理に役立ちます。また、関数をモジュールのスコープ内に隠し、main()スコープからのアクセスを制限することも可能です。リーダーアプリケーションでは、RSSフィードのコンテンツを取得し、XMLレスポンスを解析したいため、`main()`\n    からは、`get_feeds()` 関数のみにアクセスできるようにし、それ以外の機能はモジュール内のみで使用できるように制限します。\n\n\n    `src/` ディレクトリに `feed_reader.rs` という名前の新しいファイルを作成します。コード提案に、`feed_reader`\n    という名前の公開モジュールと、String HashMapをインプットとして受け取る `get_feeds()`\n    という公開関数を作成するよう指示します。重要：[Rustのモジュール構造](https://doc.rust-lang.org/book/ch07-02-defining-modules-to-control-scope-and-privacy.html)に従って、ファイル名とモジュール名を同じにする必要があります。\n\n\n    ![コード提案：関数と入力型を使った公開モジュールの作成](https://about.gitlab.com/images/blogimages/learn-rust-with-ai-code-suggestions-advanced-programming/code_suggestions_rust_public_module_function_input.png){:\n    .shadow}\n\n\n    コード提案に入力変数名と型を指定すると、必要な `std::collections::HashMap`\n    モジュールが自動的にインポートされます。ヒント：最適な結果を得られるよう、コメントを使って変数の型を調整してみましょう。また、Rustでは関数のパラメータをオブジェクト参照として渡すのがベストプラクティスとされています。以下に例を示します。\n\n\n    ```rust\n\n    // Create public module feed_reader\n\n    // Define get_feeds() function which takes rss_feeds as String HashMap\n    reference as input\n\n    pub mod feed_reader {##$_0A$##    use\n    std::collections::HashMap;##$_0A$####$_0A$##    pub fn get_feeds(rss_feeds:\n    &HashMap\u003CString, String>) {##$_0A$##        // Do something with the RSS\n    feeds##$_0A$##    }##$_0A$##}\n\n    ```\n\n\n    ![コード提案：`get_feeds()`\n    関数と提案された入力変数を含む公開モジュール](https://about.gitlab.com/images/blogimages/learn-rust-with-ai-code-suggestions-advanced-programming/code_suggestions_rust_public_module_function_input.png){:\n    .shadow}\n\n\n    関数内では、コード提案に以下の手順を指示します。\n\n\n    1. `// Iterate over the RSS feed URLs` （RSSフィードURLを反復処理する）\n\n    2. `// Fetch URL content` （URLコンテンツを取得する）\n\n    3. `// Parse XML body` （XMLの本文を解析する）\n\n    4. `// Print the result`  (結果を出力する)\n\n\n    ![コード提案：`get_feeds()`\n    関数を含む公開モジュール、ステップ1：イテレート](https://about.gitlab.com/images/blogimages/learn-rust-with-ai-code-suggestions-advanced-programming/code_suggestions_rust_module_function_01_iterate.png){:\n    .shadow}\n\n\n    ![コード提案：`get_feeds()`\n    関数を含む公開モジュール、ステップ2：URLコンテンツの取得](https://about.gitlab.com/images/blogimages/learn-rust-with-ai-code-suggestions-advanced-programming/code_suggestions_rust_module_function_02_fetch_content.png){:\n    .shadow}\n\n\n    ![コード提案：`get_feeds()`\n    関数を含む公開モジュール、ステップ3：XML本文の解析](https://about.gitlab.com/images/blogimages/learn-rust-with-ai-code-suggestions-advanced-programming/code_suggestions_rust_module_function_03_parse_body.png){:\n    .shadow}\n\n\n    ![コード提案：`get_feeds()`\n    関数を含む公開モジュール、ステップ4：結果の出力](https://about.gitlab.com/images/blogimages/learn-rust-with-ai-code-suggestions-advanced-programming/code_suggestions_rust_module_function_04_print_result.png){:\n    .shadow}\n\n\n    次のコードが提案されます。\n\n\n    ```rust\n\n    // Create public module feed_reader\n\n    // Define get_feeds() function which takes rss_feeds as String HashMap\n    reference as input\n\n    pub mod feed_reader {##$_0A$##    use\n    std::collections::HashMap;##$_0A$####$_0A$##    pub fn get_feeds(rss_feeds:\n    &HashMap\u003CString, String>) {##$_0A$##        // Iterate over the RSS feed\n    URLs##$_0A$##        for (name, url) in rss_feeds {##$_0A$## println!(\"{}:\n    {}\", name, url);##$_0A$####$_0A$##            // Fetch URL\n    content##$_0A$##            let body =\n    reqwest::blocking::get(url).unwrap().text().unwrap();##$_0A$####$_0A$## //\n    Parse XML body##$_0A$##            let parsed_body =\n    roxmltree::Document::parse(&body).unwrap();##$_0A$####$_0A$##            //\n    Print the result##$_0A$##            println!(\"{:#?}\",\n    parsed_body);##$_0A$##        }##$_0A$##    }##$_0A$##}\n\n    ```\n\n\n    ここで新しいキーワード\n    [`unwrap()`](https://doc.rust-lang.org/rust-by-example/error/option_unwrap.html)が登場します。Rustは\n    `null` 値をサポートしておらず、すべての値に対して [`Option`\n    型](https://doc.rust-lang.org/rust-by-example/std/option.html)を使用します。たとえば、`Text`\n    や `String` といった特定のラップされた型を使用することが確定している場合、`unwrap()`\n    メソッドを呼び出してその値を取得できます。ただし、値が `None` の場合、`unwrap()` メソッドはパニックを起こします。\n\n\n    **注意**：コード提案は、`// Fetch URL content` のコメント指示に従って、`reqwest::blocking::get`\n    関数を参照します。[`reqwest`](https://docs.rs/reqwest/latest/reqwest/)というクレーと名は意図的なものであり、タイポではありません。非同期リクエストとブロッキングリクエストの処理に役立つ、優れた利便性と高レベルのHTTPクライアントの機能を提供します。\n\n\n    XMLの本文の解析は難しく、異なる結果が得られることがあります。また、スキーマはRSSフィードURLごとに異なる可能性があります。まずは\n    `get_feeds()` 関数を呼び出し、その後でコードの改善に取り組みましょう。\n\n\n    ### main() 関数によるモジュール関数の呼び出し\n\n\n    現在、main() 関数は `get_feeds()`\n    関数を認識していないため、まずそのモジュールをインポートする必要があります。他のプログラミング言語では `include` や `import`\n    といったキーワードを目にすることがありますが、Rustのモジュールシステムは異なります。\n\n\n    モジュールはパスディレクトリに整理されます。今回の例では、両方のソースファイルが同じディレクトリレベルに存在しています。`feed_reader.rs`\n    はクレートとして解釈され、その中に `feed_reader` というモジュールがあり、そのモジュールが `get_feeds()`\n    関数を定義しています。\n\n\n    ```text\n\n    src/\n      main.rs\n      feed_reader.rs\n\n    ```\n\n\n    `feed_reader.rs` ファイルの `get_feeds()` 関数にアクセスするためには、まず `main.rs`\n    のスコープに[モジュールパスを取り込み](https://doc.rust-lang.org/book/ch07-04-bringing-paths-into-scope-with-the-use-keyword.html)、その後フルパスで関数を呼び出します。\n\n\n    ```rust\n\n    mod feed_reader;\n\n\n    fn main() {\n\n        feed_reader::feed_reader::get_feeds(&rss_feeds);\n\n    ```\n\n\n    あるいは、`use` キーワードを使って関数のフルパスをインポートし、その後短い関数名で呼び出すこともできます。\n\n\n    ```rust\n\n    mod feed_reader;\n\n    use feed_reader::feed_reader::get_feeds;\n\n\n    fn main() {\n\n        get_feeds(&rss_feeds);\n\n    ```\n\n\n    **ヒント**：Rustのモジュールシステムを視覚的によりよく理解するために、[Rustモジュールシステムについてわかりやすく説明したブログ記事](https://www.sheshbabu.com/posts/rust-module-system/)（英語）をお読みください。\n\n\n    ```diff\n\n    fn main() {\n        // ...\n\n        // Print feed_reader get_feeds() output\n        println!(\"{}\", feed_reader::get_feeds(&rss_feeds));\n\n    ```\n\n\n    ```rust\n\n    use std::collections::HashMap;\n\n\n    mod feed_reader;\n\n    // Alternative: Import full function path\n\n    //use feed_reader::feed_reader::get_feeds;\n\n\n    fn main() {##$_0A$##    // Define RSS feed URLs in the variable\n    rss_feeds##$_0A$##    // Use a HashMap##$_0A$##    // Add Hacker News and\n    TechCrunch##$_0A$##    // Ensure to use String as type##$_0A$##    let\n    rss_feeds = HashMap::from([##$_0A$##        (\"Hacker News\".to_string(),\n    \"https://news.ycombinator.com/rss\".to_string()),##$_0A$##\n    (\"TechCrunch\".to_string(),\n    \"https://techcrunch.com/feed/\".to_string()),##$_0A$##\n    ]);##$_0A$####$_0A$##    // Call get_feeds() from feed_reader\n    module##$_0A$##\n    feed_reader::feed_reader::get_feeds(&rss_feeds);##$_0A$##    // Alternative:\n    Imported full path, use short path here.##$_0A$##\n    //get_feeds(&rss_feeds);##$_0A$##}\n\n    ```\n\n\n    ターミナルで `cargo build` を再実行しコードをビルドします。\n\n\n    ```shell\n\n    cargo build\n\n    ```\n\n\n    以下は、HTTPリクエストやXML解析に関する一般的なコードを参照した際に発生する可能性のあるビルドエラーの例です。\n\n\n    1. エラー： `could not find blocking in reqwest`\n\n    解決策：`Config.toml` ファイルで `reqwest` クレートの `blocking` 機能を有効にします（`reqwest = {\n    version = \"0.11.20\", features = [\"blocking\"] }`）\n\n    2. エラー：`failed to resolve: use of undeclared crate or module reqwest`\n\n    解決策：`reqwest` クレートを追加します\n\n    3. エラー：`failed to resolve: use of undeclared crate or module roxmltree`\n\n    解決策：`roxmltree` クレートを追加します\n\n\n    ```shell\n\n    vim Config.toml\n\n\n    reqwest = { version = \"0.11.20\", features = [\"blocking\"] }\n\n    ```\n\n\n    ```shell\n\n    cargo add reqwest\n\n    cargo add roxmltree\n\n    ```\n\n\n    **ヒント**：エラーメッセージの文字列を `Rust \u003Cerror message>`\n    としてブラウザで検索し、欠落しているクレートが利用可能であるかどうか確認しましょう。通常、検索結果に「crates.io」が表示され、そこから不足している依存関係を追加できます。\n\n\n    ビルドが成功したら、`cargo run` でコードを実行し、Hacker NewsのRSSフィードの出力を確認します。\n\n\n    ![VS Codeのターミナルでcargo runを実行して、Hacker\n    NewsのXMLフィードを取得](https://about.gitlab.com/images/blogimages/learn-rust-with-ai-code-suggestions-advanced-programming/vs_code_terminal_fetch_rss_feed_output_hacker_news.png){:\n    .shadow}\n\n\n    XML本文を人間が読める形式に解析するにはどうすればいいでしょうか？次のセクションでは、既存の解決策とRustのクレートがどのように機能するかを説明します。\n\n\n    ## クレート\n\n    RSSフィードは共通のプロトコルと仕様に基づいており、XMLを解析して下位のオブジェクト構造を理解するのは、例えば車輪のように、すでに存在しているものを再び深く掘り下げて再発明するかのようです。。こういったタスクに対するおすすめのアプローチは、過去に同じ問題に直面した人がいないか、また、その問題を解決するためのコードがすでに作られていないかを調べることです。\n\n\n    Rustでの再利用可能なライブラリコードは\n    [`Crates`](https://doc.rust-lang.org/rust-by-example/crates.html)と呼ばれる単位で整理され、パッケージで提供されます。これらはcrates.ioのパッケージレジストリで利用可能です。これらの依存関係をプロジェクトに追加するには、`Config.toml`\n    ファイルの `[dependencies]` セクションを編集するか、`cargo add \u003Cname>` コマンドを使用します。\n\n\n    リーダーアプリケーションでは、[feed-rs\n    クレート](https://crates.io/crates/feed-rs)を使用したいため、新しいターミナルを開き、次のコマンドを実行してください。\n\n\n    ```shell\n\n    cargo add feed-rs\n\n    ```\n\n\n    ![VS\n    Codeのターミナル：クレートを追加し、Config.tomlで確認](https://about.gitlab.com/images/blogimages/learn-rust-with-ai-code-suggestions-advanced-programming/vs_code_rust_crate_add_feed-rs_explained.png)\n\n\n    ### feed-rs：XMLフィードの解析\n\n    `src/feed_reader.rs` に移動し、XML本文を解析する部分に対して修正を加えます。コード提案は、`feed-rs` クレートの\n    `parser::parse` 関数をどのように呼び出すか理解していますが、ひとつだけ特別な点があります。`feed-rs`\n    は文字列をrawバイトとして入力し、自らエンコーディングを判断します。ただし、コメントに指示を追加することで、期待の結果を得ることは可能です。\n\n\n    ```rust\n\n                // Parse XML body with feed_rs parser, input in bytes\n                let parsed_body = feed_rs::parser::parse(body.as_bytes()).unwrap();\n\n    ```\n\n\n    ![コード提案：`get_feeds()`\n    関数を含む公開モジュール、ステップ5：XMLパーサーをfeed-rsに変更](https://about.gitlab.com/images/blogimages/learn-rust-with-ai-code-suggestions-advanced-programming/code_suggestions_rust_module_function_05_use_feed_rs_to_parse.png){:\n    .shadow}\n\n\n    `feed-rs` を使用するメリットは即座に可視化できるものでなく、`cargo run`\n    で出力を確認するとその効果が明らかになります。すべてのキーと値がそれぞれのRustオブジェクト型にマッピングされ、さらに高度な処理に利用できるようになります。\n\n\n    ![VS Codeのターミナル、cargo runを実行してHacker\n    NewsのXMLフィードを取得](https://about.gitlab.com/images/blogimages/learn-rust-with-ai-code-suggestions-advanced-programming/vs_code_terminal_fetch_rss_feed_output_hacker_news_feed_rs.png){:\n    .shadow}\n\n\n    ## ランタイム設定：プログラム引数\n\n    ここまで、コンパイル時にバイナリに埋め込まれ、ハードコードされたRSSフィードの値を使用してプログラムを実行してきました。次のステップでは、実行時にRSSフィードを設定できるようにします。\n\n\n    Rustでは、標準miscライブラリに[プログラム引数](https://doc.rust-lang.org/rust-by-example/std_misc/arg.html)を処理するための機能が用意されています。[プログラム引数を解析](https://doc.rust-lang.org/rust-by-example/std_misc/arg/matching.html)することで、高度なプログラム引数パーサー（たとえば[clap](https://docs.rs/clap/latest/clap/)クレート）を使用したり、プログラムパラメータを構成ファイルやフォーマット（[TOML](https://toml.io/en/)やYAML）に移したりするよりも、簡単かつ効率的に学習を進めることができます。実際にいくつかの方法を試した結果、学習効果を最大限に高めるには、この方法が最適であると判断しましたが、唯一の方法ではありません。他の方法でRSSフィードの設定を試してみる価値もあります。\n\n\n    単純な解決策としては、コマンドパラメータを `\"name,url\"` の文字列ペアとして渡してから、`,`\n    で分割して名前とURLの値を抽出します。コード提案に、これらの操作を実行して新しい値で `rss_feeds`\n    ハッシュマップを拡張するようコメントで指示します。変数が変更可能でない可能性があるため、`let rss_feeds` を `let mut\n    rss_feeds` に変更する必要がある点にご注意ください。\n\n\n    `src/main.rs` に移動し、`rss_feeds` 変数の後に次のコードを `main()`\n    関数の追加します。まずはコメントでプログラム引数を定義し、提案されたコードスニペットを確認します。\n\n\n    ```rust\n\n        // Program args, format \"name,url\"\n        // Split value by , into name, url and add to rss_feeds\n\n    ```\n\n\n    ![プログラム引数に関するコード提案、および rss_feeds\n    変数のために名前とURLの値を分割するコード提案](https://about.gitlab.com/images/blogimages/learn-rust-with-ai-code-suggestions-advanced-programming/code_suggestions_rust_program_args_boring_solution.png){:\n    .shadow}\n\n\n    コード全体の例は次のようになります。\n\n\n    ```rust\n\n    fn main() {##$_0A$##    // Define RSS feed URLs in the variable\n    rss_feeds##$_0A$##    // Use a HashMap##$_0A$##    // Add Hacker News and\n    TechCrunch##$_0A$##    // Ensure to use String as type##$_0A$##    let mut\n    rss_feeds = HashMap::from([##$_0A$##        (\"Hacker News\".to_string(),\n    \"https://news.ycombinator.com/rss\".to_string()),##$_0A$##\n    (\"TechCrunch\".to_string(),\n    \"https://techcrunch.com/feed/\".to_string()),##$_0A$##\n    ]);##$_0A$####$_0A$##    // Program args, format \"name,url\"##$_0A$##    //\n    Split value by , into name, url and add to rss_feeds##$_0A$##    for arg in\n    std::env::args().skip(1) {##$_0A$##        let mut split =\n    arg.split(\",\");##$_0A$##        let name =\n    split.next().unwrap();##$_0A$##        let url =\n    split.next().unwrap();##$_0A$##        rss_feeds.insert(name.to_string(),\n    url.to_string());##$_0A$##    }##$_0A$####$_0A$##    // Call get_feeds()\n    from feed_reader module##$_0A$##\n    feed_reader::feed_reader::get_feeds(&rss_feeds);##$_0A$##    // Alternative:\n    Imported full path, use short path here.##$_0A$##\n    //get_feeds(&rss_feeds);##$_0A$##}\n\n    ```\n\n\n    プログラムの引数を `cargo run` コマンドに直接渡すことができます。その際は引数の前に `--`をつけます。\n    すべての引数をダブルクォートで囲み、名前の後にカンマを付けてRSSフィードのURLを引数として渡します。引数は空白で区切ります。\n\n\n    ```shell\n\n    cargo build\n\n\n    cargo run -- \"GitLab Blog,https://about.gitlab.com/atom.xml\"\n    \"CNCF,https://www.cncf.io/feed/\"\n\n    ```\n\n\n    ![VS\n    Codeターミナル、GitLabブログのRSSフィード出力例](https://about.gitlab.com/images/blogimages/learn-rust-with-ai-code-suggestions-advanced-programming/vs_code_terminal_gitlab_blog_rss_feed_example.png){:\n    .shadow}\n\n\n    ### ユーザー入力のエラーハンドリング\n\n    提供されたユーザー入力がプログラムで想定される内容と異なる場合、[エラーを発生](https://doc.rust-lang.org/rust-by-example/error.html)させ、呼び出し元がプログラム引数を修正できるようにする必要があります。たとえば、不正なURL形式が渡された場合、それをランタイムエラーとして処理する必要があります。コード提案に対し、URLが無効な場合はエラーをスローするようコメントで指示します。\n\n\n    ```rust\n\n        // Ensure that URL contains a valid format, otherwise throw an error\n\n    ```\n\n\n    ひとつの解決策として、`url` 変数が `http://` または `https://` で始まっているかを確認し、そうでなければ [panic!\n    マクロ](https://doc.rust-lang.org/rust-by-example/std/panic.html)を使ってエラーをスローする方法があります。コード全体の例は次のようになります。\n\n\n    ```rust\n\n        // Program args, format \"name,url\"\n        // Split value by , into name, url and add to rss_feeds\n        for arg in std::env::args().skip(1) {\n            let mut split = arg.split(\",\");\n            let name = split.next().unwrap();\n            let url = split.next().unwrap();\n\n            // Ensure that URL contains a valid format, otherwise throw an error\n            if !url.starts_with(\"http://\") && !url.starts_with(\"https://\") {\n                panic!(\"Invalid URL format: {}\", url);\n            }\n\n            rss_feeds.insert(name.to_string(), url.to_string());\n        }\n\n    ```\n\n\n    任意のURL文字列から `:` を削除してエラーハンドリングをテストします。`RUST_BACKTRACE=full`\n    環境変数を追加すると、`panic()` 呼び出しが発生した際に詳細な出力を取得できます。\n\n\n    ```shell\n\n    RUST_BACKTRACE=full cargo run -- \"GitLab\n    Blog,https://about.gitlab.com/atom.xml\" \"CNCF,https//www.cncf.io/feed/\"\n\n    ```\n\n\n    ![間違ったURL形式によるパニックエラーのバックトレースが表示されたVS\n    Codeターミナル](https://about.gitlab.com/images/blogimages/learn-rust-with-ai-code-suggestions-advanced-programming/vs_code_terminal_url_format_error_panic_backtrace.png){:\n    .shadow}\n\n\n    ## 永続性とデータ保存\n\n    フィードデータを保存する単純な解決策は、解析されたデータを新しいファイルに書き出すことです。コード提案に、RSSフィードの名前と現在のISO日付を含むパターンでファイルを保存するよう指示します。\n\n\n    ```rust\n\n        // Parse XML body with feed_rs parser, input in bytes\n        let parsed_body = feed_rs::parser::parse(body.as_bytes()).unwrap();\n\n        // Print the result\n        println!(\"{:#?}\", parsed_body);\n\n        // Dump the parsed body to a file, as name-current-iso-date.xml\n        let now = chrono::offset::Local::now();\n        let filename = format!(\"{}-{}.xml\", name, now.format(\"%Y-%m-%d\"));\n        let mut file = std::fs::File::create(filename).unwrap();\n        file.write_all(body.as_bytes()).unwrap();\n\n    ```\n\n\n    この処理の一例として、[chronoクレート](https://crates.io/crates/chrono)を使用する方法があります。`cargo\n    add chrono` を実行して追加し、その後 `cargo build` と `cargo run` を再度実行します。\n\n\n    ファイルは `cargo run` が実行されたディレクトリに保存されます。バイナリを `target/debug/`\n    ディレクトリで直接実行している場合、すべてのファイルはそのディレクトリにダンプされます。\n\n\n    ![CNCFのRSSフィード内容を含むファイルがディスクに保存された状態のVS\n    Code](https://about.gitlab.com/images/blogimages/learn-rust-with-ai-code-suggestions-advanced-programming/vs_code_cncf_rss_feed_saved_on_disk.png)\n\n\n    ## 最適化\n\n    `rss_feeds`\n    変数内のエントリは逐次実行されています。もし100件以上のURLがリストに設定されている場合、データの取得と処理にはかなりの時間がかかるでしょう。複数のフェッチリクエストを並行して実行できたらどうでしょうか？\n\n\n    ### 非同期実行\n\n    Rustでは、[スレッド](https://doc.rust-lang.org/book/ch16-01-threads.html)を使用した非同期実行が可能です。\n\n\n    最も簡単な解決策は、各RSSフィードURLごとにスレッドを生成することです。最適化戦略については後のセクションで説明します。並行実行を開始する前に、\n    `time` コマンドの前に`cargo run` をつけて、逐次実行コードの実行時間を測定しましょう。\n\n\n    ```text\n\n    time cargo run -- \"GitLab Blog,https://about.gitlab.com/atom.xml\"\n    \"CNCF,https://www.cncf.io/feed/\"\n\n\n    0.21s user 0.08s system 10% cpu 2.898 total\n\n    ```\n\n\n    なお、この演習では、手動のコーディング作業が多くなる場合があります。並列実行の影響をより効果的に比較するには、逐次実行の動作状態を新しいGitコミットとブランチ\n    `sequential-exec` に保存してください。\n\n    ```shell\n\n    git commit -avm \"Sequential execution working\"\n\n    git checkout -b sequential-exec\n\n    git push -u origin sequential-exec\n\n\n    git checkout main\n\n    ```\n\n\n    ### スレッドの生成\n\n    `src/feed_reader.rs` を開き、`get_feeds()`\n    関数をリファクタリングします。まず、現在の状態をGitコミットで保存し、その後、関数のスコープ内の内容を削除します。次に、コード提案への指示と以下のコードコメントを入力します。\n\n\n    1. `// Store threads in\n    vector`：スレッドのハンドルをベクターに格納し、関数呼び出しの最後にスレッドが終了するまで待機できるようにします。\n\n    2. `// Loop over rss_feeds and spawn\n    threads`：すべてのRSSフィードを反復処理するためのコードを作成し、新しいスレッドを作成します。\n\n\n    `thread` モジュールと `time` モジュールを扱うには、次の `use` 文を追加します。\n\n\n    ```rust\n\n        use std::thread;\n        use std::time::Duration;\n\n    ```\n\n\n    その後、forループを閉じるまでコードを書き進めます。コード提案は、自動的にスレッドハンドルを `threads`\n    ベクター変数に追加し、関数の最後でスレッドを結合（join）するよう提案します。\n\n\n    ```rust\n\n        pub fn get_feeds(rss_feeds: &HashMap\u003CString, String>) {\n\n            // Store threads in vector\n            let mut threads: Vec\u003Cthread::JoinHandle\u003C()>> = Vec::new();\n\n            // Loop over rss_feeds and spawn threads\n            for (name, url) in rss_feeds {\n                let thread_name = name.clone();\n                let thread_url = url.clone();\n                let thread = thread::spawn(move || {\n\n                });\n                threads.push(thread);\n            }\n\n            // Join threads\n            for thread in threads {\n                thread.join().unwrap();\n            }\n        }\n\n    ```\n\n\n    次に、`thread` クレートを追加し、もう一度コードをビルドし実行します。\n\n\n    ```shell\n\n    cargo add thread\n\n\n    cargo build\n\n\n    cargo run -- \"GitLab Blog,https://about.gitlab.com/atom.xml\"\n    \"CNCF,https://www.cncf.io/feed/\"\n\n    ```\n\n\n    この段階では、データの処理や出力は行われていません。次のステップに移る前に、ここで新たに導入されたキーワードについて解説します。\n\n\n    ### 関数スコープ、スレッド、クロージャ\n\n    提案されるコードには新しいキーワードやデザインパターンが含まれることもあるため、これらについて理解しておくことが重要になります。スレッドハンドルは\n    `thread::JoinHandle`\n    という型で、スレッドが終わるのを待機するために使用します（[join()](https://doc.rust-lang.org/book/ch16-01-threads.html#waiting-for-all-threads-to-finish-using-join-handles)メソッドを使います）。\n\n\n    `thread::spawn()`\n    は新しいスレッドを生成し、このスレッド内では、関数オブジェクトを渡すことができます。この場合、[クロージャ](https://doc.rust-lang.org/book/ch13-01-closures.html)式が無名関数として渡されます。また、クロージャ入力は\n    `||` 構文を使用して渡されますが、この際には[`move`\n    クロージャ](https://doc.rust-lang.org/book/ch16-01-threads.html#using-move-closures-with-threads)が関数スコープの変数をスレッドスコープに移動します。これにより、どの変数を新しい関数やクロージャスコープに渡すかを手動で指定する必要がなくなります。\n\n\n    ただし、これには制限があります。`rss_feeds` は参照 `&` で、`get_feeds()`\n    関数の呼び出し元からパラメータとして渡されています。この変数は関数スコープ内でのみ有効です。このエラーを引き起こすには、次のコードスニペットを使用します。\n\n\n    ```rust\n\n    pub fn get_feeds(rss_feeds: &HashMap\u003CString, String>) {##$_0A$####$_0A$## //\n    Store threads in vector##$_0A$##    let mut threads:\n    Vec\u003Cthread::JoinHandle\u003C()>> = Vec::new();##$_0A$####$_0A$##    // Loop over\n    rss_feeds and spawn threads##$_0A$##    for (key, value) in rss_feeds\n    {##$_0A$##        let thread = thread::spawn(move || {##$_0A$##\n    println!(\"{}\", key);##$_0A$##        });##$_0A$##    }##$_0A$##}\n\n    ```\n\n\n    ![VS\n    Codeターミナル、参照とスレッドのmoveクロージャに関する変数スコープエラー](https://about.gitlab.com/images/blogimages/learn-rust-with-ai-code-suggestions-advanced-programming/vs_code_terminal_cargo_build_error_function_threads_variable_scopes.png){:\n    .shadow}\n\n\n    `key` 変数は関数スコープ内で作成されていますが、`rss_feeds`\n    変数を参照しているため、スレッドスコープに移動することはできません。関数パラメータ `rss_feeds`\n    のハッシュマップからアクセスされる値は、`clone()` を使ってローカルコピーを作成する必要があります。\n\n\n    ![VS\n    Codeターミナル、cloneを使用したスレッドの生成](https://about.gitlab.com/images/blogimages/learn-rust-with-ai-code-suggestions-advanced-programming/code_suggestions_rust_thread_spawn_clone.png){:\n    .shadow}\n\n\n    ```rust\n\n    pub fn get_feeds(rss_feeds: &HashMap\u003CString, String>) {\n\n        // Store threads in vector\n        let mut threads: Vec\u003Cthread::JoinHandle\u003C()>> = Vec::new();\n\n        // Loop over rss_feeds and spawn threads\n        for (name, url) in rss_feeds {\n            let thread_name = name.clone();\n            let thread_url = url.clone();\n            let thread = thread::spawn(move || {\n                // Use thread_name and thread_url as values, see next chapter for instructions.\n\n    ```\n\n\n    ## フィードのXMLの解析およびオブジェクト型への変換\n\n    次のステップは、スレッド内のクロージャでRSSフィードの解析手順を繰り返すことです。コード提案の指示とともに以下のコードコメントを追加します。\n\n\n    1. `// Parse XML body with feed_rs parser, input in\n    bytes`：コード提案に対し、RSSフィードのURLコンテンツを取得し、`feed_rs` クレートの関数で解析したいという要望を伝えます。\n\n    2. `// Check feed_type attribute feed_rs::model::FeedType::RSS2 or Atom and\n    print its name`：`feed_type` 属性を\n    [`feed_rs::model::FeedType`](https://docs.rs/feed-rs/latest/feed_rs/model/enum.FeedType.html)\n    と照合してフィードの種類を抽出します。この場合、コード提案に対して、照合の対象とする正確なEnum値を指示する必要があります。\n\n\n    ![コード提案に特定のフィードタイプと照合するよう指示](https://about.gitlab.com/images/blogimages/learn-rust-with-ai-code-suggestions-advanced-programming/code_suggestions_feed_rs_type_condition.png){:\n    .shadow}\n\n\n    ```rust\n\n                // Parse XML body with feed_rs parser, input in bytes\n                let body = reqwest::blocking::get(thread_url).unwrap().bytes().unwrap();\n                let feed = feed_rs::parser::parse(body.as_ref()).unwrap();\n\n                // Check feed_type attribute feed_rs::model::FeedType::RSS2 or Atom and print its name\n                if feed.feed_type == feed_rs::model::FeedType::RSS2 {\n                    println!(\"{} is an RSS2 feed\", thread_name);\n                } else if feed.feed_type == feed_rs::model::FeedType::Atom {\n                    println!(\"{} is an Atom feed\", thread_name);\n                }\n\n    ```\n\n\n    プログラムをもう一度ビルドして実行し、出力を確認します。\n\n\n    ```text\n\n    time cargo run -- \"GitLab Blog,https://about.gitlab.com/atom.xml\"\n    \"CNCF,https://www.cncf.io/feed/\"\n\n\n    CNCF is an RSS2 feed\n\n    TechCrunch is an RSS2 feed\n\n    GitLab Blog is an Atom feed\n\n    Hacker News is an RSS2 feed\n\n    ```\n\n\n    フィードURLをブラウザで開くか、以前にダウンロードしたファイルを調べて、この出力を確認します。\n\n\n    Hacker News はRSS\n    2.0をサポートしており、`channel(title,link,description,item(title,link,pubDate,comments))`\n    の構造を持っています。TechCrunchとCNCFブログは同様の構造をしています。\n\n    ```xml\n\n    \u003Crss version=\"2.0\">\u003Cchannel>\u003Ctitle>Hacker\n    News\u003C/title>\u003Clink>https://news.ycombinator.com/\u003C/link>\u003Cdescription>Links for\n    the intellectually curious, ranked by\n    readers.\u003C/description>\u003Citem>\u003Ctitle>Writing a debugger from scratch:\n    Breakpoints\u003C/title>\u003Clink>https://www.timdbg.com/posts/writing-a-debugger-from-scratch-part-5/\u003C/link>\u003CpubDate>Wed,\n    27 Sep 2023 06:31:25\n    +0000\u003C/pubDate>\u003Ccomments>https://news.ycombinator.com/item?id=37670938\u003C/comments>\u003Cdescription>\u003C![CDATA[\u003Ca\n    href=\"https://news.ycombinator.com/item?id=37670938\">Comments\u003C/a>]]>\u003C/description>\u003C/item>\u003Citem>\n\n    ```\n\n\n    GitLabブログには[Atom](https://datatracker.ietf.org/doc/html/rfc4287)フィード形式が使用されています。RSSに類似していますが異なる解析ロジックが必要です。\n\n    ```xml\n\n    \u003C?xml version='1.0' encoding='utf-8' ?>\n\n    \u003Cfeed xmlns='http://www.w3.org/2005/Atom'>\n\n    \u003C!-- / Get release posts -->\n\n    \u003C!-- / Get blog posts -->\n\n    \u003Ctitle>GitLab\u003C/title>\n\n    \u003Cid>https://about.gitlab.com/blog\u003C/id>\n\n    \u003Clink href='https://about.gitlab.com/blog/' />\n\n    \u003Cupdated>2023-09-26T00:00:00+00:00\u003C/updated>\n\n    \u003Cauthor>\n\n    \u003Cname>The GitLab Team\u003C/name>\n\n    \u003C/author>\n\n    \u003Centry>\n\n    \u003Ctitle>Atlassian Server ending: Goodbye disjointed toolchain, hello\n    DevSecOps platform\u003C/title>\n\n    \u003Clink\n    href='https://about.gitlab.com/blog/atlassian-server-ending-move-to-a-single-devsecops-platform/'\n    rel='alternate' />\n\n    \u003Cid>https://about.gitlab.com/blog/atlassian-server-ending-move-to-a-single-devsecops-platform/\u003C/id>\n\n    \u003Cpublished>2023-09-26T00:00:00+00:00\u003C/published>\n\n    \u003Cupdated>2023-09-26T00:00:00+00:00\u003C/updated>\n\n    \u003Cauthor>\n\n    \u003Cname>Dave Steer, Justin Farris\u003C/name>\n\n    \u003C/author>\n\n    ```\n\n\n    ### 汎用的なフィードデータ型のマッピング\n\n    [`roxmltree::Document::parse`](https://docs.rs/roxmltree/latest/roxmltree/struct.Document.html)\n    を使用する場合、XMLノードツリーとその特定のタグ名を理解する必要があります。幸い、[`feed_rs::model::Feed`](https://docs.rs/feed-rs/latest/feed_rs/model/struct.Feed.html)\n    はRSSとAtomフィードの統合モデルを提供しているため、引き続き `feed_rs` クレートを使用して進めましょう。\n\n\n    1. Atom：Feed->Feed、Entry->Entry\n\n    2. RSS：Channel->Feed、Item->Entry\n\n\n    上記のマッピングに加えて、必要な属性を抽出し、それらのデータ型をマッピングする必要があります。[feed_rs::modelのドキュメント](https://docs.rs/feed-rs/latest/feed_rs/model/index.html)を開き、構造体やそのフィールド、実装について理解しながら進めることをお勧めします。これは、`feed_rs`\n    の実装に特有の型変換エラーやコンパイルエラーが発生するのを防ぐためです。\n\n\n    [`Feed`](https://docs.rs/feed-rs/latest/feed_rs/model/struct.Feed.html) 構造体は\n    `title` を提供しますが、その型は `Option\u003CText>`\n    で、値が設定されているか、何もないかのいずれかです。[`Entry`](https://docs.rs/feed-rs/latest/feed_rs/model/struct.Entry.html)\n    構造体は以下の情報を提供します。\n\n\n    1. `title`：`Option\u003CText>`\n    型で、[`Text`](https://docs.rs/feed-rs/latest/feed_rs/model/struct.Text.html)\n    には `content` フィールドが `String` 型として含まれています。\n\n    2. `published`：`Option\u003CDateTime\u003CUtc>>`\n    型で、[`DateTime`](https://docs.rs/chrono/latest/chrono/struct.DateTime.html) は\n    [`format()`\n    メソッド](https://docs.rs/chrono/latest/chrono/struct.DateTime.html#method.format)を持ちます。\n\n    3. `summary`：`Option\u003CText>`\n    型で、[`Text`](https://docs.rs/feed-rs/latest/feed_rs/model/struct.Text.html)\n    には `content` フィールドが `String` 型として含まれています。\n\n    4. `links`：`Vec\u003CLink>`\n    型で、[`Link`](https://docs.rs/feed-rs/latest/feed_rs/model/struct.Link.html)\n    項目のベクターです。`href` 属性が生のURL文字列を提供します。\n\n\n    1から4に従って、フィードエントリから必要なデータを抽出します。繰り返しますが、すべての `Option` 型には `unwrap()`\n    を呼び出す必要があります。このため、コード提案に対してより直接的な指示が求められます。\n\n\n    ```rust\n\n                    // https://docs.rs/feed-rs/latest/feed_rs/model/struct.Feed.html\n                    // https://docs.rs/feed-rs/latest/feed_rs/model/struct.Entry.html\n                    // Loop over all entries, and print\n                    // title.unwrap().content\n                    // published.unwrap().format\n                    // summary.unwrap().content\n                    // links href as joined string\n                    for entry in feed.entries {\n                        println!(\"Title: {}\", entry.title.unwrap().content);\n                        println!(\"Published: {}\", entry.published.unwrap().format(\"%Y-%m-%d %H:%M:%S\"));\n                        println!(\"Summary: {}\", entry.summary.unwrap().content);\n                        println!(\"Links: {:?}\", entry.links.iter().map(|link| link.href.clone()).collect::\u003CVec\u003CString>>().join(\", \"));\n                        println!();\n                    }\n\n    ```\n\n\n    ![特定の要件に基づいてフィードエントリの型を出力するためのコード提案](https://about.gitlab.com/images/blogimages/learn-rust-with-ai-code-suggestions-advanced-programming/code_suggestions_print_feed_entries_fields_with_rust_type_specifics.png){:\n    .shadow}\n\n\n    ### Option::unwrap()によるエラーハンドリング\n\n    プログラムを再ビルドして実行した後、複数行の指示を引き続き処理します。補足：`unwrap()` は空の値に遭遇すると `panic!`\n    マクロを呼び出し、プログラムを強制終了させます。これは、フィードデータ内に `summary` のようなフィールドが設定されていない場合に発生します。\n\n\n    ```shell\n\n    GitLab Blog is an Atom feed\n\n    Title: How the Colmena project uses GitLab to support citizen journalists\n\n    Published: 2023-09-27 00:00:00\n\n    thread '\u003Cunnamed>' panicked at 'called `Option::unwrap()` on a `None`\n    value', src/feed_reader.rs:40:59\n\n    ```\n\n\n    解決策のひとつとして、[`std::Option::unwrap_or_else`](https://doc.rust-lang.org/std/option/enum.Option.html#method.unwrap_or_else)\n    を使用し、デフォルト値として空の文字列を設定することが挙げられます。この構文には、空の `Text` 構造体のインスタンスを返すクロージャが必要です。\n\n\n    問題を解決する上で、正しい初期化方法を見つけるために試行錯誤を重ねました。単に空の文字列を渡すだけではカスタム型ではうまく機能しませんでした。以下に、筆者が試したすべてのアプローチとリサーチの過程をまとめました。\n\n\n    ```rust\n\n    // Problem: The `summary` attribute is not always initialized. unwrap() will\n    panic! then.\n\n    // Requires use mime; and use feed_rs::model::Text;\n\n    /*\n\n    // 1st attempt: Use unwrap() to extraxt Text from Option\u003CText> type.\n\n    println!(\"Summary: {}\", entry.summary.unwrap().content);\n\n    // 2nd attempt. Learned about unwrap_or_else, passing an empty string.\n\n    println!(\"Summary: {}\", entry.summary.unwrap_or_else(|| \"\").content);\n\n    // 3rd attempt. summary is of the Text type, pass a new struct\n    instantiation.\n\n    println!(\"Summary: {}\", entry.summary.unwrap_or_else(|| Text{}).content);\n\n    // 4th attempt. Struct instantiation requires 3 field values.\n\n    println!(\"Summary: {}\", entry.summary.unwrap_or_else(|| Text{\"\", \"\",\n    \"\"}).content);\n\n    // 5th attempt. Struct instantation with public fields requires key: value\n    syntax\n\n    println!(\"Summary: {}\", entry.summary.unwrap_or_else(|| Text{content_type:\n    \"\", src: \"\", content: \"\"}).content);\n\n    // 6th attempt. Reviewed expected Text types in\n    https://docs.rs/feed-rs/latest/feed_rs/model/struct.Text.html and created\n    Mime and String objects\n\n    println!(\"Summary: {}\", entry.summary.unwrap_or_else(|| Text{content_type:\n    mime::TEXT_PLAIN, src: String::new(), content: String::new()}).content);\n\n    // 7th attempt: String and Option\u003CString> cannot be casted automagically.\n    Compiler suggested using `Option::Some()`.\n\n    println!(\"Summary: {}\", entry.summary.unwrap_or_else(|| Text{content_type:\n    mime::TEXT_PLAIN, src: Option::Some(), content: String::new()}).content);\n\n    */\n\n\n    // xth attempt: Solution. Option::Some() requires a new String object.\n\n    println!(\"Summary: {}\", entry.summary.unwrap_or_else(|| Text{content_type:\n    mime::TEXT_PLAIN, src: Option::Some(String::new()), content:\n    String::new()}).content);\n\n    ```\n\n\n    このアプローチは、コードが複雑で読みにくく、さらにコード提案の支援がなかったため、手動でコーディングする必要があり、満足のいくものではありませんでした。一旦立ち止まり、アプローチを見直しました。`Option`\n    が `none` の場合に`unwrap()`\n    はエラーをスローするのであれば、そのエラーを処理する簡単な方法があるのではと考え、コード提案に新しいコメントで質問しました。\n\n\n    ```shell\n\n                    // xth attempt: Solution. Option::Some() requires a new String object.\n                    println!(\"Summary: {}\", entry.summary.unwrap_or_else(|| Text{content_type: mime::TEXT_PLAIN, src: Option::Some(String::new()), content: String::new()}).content);\n\n                    // Alternatively, use Option.is_none()\n\n    ```\n\n\n    ![コード提案によるOptions.is_noneを使った代替案](https://about.gitlab.com/images/blogimages/learn-rust-with-ai-code-suggestions-advanced-programming/code_suggestions_after_complex_unwrap_or_else_ask_for_alternative_option.png){:\n    .shadow}\n\n\n    結果として、読みやすさが向上し、`unwrap()`\n    による無駄なCPUサイクルが減りました。複雑な問題を解決することから単純な解決策を使用することまでの学習過程を大幅に短縮できました。まさにウィンウィンです。\n\n\n    忘れないうちに、XMLデータをディスクに保存する指示を再度追加して、リーダーアプリを完成させましょう。\n\n\n    ```rust\n\n                    // Dump the parsed body to a file, as name-current-iso-date.xml\n                    let file_name = format!(\"{}-{}.xml\", thread_name, chrono::Local::now().format(\"%Y-%m-%d-%H-%M-%S\"));\n                    let mut file = std::fs::File::create(file_name).unwrap();\n                    file.write_all(body.as_ref()).unwrap();\n\n    ```\n\n\n    プログラムをビルドして実行し、出力を確認します。\n\n\n    ```shell\n\n    cargo build\n\n\n    time cargo run -- \"GitLab Blog,https://about.gitlab.com/atom.xml\"\n    \"CNCF,https://www.cncf.io/feed/\"\n\n    ```\n\n\n    ![VS Codeターミナルでcargo\n    runを実行し、フォーマットされたフィードエントリの出力](https://about.gitlab.com/images/blogimages/learn-rust-with-ai-code-suggestions-advanced-programming/vs_code_terminal_cargo_run_formatted_output_final.png)\n\n\n    ## ベンチマーク\n\n\n    ### 逐次実行と並列実行のベンチマークの比較\n\n    それぞれ5つのサンプルを作成して、実行時間のベンチマークを比較します。\n\n\n    1. 逐次実行\n\n    2. 並列実行\n\n\n    ```shell\n\n    # Sequential\n\n    git checkout sequential-exec\n\n\n    time cargo run -- \"GitLab Blog,https://about.gitlab.com/atom.xml\"\n    \"CNCF,https://www.cncf.io/feed/\"\n\n\n    0.21s user 0.08s system 10% cpu 2.898 total\n\n    0.21s user 0.08s system 11% cpu 2.585 total\n\n    0.21s user 0.09s system 10% cpu 2.946 total\n\n    0.19s user 0.08s system 10% cpu 2.714 total\n\n    0.20s user 0.10s system 10% cpu 2.808 total\n\n    ```\n\n\n    ```shell\n\n    # Parallel\n\n    git checkout parallel-exec\n\n\n    time cargo run -- \"GitLab Blog,https://about.gitlab.com/atom.xml\"\n    \"CNCF,https://www.cncf.io/feed/\"\n\n\n    0.19s user 0.08s system 17% cpu 1.515 total\n\n    0.18s user 0.08s system 16% cpu 1.561 total\n\n    0.18s user 0.07s system 17% cpu 1.414 total\n\n    0.19s user 0.08s system 18% cpu 1.447 total\n\n    0.17s user 0.08s system 16% cpu 1.453 total\n\n    ```\n\n\n    4つのRSSフィードスレッドを並列実行した場合、CPU使用率は増加しましたが、逐次実行に比べて全体の実行時間はほぼ半減しました。この点を踏まえて、Rustの学習を続け、コードと機能を最適化していきます。\n\n\n    なお、ここではCargoを使ってデバッグビルドを実行しており、最適化されたリリースビルドはまだ実行していません。また、並列実行にはいくつか注意点があります。一部のHTTPエンドポイントはレート制限を設けており、並列処理がこの制限に引っかかりやすくなる可能性があります。\n\n\n    複数のスレッドを並列実行するシステムも過負荷になる可能性があります。これは、カーネル内でのコンテキストスイッチ（スレッド間の切り替え）を通じて各スレッドにリソースを割り当てる必要があるためです。1つのスレッドが計算リソースを使用している間、他のスレッドは待機状態になります。スレッドが多すぎると、システム全体が遅くなり、処理がスピードアップするどころか逆に遅くなることもあります。解決策としては、呼び出し元がキューにタスクを追加し、定義された数のワーカースレッドが非同期実行のためにタスクを処理する[ワークキュー](https://docs.rs/work-queue/latest/work_queue/)などの設計パターンがあります。\n\n\n    Rustでは、スレッド間のデータ同期を行う[チャンネル](https://doc.rust-lang.org/rust-by-example/std_misc/channels.html)を利用することもできます。競合状態を防ぐために、セーフロックを提供する[ミューテックス](https://doc.rust-lang.org/std/sync/struct.Mutex.html)も利用可能です。\n\n\n    ### Rustのキャッシュを使用したCI/CD\n\n    以下のCI/CD構成を `.gitlab-ci.yml` ファイルに追加します。この `run-latest` ジョブは `cargo run`\n    をRSSフィードURLの例とともに呼び出し、実行時間を継続的に計測します。\n\n\n    ```text\n\n    stages:\n      - build\n      - test\n      - run\n\n    default:\n      image: rust:latest\n      cache:\n        key: ${CI_COMMIT_REF_SLUG}\n        paths:\n          - .cargo/bin\n          - .cargo/registry/index\n          - .cargo/registry/cache\n          - target/debug/deps\n          - target/debug/build\n        policy: pull-push\n\n    # Cargo data needs to be in the project directory for being cached.\n\n    variables:\n      CARGO_HOME: ${CI_PROJECT_DIR}/.cargo\n\n    build-latest:\n      stage: build\n      script:\n        - cargo build --verbose\n\n    test-latest:\n      stage: build\n      script:\n        - cargo test --verbose\n\n    run-latest:\n      stage: run\n      script:\n        - time cargo run -- \"GitLab Blog,https://about.gitlab.com/atom.xml\" \"CNCF,https://www.cncf.io/feed/\"\n\n    ```\n\n\n    ![Rust用のGitLab CI/CDパイプライン、cargo\n    runの出力](https://about.gitlab.com/images/blogimages/learn-rust-with-ai-code-suggestions-advanced-programming/gitlab_cicd_pipeline_rust_cargo_run_output.png){:\n    .shadow}\n\n\n    ## 次のステップ\n\n    このブログ記事の執筆は、筆者自身が高度なRustプログラミング技術を学びつつ、コード提案を使って最適な学習過程を見出すという点で難しいものでした。コード提案は、単なる定型的なコードだけでなく、ローカルコンテキストを理解し、記述されたコードが多いほどアルゴリズムの目的や範囲をよりよく把握した迅速なコード生成にも大いに役立ちます。このブログ記事を読むことで、全てではないにしろ、いくつかの課題や解決策についてご理解いただけたかと思います。\n\n\n    RSSフィードの解析は、外部HTTPリクエストや並列最適化を伴うデータ構造が関わるため、ハードルが高めです。経験豊富なRustユーザーであれば、`なぜstd::rssクレートを使わなかったのか？`\n    と疑問に思うかもしれません。その理由は、std::rssは高度な非同期実行に最適化されており、このブログ記事でご説明したさまざまなRustの機能を示したり説明したりすることができないためです。ぜひ、非同期の演習として、[`rss`\n    クレート](https://docs.rs/rss/latest/rss/)を使ってコードを書き直してみてください。\n\n\n    ### 非同期学習のエクササイズ\n\n    このブログ記事で学んだ内容は、永続的なデータ保存やデータ表示に関する理解を今後も深めていく上で基礎となります。引き続きRustを学びながら、リーダーアプリを最適化していくためのアイデアをいくつかご紹介します。\n\n\n    1. データ保存：sqliteなどのデータベースを使用し、RSSフィードの更新履歴を追跡する。\n\n    2. 通知：子プロセスを生成して、Telegramなどに通知を送る機能を追加する。\n\n    3. 機能：リーダータイプを拡張して、REST APIをサポートする。\n\n    4. 構成：RSSフィードやAPIなどの構成ファイルのサポートを追加する。\n\n    5. 効率性：フィルタリングやサブスクライブしたタグのサポートを追加する。\n\n    6.\n    デプロイ：Webサーバーを使用して、Prometheusメトリクスを収集して[Kubernetes](https://about.gitlab.com/ja-jp/blog/what-is-kubernetes/)にデプロイする。\n\n\n    今後のブログ記事では、これらのアイデアのいくつかを取り上げ、その実装方法について説明します。既存のRSSフィードの実装を詳しく調べ、どのように既存のコードをリファクタリングしてRustのライブラリ（`crates`）を活用できるか学びましょう。\n\n\n    ### フィードバックの共有\n\n    [GitLab\n    Duo](https://about.gitlab.com/ja-jp/gitlab-duo-agent-platform/)のコード提案をご使用になった方は、ぜひ[ご意見をフィードバックイシューにお寄せください](https://gitlab.com/gitlab-org/gitlab/-/issues/405152)。\n\n\n    *監修：佐々木 直晴 [@naosasaki](https://gitlab.com/naosasaki) \u003Cbr>\n\n    （GitLab合同会社 ソリューションアーキテクト本部 シニアソリューションアーキテクト）*\n  category: ai-ml\n  tags:\n    - DevSecOps\n    - careers\n    - tutorial\n    - workflow\n    - AI/ML\n  updatedDate: '2025-01-24'\nconfig:\n  slug: learn-advanced-rust-programming-with-a-little-help-from-ai-code-suggestions\n  featured: false\n  template: BlogPost\n",{"title":5,"description":17,"ogTitle":5,"ogDescription":17,"noIndex":14,"ogImage":19,"ogUrl":35,"ogSiteName":36,"ogType":37,"canonicalUrls":35},"https://about.gitlab.com/blog/learn-advanced-rust-programming-with-a-little-help-from-ai-code-suggestions","https://about.gitlab.com","article","ja-jp/blog/learn-advanced-rust-programming-with-a-little-help-from-ai-code-suggestions",[40,23,24,25,41],"devsecops","aiml",[22,23,24,25,26],"mzDogVnvBOnpU30sVzqEL159M91QpW4jo_t1uMy1hhQ",{"data":45},{"logo":46,"freeTrial":51,"sales":56,"login":61,"items":66,"search":375,"minimal":408,"duo":425,"switchNav":434,"pricingDeployment":445},{"config":47},{"href":48,"dataGaName":49,"dataGaLocation":50},"/ja-jp/","gitlab logo","header",{"text":52,"config":53},"無料トライアルを開始",{"href":54,"dataGaName":55,"dataGaLocation":50},"https://gitlab.com/-/trial_registrations/new?glm_source=about.gitlab.com/ja-jp&glm_content=default-saas-trial/","free trial",{"text":57,"config":58},"お問い合わせ",{"href":59,"dataGaName":60,"dataGaLocation":50},"/ja-jp/sales/","sales",{"text":62,"config":63},"サインイン",{"href":64,"dataGaName":65,"dataGaLocation":50},"https://gitlab.com/users/sign_in/","sign in",[67,94,191,196,297,357],{"text":68,"config":69,"cards":71},"プラットフォーム",{"dataNavLevelOne":70},"platform",[72,78,86],{"title":68,"description":73,"link":74},"DevSecOpsに特化したインテリジェントオーケストレーションプラットフォーム",{"text":75,"config":76},"プラットフォームを探索",{"href":77,"dataGaName":70,"dataGaLocation":50},"/ja-jp/platform/",{"title":79,"description":80,"link":81},"GitLab Duo Agent Platform","ソフトウェアライフサイクル全体を支えるエージェント型AI",{"text":82,"config":83},"GitLab Duoのご紹介",{"href":84,"dataGaName":85,"dataGaLocation":50},"/ja-jp/gitlab-duo-agent-platform/","gitlab duo agent platform",{"title":87,"description":88,"link":89},"GitLabが選ばれる理由","エンタープライズがGitLabを選ぶ主な理由をご覧ください",{"text":90,"config":91},"詳細はこちら",{"href":92,"dataGaName":93,"dataGaLocation":50},"/ja-jp/why-gitlab/","why gitlab",{"text":95,"left":31,"config":96,"link":98,"lists":102,"footer":173},"製品",{"dataNavLevelOne":97},"solutions",{"text":99,"config":100},"すべてのソリューションを表示",{"href":101,"dataGaName":97,"dataGaLocation":50},"/ja-jp/solutions/",[103,128,151],{"title":104,"description":105,"link":106,"items":111},"自動化","CI/CDと自動化でデプロイを加速",{"config":107},{"icon":108,"href":109,"dataGaName":110,"dataGaLocation":50},"AutomatedCodeAlt","/ja-jp/solutions/delivery-automation/","automated software delivery",[112,116,119,124],{"text":113,"config":114},"CI/CD",{"href":115,"dataGaLocation":50,"dataGaName":113},"/ja-jp/solutions/continuous-integration/",{"text":79,"config":117},{"href":84,"dataGaLocation":50,"dataGaName":118},"gitlab duo agent platform - product menu",{"text":120,"config":121},"ソースコード管理",{"href":122,"dataGaLocation":50,"dataGaName":123},"/ja-jp/solutions/source-code-management/","Source Code Management",{"text":125,"config":126},"自動化されたソフトウェアデリバリー",{"href":109,"dataGaLocation":50,"dataGaName":127},"Automated software delivery",{"title":129,"description":130,"link":131,"items":136},"セキュリティ","セキュリティを犠牲にすることなくコード作成を高速化",{"config":132},{"href":133,"dataGaName":134,"dataGaLocation":50,"icon":135},"/ja-jp/solutions/application-security-testing/","security and compliance","ShieldCheckLight",[137,141,146],{"text":138,"config":139},"アプリケーションセキュリティテスト",{"href":133,"dataGaName":140,"dataGaLocation":50},"Application security testing",{"text":142,"config":143},"ソフトウェアサプライチェーンの安全性",{"href":144,"dataGaLocation":50,"dataGaName":145},"/ja-jp/solutions/supply-chain/","Software supply chain security",{"text":147,"config":148},"ソフトウェアコンプライアンス",{"href":149,"dataGaName":150,"dataGaLocation":50},"/ja-jp/solutions/software-compliance/","software compliance",{"title":152,"link":153,"items":158},"測定",{"config":154},{"icon":155,"href":156,"dataGaName":157,"dataGaLocation":50},"DigitalTransformation","/ja-jp/solutions/visibility-measurement/","visibility and measurement",[159,163,168],{"text":160,"config":161},"可視性と測定",{"href":156,"dataGaLocation":50,"dataGaName":162},"Visibility and Measurement",{"text":164,"config":165},"バリューストリーム管理",{"href":166,"dataGaLocation":50,"dataGaName":167},"/ja-jp/solutions/value-stream-management/","Value Stream Management",{"text":169,"config":170},"分析とインサイト",{"href":171,"dataGaLocation":50,"dataGaName":172},"/ja-jp/solutions/analytics-and-insights/","Analytics and insights",{"title":174,"items":175},"GitLabが活躍する場所",[176,181,186],{"text":177,"config":178},"大企業",{"href":179,"dataGaLocation":50,"dataGaName":180},"/ja-jp/enterprise/","enterprise",{"text":182,"config":183},"スモールビジネス",{"href":184,"dataGaLocation":50,"dataGaName":185},"/ja-jp/small-business/","small business",{"text":187,"config":188},"公共部門",{"href":189,"dataGaLocation":50,"dataGaName":190},"/ja-jp/solutions/public-sector/","public sector",{"text":192,"config":193},"価格",{"href":194,"dataGaName":195,"dataGaLocation":50,"dataNavLevelOne":195},"/ja-jp/pricing/","pricing",{"text":197,"config":198,"link":200,"lists":204,"feature":284},"リソース",{"dataNavLevelOne":199},"resources",{"text":201,"config":202},"すべてのリソースを表示",{"href":203,"dataGaName":199,"dataGaLocation":50},"/ja-jp/resources/",[205,238,256],{"title":206,"items":207},"はじめに",[208,213,218,223,228,233],{"text":209,"config":210},"インストール",{"href":211,"dataGaName":212,"dataGaLocation":50},"/ja-jp/install/","install",{"text":214,"config":215},"クイックスタートガイド",{"href":216,"dataGaName":217,"dataGaLocation":50},"/ja-jp/get-started/","quick setup checklists",{"text":219,"config":220},"学ぶ",{"href":221,"dataGaLocation":50,"dataGaName":222},"https://university.gitlab.com/","learn",{"text":224,"config":225},"製品ドキュメント",{"href":226,"dataGaName":227,"dataGaLocation":50},"https://docs.gitlab.com/ja-jp/","product documentation",{"text":229,"config":230},"ベストプラクティスビデオ",{"href":231,"dataGaName":232,"dataGaLocation":50},"/ja-jp/getting-started-videos/","best practice videos",{"text":234,"config":235},"インテグレーション",{"href":236,"dataGaName":237,"dataGaLocation":50},"/ja-jp/integrations/","integrations",{"title":239,"items":240},"検索する",[241,246,251],{"text":242,"config":243},"お客様成功事例",{"href":244,"dataGaName":245,"dataGaLocation":50},"/ja-jp/customers/","customer success stories",{"text":247,"config":248},"ブログ",{"href":249,"dataGaName":250,"dataGaLocation":50},"/ja-jp/blog/","blog",{"text":252,"config":253},"リモート",{"href":254,"dataGaName":255,"dataGaLocation":50},"https://handbook.gitlab.com/handbook/company/culture/all-remote/","remote",{"title":257,"items":258},"つなげる",[259,264,269,274,279],{"text":260,"config":261},"GitLabサービス",{"href":262,"dataGaName":263,"dataGaLocation":50},"/ja-jp/services/","services",{"text":265,"config":266},"コミュニティ",{"href":267,"dataGaName":268,"dataGaLocation":50},"/community/","community",{"text":270,"config":271},"フォーラム",{"href":272,"dataGaName":273,"dataGaLocation":50},"https://forum.gitlab.com/","forum",{"text":275,"config":276},"イベント",{"href":277,"dataGaName":278,"dataGaLocation":50},"/events/","events",{"text":280,"config":281},"パートナー",{"href":282,"dataGaName":283,"dataGaLocation":50},"/ja-jp/partners/","partners",{"background":285,"textColor":286,"text":287,"image":288,"link":292},"#2f2a6b","#fff","ソフトウェア開発の未来への洞察",{"altText":289,"config":290},"ソースプロモカード",{"src":291},"https://res.cloudinary.com/about-gitlab-com/image/upload/v1758208064/dzl0dbift9xdizyelkk4.svg",{"text":293,"config":294},"最新情報を読む",{"href":295,"dataGaName":296,"dataGaLocation":50},"/ja-jp/the-source/","the source",{"text":298,"config":299,"lists":301},"会社情報",{"dataNavLevelOne":300},"company",[302],{"items":303},[304,309,315,317,322,327,332,337,342,347,352],{"text":305,"config":306},"GitLabについて",{"href":307,"dataGaName":308,"dataGaLocation":50},"/ja-jp/company/","about",{"text":310,"config":311,"footerGa":314},"採用情報",{"href":312,"dataGaName":313,"dataGaLocation":50},"/jobs/","jobs",{"dataGaName":313},{"text":275,"config":316},{"href":277,"dataGaName":278,"dataGaLocation":50},{"text":318,"config":319},"経営陣",{"href":320,"dataGaName":321,"dataGaLocation":50},"/company/team/e-group/","leadership",{"text":323,"config":324},"チーム",{"href":325,"dataGaName":326,"dataGaLocation":50},"/company/team/","team",{"text":328,"config":329},"ハンドブック",{"href":330,"dataGaName":331,"dataGaLocation":50},"https://handbook.gitlab.com/","handbook",{"text":333,"config":334},"投資家向け情報",{"href":335,"dataGaName":336,"dataGaLocation":50},"https://ir.gitlab.com/","investor relations",{"text":338,"config":339},"トラストセンター",{"href":340,"dataGaName":341,"dataGaLocation":50},"/ja-jp/security/","trust center",{"text":343,"config":344},"AI Transparency Center",{"href":345,"dataGaName":346,"dataGaLocation":50},"/ja-jp/ai-transparency-center/","ai transparency center",{"text":348,"config":349},"ニュースレター",{"href":350,"dataGaName":351,"dataGaLocation":50},"/company/contact/#contact-forms","newsletter",{"text":353,"config":354},"プレス",{"href":355,"dataGaName":356,"dataGaLocation":50},"/press/","press",{"text":57,"config":358,"lists":359},{"dataNavLevelOne":300},[360],{"items":361},[362,365,370],{"text":57,"config":363},{"href":59,"dataGaName":364,"dataGaLocation":50},"talk to sales",{"text":366,"config":367},"サポートを受ける",{"href":368,"dataGaName":369,"dataGaLocation":50},"https://support.gitlab.com","support portal",{"text":371,"config":372},"カスタマーポータル",{"href":373,"dataGaName":374,"dataGaLocation":50},"https://customers.gitlab.com/customers/sign_in/","customer portal",{"close":376,"login":377,"suggestions":384},"閉じる",{"text":378,"link":379},"リポジトリとプロジェクトを検索するには、次にログインします",{"text":380,"config":381},"GitLab.com",{"href":64,"dataGaName":382,"dataGaLocation":383},"search login","search",{"text":385,"default":386},"提案",[387,389,394,396,400,404],{"text":79,"config":388},{"href":84,"dataGaName":79,"dataGaLocation":383},{"text":390,"config":391},"コード提案（AI）",{"href":392,"dataGaName":393,"dataGaLocation":383},"/ja-jp/solutions/code-suggestions/","Code Suggestions (AI)",{"text":113,"config":395},{"href":115,"dataGaName":113,"dataGaLocation":383},{"text":397,"config":398},"GitLab on AWS",{"href":399,"dataGaName":397,"dataGaLocation":383},"/ja-jp/partners/technology-partners/aws/",{"text":401,"config":402},"GitLab on Google Cloud",{"href":403,"dataGaName":401,"dataGaLocation":383},"/ja-jp/partners/technology-partners/google-cloud-platform/",{"text":405,"config":406},"GitLabを選ぶ理由",{"href":92,"dataGaName":407,"dataGaLocation":383},"Why GitLab?",{"freeTrial":409,"mobileIcon":413,"desktopIcon":418,"secondaryButton":421},{"text":52,"config":410},{"href":411,"dataGaName":55,"dataGaLocation":412},"https://gitlab.com/-/trials/new/","nav",{"altText":414,"config":415},"GitLabアイコン",{"src":416,"dataGaName":417,"dataGaLocation":412},"https://res.cloudinary.com/about-gitlab-com/image/upload/v1758203874/jypbw1jx72aexsoohd7x.svg","gitlab icon",{"altText":414,"config":419},{"src":420,"dataGaName":417,"dataGaLocation":412},"https://res.cloudinary.com/about-gitlab-com/image/upload/v1758203875/gs4c8p8opsgvflgkswz9.svg",{"text":206,"config":422},{"href":423,"dataGaName":424,"dataGaLocation":412},"https://gitlab.com/-/trial_registrations/new?glm_source=about.gitlab.com/ja-jp/get-started/","get started",{"freeTrial":426,"mobileIcon":430,"desktopIcon":432},{"text":427,"config":428},"GitLab Duoの詳細について",{"href":84,"dataGaName":429,"dataGaLocation":412},"gitlab duo",{"altText":414,"config":431},{"src":416,"dataGaName":417,"dataGaLocation":412},{"altText":414,"config":433},{"src":420,"dataGaName":417,"dataGaLocation":412},{"button":435,"mobileIcon":440,"desktopIcon":442},{"text":436,"config":437},"/switch",{"href":438,"dataGaName":439,"dataGaLocation":412},"#contact","switch",{"altText":414,"config":441},{"src":416,"dataGaName":417,"dataGaLocation":412},{"altText":414,"config":443},{"src":444,"dataGaName":417,"dataGaLocation":412},"https://res.cloudinary.com/about-gitlab-com/image/upload/v1773335277/ohhpiuoxoldryzrnhfrh.png",{"freeTrial":446,"mobileIcon":451,"desktopIcon":453},{"text":447,"config":448},"料金ページに戻る",{"href":194,"dataGaName":449,"dataGaLocation":412,"icon":450},"back to pricing","GoBack",{"altText":414,"config":452},{"src":416,"dataGaName":417,"dataGaLocation":412},{"altText":414,"config":454},{"src":420,"dataGaName":417,"dataGaLocation":412},{"title":456,"button":457,"config":462},"エージェント型AIがソフトウェア配信をどのように変革するかをご覧ください",{"text":458,"config":459},"GitLab Transcendを今すぐ視聴",{"href":460,"dataGaName":461,"dataGaLocation":50},"/ja-jp/events/transcend/virtual/","transcend event",{"layout":463,"icon":464,"disabled":31},"release","AiStar",{"data":466},{"text":467,"source":468,"edit":474,"contribute":479,"config":484,"items":489,"minimal":690},"GitはSoftware Freedom Conservancyの商標です。当社は「GitLab」をライセンスに基づいて使用しています",{"text":469,"config":470},"ページのソースを表示",{"href":471,"dataGaName":472,"dataGaLocation":473},"https://gitlab.com/gitlab-com/marketing/digital-experience/about-gitlab-com/","page source","footer",{"text":475,"config":476},"このページを編集",{"href":477,"dataGaName":478,"dataGaLocation":473},"https://gitlab.com/gitlab-com/marketing/digital-experience/about-gitlab-com/-/blob/main/content/","web ide",{"text":480,"config":481},"ご協力をお願いします",{"href":482,"dataGaName":483,"dataGaLocation":473},"https://gitlab.com/gitlab-com/marketing/digital-experience/about-gitlab-com/-/blob/main/CONTRIBUTING.md/","please contribute",{"twitter":485,"facebook":486,"youtube":487,"linkedin":488},"https://twitter.com/gitlab","https://www.facebook.com/gitlab","https://www.youtube.com/channel/UCnMGQ8QHMAnVIsI3xJrihhg","https://www.linkedin.com/company/gitlab-com",[490,535,586,629,656],{"title":192,"links":491,"subMenu":506},[492,496,501],{"text":493,"config":494},"プランの表示",{"href":194,"dataGaName":495,"dataGaLocation":473},"view plans",{"text":497,"config":498},"Premiumを選ぶ理由",{"href":499,"dataGaName":500,"dataGaLocation":473},"/ja-jp/pricing/premium/","why premium",{"text":502,"config":503},"Ultimateを選ぶ理由",{"href":504,"dataGaName":505,"dataGaLocation":473},"/ja-jp/pricing/ultimate/","why ultimate",[507],{"title":57,"links":508},[509,511,513,515,520,525,530],{"text":57,"config":510},{"href":59,"dataGaName":60,"dataGaLocation":473},{"text":366,"config":512},{"href":368,"dataGaName":369,"dataGaLocation":473},{"text":371,"config":514},{"href":373,"dataGaName":374,"dataGaLocation":473},{"text":516,"config":517},"ステータス",{"href":518,"dataGaName":519,"dataGaLocation":473},"https://status.gitlab.com/","status",{"text":521,"config":522},"利用規約",{"href":523,"dataGaName":524,"dataGaLocation":473},"/terms/","terms of use",{"text":526,"config":527},"プライバシーに関する声明",{"href":528,"dataGaName":529,"dataGaLocation":473},"/ja-jp/privacy/","privacy statement",{"text":531,"config":532},"Cookie 優先設定",{"dataGaName":533,"dataGaLocation":473,"id":534,"isOneTrustButton":31},"cookie preferences","ot-sdk-btn",{"title":95,"links":536,"subMenu":545},[537,541],{"text":538,"config":539},"DevSecOpsプラットフォーム",{"href":77,"dataGaName":540,"dataGaLocation":473},"devsecops platform",{"text":542,"config":543},"AI支援開発",{"href":84,"dataGaName":544,"dataGaLocation":473},"ai-assisted development",[546],{"title":547,"links":548},"トピック",[549,553,558,563,568,571,576,581],{"text":113,"config":550},{"href":551,"dataGaName":552,"dataGaLocation":473},"/ja-jp/topics/ci-cd/","cicd",{"text":554,"config":555},"GitOps",{"href":556,"dataGaName":557,"dataGaLocation":473},"/ja-jp/topics/gitops/","gitops",{"text":559,"config":560},"DevOps",{"href":561,"dataGaName":562,"dataGaLocation":473},"/ja-jp/topics/devops/","devops",{"text":564,"config":565},"バージョン管理",{"href":566,"dataGaName":567,"dataGaLocation":473},"/ja-jp/topics/version-control/","version control",{"text":22,"config":569},{"href":570,"dataGaName":40,"dataGaLocation":473},"/ja-jp/topics/devsecops/",{"text":572,"config":573},"クラウドネイティブ",{"href":574,"dataGaName":575,"dataGaLocation":473},"/ja-jp/topics/cloud-native/","cloud native",{"text":577,"config":578},"コーディングのためのAI",{"href":579,"dataGaName":580,"dataGaLocation":473},"/ja-jp/topics/devops/ai-for-coding/","ai for coding",{"text":582,"config":583},"エージェント型AI",{"href":584,"dataGaName":585,"dataGaLocation":473},"/ja-jp/topics/agentic-ai/","agentic ai",{"title":587,"links":588},"ソリューション",[589,592,594,599,603,606,609,612,614,616,619,624],{"text":138,"config":590},{"href":133,"dataGaName":591,"dataGaLocation":473},"Application Security Testing",{"text":125,"config":593},{"href":109,"dataGaName":110,"dataGaLocation":473},{"text":595,"config":596},"アジャイル開発",{"href":597,"dataGaName":598,"dataGaLocation":473},"/ja-jp/solutions/agile-delivery/","agile delivery",{"text":600,"config":601},"SCM",{"href":122,"dataGaName":602,"dataGaLocation":473},"source code management",{"text":113,"config":604},{"href":115,"dataGaName":605,"dataGaLocation":473},"continuous integration & delivery",{"text":164,"config":607},{"href":166,"dataGaName":608,"dataGaLocation":473},"value stream management",{"text":554,"config":610},{"href":611,"dataGaName":557,"dataGaLocation":473},"/ja-jp/solutions/gitops/",{"text":177,"config":613},{"href":179,"dataGaName":180,"dataGaLocation":473},{"text":182,"config":615},{"href":184,"dataGaName":185,"dataGaLocation":473},{"text":617,"config":618},"公共機関",{"href":189,"dataGaName":190,"dataGaLocation":473},{"text":620,"config":621},"教育",{"href":622,"dataGaName":623,"dataGaLocation":473},"/ja-jp/solutions/education/","education",{"text":625,"config":626},"金融サービス",{"href":627,"dataGaName":628,"dataGaLocation":473},"/ja-jp/solutions/finance/","financial services",{"title":197,"links":630},[631,633,635,637,640,642,644,646,648,650,652,654],{"text":209,"config":632},{"href":211,"dataGaName":212,"dataGaLocation":473},{"text":214,"config":634},{"href":216,"dataGaName":217,"dataGaLocation":473},{"text":219,"config":636},{"href":221,"dataGaName":222,"dataGaLocation":473},{"text":224,"config":638},{"href":226,"dataGaName":639,"dataGaLocation":473},"docs",{"text":247,"config":641},{"href":249,"dataGaName":250,"dataGaLocation":473},{"text":242,"config":643},{"href":244,"dataGaName":245,"dataGaLocation":473},{"text":252,"config":645},{"href":254,"dataGaName":255,"dataGaLocation":473},{"text":260,"config":647},{"href":262,"dataGaName":263,"dataGaLocation":473},{"text":265,"config":649},{"href":267,"dataGaName":268,"dataGaLocation":473},{"text":270,"config":651},{"href":272,"dataGaName":273,"dataGaLocation":473},{"text":275,"config":653},{"href":277,"dataGaName":278,"dataGaLocation":473},{"text":280,"config":655},{"href":282,"dataGaName":283,"dataGaLocation":473},{"title":298,"links":657},[658,660,662,664,666,668,670,674,679,681,683,685],{"text":305,"config":659},{"href":307,"dataGaName":300,"dataGaLocation":473},{"text":310,"config":661},{"href":312,"dataGaName":313,"dataGaLocation":473},{"text":318,"config":663},{"href":320,"dataGaName":321,"dataGaLocation":473},{"text":323,"config":665},{"href":325,"dataGaName":326,"dataGaLocation":473},{"text":328,"config":667},{"href":330,"dataGaName":331,"dataGaLocation":473},{"text":333,"config":669},{"href":335,"dataGaName":336,"dataGaLocation":473},{"text":671,"config":672},"Sustainability",{"href":673,"dataGaName":671,"dataGaLocation":473},"/sustainability/",{"text":675,"config":676},"ダイバーシティ、インクルージョン、ビロンギング（DIB）",{"href":677,"dataGaName":678,"dataGaLocation":473},"/ja-jp/diversity-inclusion-belonging/","Diversity, inclusion and belonging",{"text":338,"config":680},{"href":340,"dataGaName":341,"dataGaLocation":473},{"text":348,"config":682},{"href":350,"dataGaName":351,"dataGaLocation":473},{"text":353,"config":684},{"href":355,"dataGaName":356,"dataGaLocation":473},{"text":686,"config":687},"現代奴隷制の透明性に関する声明",{"href":688,"dataGaName":689,"dataGaLocation":473},"https://handbook.gitlab.com/handbook/legal/modern-slavery-act-transparency-statement/","modern slavery transparency statement",{"items":691},[692,694,697],{"text":521,"config":693},{"href":523,"dataGaName":524,"dataGaLocation":473},{"text":695,"config":696},"Cookieの設定",{"dataGaName":533,"dataGaLocation":473,"id":534,"isOneTrustButton":31},{"text":526,"config":698},{"href":528,"dataGaName":529,"dataGaLocation":473},[700],{"id":701,"title":9,"body":29,"config":702,"content":704,"description":29,"extension":28,"meta":708,"navigation":31,"path":709,"seo":710,"stem":711,"__hash__":712},"blogAuthors/en-us/blog/authors/michael-friedrich.yml",{"template":703},"BlogAuthor",{"name":9,"config":705},{"headshot":706,"ctfId":707},"https://res.cloudinary.com/about-gitlab-com/image/upload/v1749659879/Blog/Author%20Headshots/dnsmichi-headshot.jpg","dnsmichi",{},"/en-us/blog/authors/michael-friedrich",{},"en-us/blog/authors/michael-friedrich","lJ-nfRIhdG49Arfrxdn1Vv4UppwD51BB13S3HwIswt4",[714,727,742],{"content":715,"config":725},{"title":716,"description":717,"authors":718,"heroImage":720,"date":721,"body":722,"category":11,"tags":723},"GitLabとAnthropic：エンタープライズ開発のためのガバナンスAI","GitLabがAnthropicとのClaude統合を強化。ガバナンス・コンプライアンス・監査証跡を組み込んだプラットフォームで、最新Claudeモデルへのアクセス、そしてGoogle CloudやAWSを通じた柔軟なクラウド展開をエンタープライズに提供します。",[719],"Stuart Moncada","https://res.cloudinary.com/about-gitlab-com/image/upload/v1776457632/llddiylsgwuze0u1rjks.png","2026-04-28","エンタープライズや公共セクターのリーダーにとって、この緊張関係は見慣れたものです。ソフトウェアチームはAIを活用してスピードを上げる必要がある一方、セキュリティ、コンプライアンス、規制上の要求はますます厳しくなっています。GitLabはAnthropicとのClaude統合を強化し、新たにリリースされたClaudeモデルへのアクセスを、ガバナンス・コンプライアンス・監査証跡がすでに組み込まれたGitLabのインテリジェントオーケストレーションプラットフォーム上で提供します。\n\nClaudeはGitLab Duo Agent Platformにおけるデフォルトモデルとして、コード生成・レビューからエージェント型チャット、脆弱性の解消まで、幅広いユースケースで機能を支えています。GitLab Duoをご利用いただいている方は、Duoエージェントがソフトウェア開発ライフサイクル（SDLC）全体にわたってワークフローを自動化する様子をすでにご体験いただいています。\n\nこの統合強化により、ClaudeのコアAI機能をGitLabにより迅速に統合し、エンタープライズが展開できる幅を広げるとともに、GitLabがソフトウェア開発・エンジニアリングプラットフォームとして本質的に異なる点を改めて示します。それは、すべてのAIインタラクションに組み込まれたガバナンス・コンプライアンス・監査証跡です。\n\n> 「GitLab Duoにより、チームの計画・構築・リリースのスピードが格段に上がりました。AnthropicのClaudeとGitLabのプラットフォームを組み合わせることで、働き方やガバナンスの仕組みを変えることなく、より高度なAIを活用できています。」 \\\n> – Mans Booijink氏、オペレーションマネージャー、Cube社\n\n## 真の差別化要因：ガバナンスAI\n\nGitLabでは、ガバナンスコントロールと監査機能がSDLCに組み込まれています。GitLab Duo Agent PlatformでClaudeがコード変更を提案する場合、その提案は他のあらゆる変更と同様に、マージリクエストのプロセス、承認ルール、セキュリティスキャン、そして監査証跡を経由します。AIはコントロールを迂回することはできません。AIはコントロールの枠組みの中で動作します。\n\nGitLabがエージェント型ソフトウェア開発、すなわちAIが明確に定義されたタスクを自律的に処理する開発手法へと深く踏み込む中、ガバナンスレイヤーの重要性はさらに増しています。マージリクエストのオープン、脆弱性の解消支援、サービスのリファクタリングを担えるAIエージェントには、人間の開発者と同様に、監査可能性・帰属明確性・ポリシー適用が求められます。この要件はGitLabが当初から下したアーキテクチャ上の決断であり、AIエージェントが担う責務の範囲が広がるにつれて、その重要性はさらに高まっています。\n\n## エンタープライズ向けデプロイの柔軟性\n\nまた、この統合強化により、組織がGitLabを通じて最新のClaudeモデルにアクセスする方法も広がります。GitLab内でのClaudeはGoogle CloudのVertex AIおよびAWS Bedrockを通じて利用可能であり、企業はすでに導入済みのハイパースケーラーとの契約やクラウドガバナンスフレームワークを通じてAIワークロードをルーティングできます。別途ベンダー契約は不要です。データレジデンシーに関する新たな懸念もありません。既存のGCPまたはAWSの関係がそのままオンランプとなります。\n\nGitLabは[Claude Marketplace](https://claude.com/platform/marketplace)にも参加しました。これにより、お客様はGitLabクレジットを購入してAnthropicへの既存の支出コミットメントに充てることができ、AIコストを一元管理しながら、Anthropicへの投資と並行してGitLabを手軽に見つけ、調達できるようになります。\n\n## エージェント型の未来へ\n\n計画・コーディング・テスト・セキュリティ確保・デプロイにわたり、AIが定義されたタスクを自律的にこなすエージェント型ソフトウェア開発というGitLabのビジョンを実現するには、高度な推論能力・信頼性・安全性を備えたモデルが必要です。そして、それらの自律的アクションが完全にガバナンスされるプラットフォームも不可欠です。\n\nエージェント型ワークフローには、高度な推論能力・信頼性・安全性を備えたモデルが求められます。これらの基準は、GitLabがAIモデルパートナーを選定・統合する際の指針となっています。また、GitLabのガバナンスフレームワークにより、AIエージェントがより高度な開発作業を担うようになっても、エンタープライズはエージェントの行動・実行タイミング・変更の追跡方法について完全な可視性とコントロールを維持できます。\n\n## GitLabをご利用のお客様への意味\n\nすでにGitLab Duo Agent Platformをご利用の方は、ソフトウェア開発ライフサイクル全体にわたってClaudeモデルへのアクセスとより深いAIアシスタンスを、これまで通りのガバナンスフレームワークの中でご活用いただけます。\n\nAI活用型ソフトウェア開発プラットフォームを評価中の方は、高度なAI機能とエンタープライズコントロールのどちらかを選ぶ必要はありません。この戦略的連携は、その両方を実現するために構築されています。\n\n> GitLab Duo Agent Platformについてさらに詳しく知りたい方は、[デモのご依頼または無料トライアルのお申し込みはこちら](https://about.gitlab.com/ja-jp/gitlab-duo-agent-platform/)からどうぞ。",[26,724,283],"product",{"featured":31,"template":15,"slug":726},"gitlab-and-anthropic-governed-ai-for-enterprise-development",{"content":728,"config":740},{"title":729,"description":730,"authors":731,"body":734,"heroImage":735,"date":736,"category":11,"tags":737},"GitLabとVertex AI on Google Cloud：エージェント型ソフトウェア開発の加速","Google CloudのVertex AIとGitLab Duo Agent Platformを組み合わせることで、ファウンデーションモデル、エンタープライズ制御、Model Gardenの豊富なモデルを活用したエージェント型開発が実現します。\n",[732,733],"Regnard Raquedan","Rajesh Agadi","GitLab Duo Agent Platformは、組織がソフトウェアをビルド、セキュア化、そして提供する方法を再定義しつつあります。2026年1月の一般提供開始以来、このプラットフォームはソフトウェア開発ライフサイクルのあらゆる段階にエージェント型AIをもたらしています。Duo Agent Platformは、ソフトウェアチームと専門エージェントが連携して計画、コーディング、レビュー、セキュリティ脆弱性の修正を行う、インテリジェントなオーケストレーションレイヤーです。\n\nこのパートナーシップを通じて、[GitLab Duo Agent Platform](https://about.gitlab.com/gitlab-duo-agent-platform/)はVertex AI on Google Cloudとの統合によりソフトウェア開発のオーケストレーションとライフサイクルコンテキストを自動化します。Vertex AIはエージェント呼び出しのモデル層を担い、ソフトウェアチームはすでに定義済みのGoogle Cloudポリシーに従って推論を実行しながら、イシュー、マージリクエスト、パイプライン、セキュリティワークフローの作業を継続できます。\n\nGoogle CloudのVertex AIモデルの進化により、Google CloudユーザーはGitLab Duo Agent Platformをさらに活用できるようになっています。GitLabではAIを活用したDevSecOpsコントロールプレーンを、Vertex AIの急速に進化するAIインフラ基盤と、Duo Agent Platformの柔軟なデプロイ・統合オプションが支えています。この組み合わせにより、エンタープライズスケールで動作する、より高度でガバナンスの効いたエージェント型ワークフローが実現します。\n\n![Google CloudのVertex AIと連携してエージェント型ソフトウェア開発とガバナンスを備えたAIワークフローを実現するGitLab Duo Agent Platformの概念図](https://res.cloudinary.com/about-gitlab-com/image/upload/v1776165990/b7jlux9kydafncwy8spc.png)\n\n## 開発ライフサイクル全体にわたるエージェント\n\n多くのAIツールは、コードをより速く生成するという単一のタスクに集中しています。GitLab Duo Agent Platformはそれをさらに超え、計画からセキュリティレビュー、リリースまで、ソフトウェア開発ライフサイクル（SDLC）全体にわたってAIエージェントをオーケストレーションします。これは、多数のプロジェクトとリリースを抱える多くのチームを対象としています。このスケールにおいて、AIコーディングアシスタントは継続的なイノベーションに必要ではありますが、それだけでは十分ではありません。\n\n単一目的のコーディングアシスタントがプロジェクトの全体像を把握することはほとんどありません。バックログの状況、オープン中のマージリクエスト、失敗したジョブ、セキュリティの検出結果はGitLabに蓄積されていますが、コーディングアシスタント内の別のチャットウィンドウはSDLCのその全体像を引き継ぐことができません。このギャップは、手動によるハンドオフ、コンテキストを持たないAIへの重複した説明、そして一つのシステムとして設計されたわけではないツール間のデータフローをマッピングしようとするガバナンスチームという形で表れます。\n\nGitLab Duo Agent Platformは、エンジニアが日々使用するオブジェクト上でエージェントとフローを動作させることで、このギャップを埋めます。Google Cloudを推論の基盤として選択している場合、Vertex AIはエージェントが呼び出すモデルとサービスを提供し、GitLabのAI Gatewayがアクセスを仲介することで管理者はどこに何が接続されているかを明確に把握できます。例えば、GitLab Duo Planner Agentはバックログを分析し、エピックを構造化タスクに分解し、優先順位付けフレームワークを適用することで、次に何を構築するかをチームが判断するのを支援します。Security Analyst Agentは脆弱性をトリアージし、平易な言葉でリスクを説明し、優先順位に従って修正を推奨します。ビルトインのフローはこれらのエージェントをエンドツーエンドのプロセスへと連結し、開発者がすべてのハンドオフを手動で管理する必要をなくします。\n\nGitLab Duo Agent PlatformのAgentic Chatは、開発者にとって統合された体験を提供します。自然言語でクエリを投げかけることで、プロジェクトのイシュー、マージリクエスト、パイプライン、セキュリティの検出結果、コードベースといった全体像を踏まえた多段階の推論に基づくコンテキスト対応の回答が得られます。GitLabがSDLCの統一データモデルを持つ記録システムとして機能しているため、GitLab Duoエージェントは、スタンドアロンのツール固有AIアシスタントでは到達できないライフサイクルコンテキストをもとに動作します。\n\n### Vertex AIによる能力の拡張\n\nGitLab Duo Agent Platformはモデルフレキシブルな設計となっており、タスクごとに最適なパフォーマンスを発揮するモデルへと異なる機能をルーティングします。このアーキテクチャの選択はGoogle Cloud上で効果を発揮します。Vertex AIはファウンデーションモデルと関連サービスのマネージド環境として機能し、幅広いモデルエコシステムとマネージドインフラを提供することでプラットフォームの能力をさらに引き出します。\n\nVertex AIを通じて利用できる最新世代のAIモデルは、以前のバージョンと比較して推論、ツール使用、長文コンテキスト理解において大幅な改善をもたらします。これらはまさに、GitLabのエージェントが大規模で複雑なコードベースを持つ多くのプロジェクトとチームにわたって依拠するプロパティです。基盤モデルにおけるより長いコンテキストウィンドウと豊富なツール連携により、エージェントが一度のパスで達成できることが広がり、深いバックログ分析やモノレポのセキュリティレビューといったワークロードに特に重要な意味を持ちます。\n\n幅広いファウンデーションモデルへのアクセスを提供する[Vertex AI Model Garden](https://cloud.google.com/model-garden)により、ベンダーロックインではなく、パフォーマンス、コスト、規制要件に基づいてこれらの選択を行う自由がお客様に与えられます。\n\nさらに、GitLabのお客様はDuo Agent PlatformにBYOM（Bring Your Own Model）を利用することで、承認済みのプロバイダーとゲートウェイをセキュリティモデルが期待する場所に配置できます。[18.9リリースにおけるセルフホスト型Duo Agent PlatformとBYOMの解説](https://about.gitlab.com/blog/agentic-ai-enterprise-control-self-hosted-duo-agent-platform-and-byom/)では、その仕組みが詳しく説明されています。このデプロイオプションにより、お客様はソフトウェア開発プロセスに合わせてカスタマイズできるより広いモデルの選択肢へのアクセスを得られます。適切なワークフローに、適切なモデルを、適切なガードレールとともに。\n\nGitLabがVertex AIを基盤として選択したのは、エンタープライズグレードの信頼性と比類ないモデルの幅広さへのニーズによるものです。Vertex AIとModel Gardenは、LLMホスティングの重労働を完全に抽象化し、迅速なバージョン提供、堅牢なセキュリティ、厳格なガバナンスをシームレスに統合に組み込んでいます。Geminiモデルの提供にとどまらず、Vertex AIはサードパーティおよびオープンソースモデルの豊富なカタログへのグローバルかつ低レイテンシのアクセスを提供します。\n\nGoogleCloudの業界をリードするデータプライバシーとモデル保護へのアプローチと組み合わせることで、Vertex AIはGitLabの次世代デベロッパーエクスペリエンスを支える明確な選択肢として浮上しました。\n\nVertex AI Model GardenをバックエンドへIntegrateすることで、GitLabはDevSecOpsプラットフォームを強化しながら、その複雑さをユーザーに負わせることがありません。開発チームは基盤となるLLMの評価や管理に煩わされることなく、アプリケーション構築のための効率的なAI支援ワークフローを体験できます。\n\nGitLabはクラウドオーケストレーションを完全に抽象化し、開発者が優れたコードの記述に集中できる環境を提供する一方、Vertex AIはその支援となる機能を動かしています。\n\n## Google Cloudをご利用のお客様への意義\n\nGitLab Duo Agent Platformはすでに、一つのガバナンスの効いた記録システムの中でソフトウェアライフサイクル全体にわたって動作するAIエージェントを提供しています。Google Cloud上では、Vertex AIがモデルとインフラ層を継続的に進化させながら、迅速なイノベーションを可能にします。\n\nGoogle Cloudをご利用のお客様にとって、この統合は厳格なエンタープライズガバナンスを維持しながらソフトウェア提供を効率化することを意味します。プラットフォームエンジニアリンググループにとっては、クライアントサイドのツールを数十種類カタログ化するのではなく、GitLab内の提案、分析、修正を担うVertex AI連携モデルを標準化することを意味します。セキュリティプログラムは、エージェントが開発者がすでに検出結果をトリアージしている場所と同じ場所で修正を提案・検証することで、頭の切り替えを減らし、管理されていないチャネルへ流出していた作業を削減できます。\n\nクラウドの費用対効果とポリシーの観点から、GitLab内からVertexへのエージェント推論をまとめることで、Google Cloud上ですでに運用している契約や統制に近い形で使用量を管理でき、調達プロセスを迂回する重複支出やシャドーパスを回避するのに役立ちます。\n\nVertex AIはGitLab Duo Agent Platformの基盤インフラプロバイダーであるため、組織はAIツールチェーンの断片化を管理するオーバーヘッドとリスクなしに、デベロッパーの生産性を大幅に向上させることができます。チームは単一のセキュアな記録システムの中で足並みを揃え、アプリケーションをより速くビルドし、確信を持ってリリースできるようになります。\n\nGitLabとGoogle Cloudのコラボレーションは2018年から続いています。今日、これはAIの実験から、Google Cloud上での完全にガバナンスが効いたエージェント型ソフトウェア開発へと移行する組織にとって、最も包括的なパスの一つとなっています。GitLabがエージェントオーケストレーションとデベロッパーコンテキストを拡充し、Vertex AIがモデル能力とエージェントインフラの限界を押し広げていく中で、共同顧客にとっての価値は今後も成長し続けるでしょう。\n\n> [GitLab Duo Agent Platformの無料トライアルを開始](https://about.gitlab.com/free-trial/)して、Google Cloud上でのGitLabとVertex AIのパワーをぜひご体験ください。","https://res.cloudinary.com/about-gitlab-com/image/upload/v1749663121/Blog/Hero%20Images/LogoLockupPlusLight.png","2026-04-14",[26,283,738,739,724],"google","news",{"featured":31,"template":15,"slug":741},"gitlab-and-vertex-ai-on-google-cloud",{"content":743,"config":753},{"heroImage":744,"body":745,"authors":746,"updatedDate":748,"date":749,"title":750,"tags":751,"description":752,"category":11},"https://res.cloudinary.com/about-gitlab-com/image/upload/v1772643639/sapu29gmlgtwvhggmj6k.png","ソフトウェア開発の管理では、複数のツールを同時に扱うことが求められます。Jiraで課題を追跡し、IDEでコードを記述し、GitLabでコラボレーションするといった具合です。こうしたプラットフォーム間のコンテキストの切り替えは集中力を妨げ、デリバリーを遅らせます。\n\nGitLab Duo Agent Platformの[MCP](https://about.gitlab.com/topics/ai/model-context-protocol/)サポートにより、Jiraをはじめ、MCPに対応するあらゆるツールを、AIを活用した開発環境に直接接続できるようになりました。課題の照会、チケットの更新、ワークフローの同期など、すべてを自然言語で、IDEを離れることなく実行できます。\n\n## この記事で学べること\n\nこのチュートリアルでは、以下の内容を解説します。\n\n* **Jira/Atlassian OAuthアプリケーションのセットアップ** — セキュアな認証を設定します\n* **GitLab Duo Agent Platformの設定** — GitLab Duo Agent PlatformをMCPクライアントとして設定します\n* **3つの実践的なユースケース** — 実際のワークフローのデモをご覧ください\n\n## 前提条件\n\n開始する前に、以下の要件を満たしていることをご確認ください。\n\n| 要件 | 詳細 |\n| ---- | ----- |\n| **GitLabインスタンス** | Duo Agent Platformが有効なGitLab 18.8以降 |\n| **Jiraアカウント** | OAuthアプリケーションを作成できる管理者権限を持つJira Cloudインスタンス |\n| **IDE** | GitLab Workflow拡張機能がインストールされたVisual Studio Code |\n| **MCPサポート** | GitLabでMCPサポートが有効化済み |\n\n\n## アーキテクチャの概要\n\nGitLab Duo Agent Platformは**MCPクライアント**として機能し、Atlassian MCPサーバーに接続してJiraのプロジェクト管理データにアクセスします。Atlassian MCPサーバーは認証を処理し、自然言語のリクエストをAPI呼び出しに変換して、構造化されたデータをGitLab Duo Agent Platformに返します。このプロセス全体を通じて、セキュリティと監査管理が維持されます。\n\n## パート1：Jira OAuthアプリケーションの設定\n\nGitLab Duo Agent PlatformをJiraインスタンスに安全に接続するには、Atlassian Developer ConsoleでOAuth 2.0アプリケーションを作成する必要があります。これにより、GitLabのMCPサーバーにJiraデータへの認可されたアクセス権が付与されます。\n\n### セットアップ手順\n\n手動で設定する場合は、以下の手順に従ってください。\n\n1. **Atlassian Developer Consoleへのアクセス**\n\n   * [developer.atlassian.com/console/myapps](https://developer.atlassian.com/console/myapps)にアクセスします。\n\n   * Atlassianアカウントでサインインします。\n\n2. **新しいOAuth 2.0アプリの作成**\n\n   * 「**Create**」→「**OAuth 2.0 integration**」をクリックします。\n\n   * アプリ名を入力します（例：「gitlab-dap-mcp」）。\n\n   * 利用規約に同意し、「**Create**」をクリックします。\n\n3. **権限の設定**\n\n   * 左サイドバーの「**Permissions**」に移動します。\n\n   * 「**Jira API**」を追加し、以下のスコープを設定します。\n\n     * `read:jira-work` — 課題、プロジェクト、ボードの読み取り\n\n     * `write:jira-work` — 課題の作成と更新\n\n     * `read:jira-user` — ユーザー情報の読み取り\n\n4. **認可の設定**\n\n   * 左サイドバーの「**Authorization**」に移動します。\n\n   * お使いの環境のコールバックURLを追加します（`https://gitlab.com/oauth/callback`）。\n\n   * 変更を保存します。\n\n5. **認証情報の取得**\n\n   * 「**Settings**」に移動します。\n\n   * 「**Client ID**」と「**Client Secret**」をコピーします。\n\n   * これらの認証情報はMCP設定に必要なため、安全な場所に保管してください。\n\n\n### インタラクティブウォークスルー：Jira OAuthのセットアップ\n\n以下の画像をクリックして開始してください。\n\n\n[![Jira OAuthセットアップツアー](https://res.cloudinary.com/about-gitlab-com/image/upload/v1772644850/wnzfoq43nkkfmgdqldmr.png)](https://gitlab.navattic.com/jira-oauth-setup)\n\n\n## パート2：GitLab Duo Agent PlatformのMCPクライアントの設定\n\nOAuth認証情報の準備ができたら、GitLab Duo Agent PlatformをAtlassian MCPサーバーに接続するための設定を行います。\n\n### MCP設定ファイルの作成\n\nGitLabプロジェクトの `.gitlab/duo/mcp.json` にMCP設定ファイルを作成します。\n\n\n```json\n{\n  \"mcpServers\": {\n    \"atlassian\": {\n      \"type\": \"http\",\n      \"url\": \"https://mcp.atlassian.com/v1/mcp\",\n      \"auth\": {\n        \"type\": \"oauth2\",\n        \"clientId\": \"YOUR_CLIENT_ID\",\n        \"clientSecret\": \"YOUR_CLIENT_SECRET\",\n        \"authorizationUrl\": \"https://auth.atlassian.com/oauth/authorize\",\n        \"tokenUrl\": \"https://auth.atlassian.com/oauth/token\"\n      },\n      \"approvedTools\": true\n    }\n  }\n}\n```\n\n`YOUR_CLIENT_ID` と `YOUR_CLIENT_SECRET` は、パート1で生成した認証情報に置き換えてください。\n\n### GitLabでMCPを有効化\n\n1. 「**グループ設定**」→「**GitLab Duo**」→「**Configuration**」に移動します。\n2. 「Allow external MCP tools」にチェックが入っていることを確認します。\n\n### 接続の確認\n\nVS Codeでプロジェクトを開いてGitLab Duo Agent Platformのチャットで次のように入力してください。\n\n```text\nWhat MCP tools do you have access to?\n```\n\n次に、以下のように入力します。\n\n```text\nTest the MCP JIRA configuration in this project\n```\n\nこの時点で、IDEからAtlassian MCPウェブサイトにリダイレクトされ、アクセスの承認を求められます。\n\n![Atlassian MCPウェブサイトへのリダイレクト](https://res.cloudinary.com/about-gitlab-com/image/upload/v1772643461/z5acqjgguh0damnnde9g.png \"MCPのAtlassianウェブサイトへのリダイレクト\")\n\n\u003Cbr>\u003C/br>\n\n![アクセスの承認](https://res.cloudinary.com/about-gitlab-com/image/upload/v1772643461/rwowamm8nsubhpixtn3i.png \"アクセスの承認\")\n\n\u003Cbr>\u003C/br>\n\n![JIRAインスタンスを選択して承認](https://res.cloudinary.com/about-gitlab-com/image/upload/v1772643461/chuzqd0jeptfwvoj7wjr.png \"JIRAインスタンスを選択して承認\")\n\n\u003Cbr>\u003C/br>\n\n![成功！](https://res.cloudinary.com/about-gitlab-com/image/upload/v1772643462/bsgti5iste2bzck19o5y.png \"成功！\")\n\n\u003Cbr>\u003C/br>\n\n### MCPダッシュボードでの確認\n\nGitLabには、IDEに組み込みの**MCPダッシュボード**も用意されています。\n\nVS CodeまたはVSCodiumで、コマンドパレット（macOSでは `Cmd+Shift+P`、Windows/Linuxでは `Ctrl+Shift+P`）を開いて「**GitLab: Show MCP Dashboard**」を検索してください。ダッシュボードは新しいエディタータブで表示され、以下の情報を確認できます。\n\n* 設定済みの各MCPサーバーの**接続ステータス**\n* サーバーが公開している**利用可能なツール**（例：`jira_get_issue`、`jira_create_issue`）\n* **サーバーログ** — リアルタイムで呼び出されているツールを確認可能\n\n![MCPサーバーのダッシュボードとステータス](https://res.cloudinary.com/about-gitlab-com/image/upload/v1772643462/mmvdfchucacsydivowvn.png \"MCPサーバーのダッシュボードとステータス\")\n\n\u003Cbr>\u003C/br>\n\n![サーバーの詳細と権限](https://res.cloudinary.com/about-gitlab-com/image/upload/v1772643462/tcocgdvovp2dl42pvfn8.png \"サーバーの詳細と権限\")\n\n\u003Cbr>\u003C/br>\n\n\n![MCPサーバーログ](https://res.cloudinary.com/about-gitlab-com/image/upload/v1772643466/mougvqqk1bozchaufsci.png \"MCPサーバーログ\")\n\n\u003Cbr>\u003C/br>\n\n### インタラクティブウォークスルー：MCPのテスト\n\n\u003Ciframe src=\"https://player.vimeo.com/video/1170005495?badge=0&amp;autopause=0&amp; player_id=0&amp;app_id=58479\" frameborder=\"0\" allow=\"autoplay; fullscreen; picture-in-picture; clipboard-write; encrypted-media; web-share\" referrerpolicy=\"strict-origin-when-cross-origin\" style=\"position:absolute;top:0;left:0;width:100%;height:100%;\" title=\"Testing MCP\">\u003C/iframe>\u003Cscript src=\"https://player.vimeo.com/api/player.js\">\u003C/script>\n\n## パート3：実践的なユースケース\n\n統合の設定が完了したら、JiraをGitLab Duo Agent Platformへの接続を実現できる3つの実践的なワークフローを見ていきましょう。\n\n### プランニングアシスタント\n\n**シナリオ：** スプリントプランニングの準備として、バックログをすばやく評価し、優先事項を把握し、ブロッカーを特定する必要があります。\n\nこのデモでは以下の操作を紹介します。\n\n* バックログの照会\n* 未割り当ての高優先度課題の特定\n* AIによるスプリント推奨の取得\n\n#### プロンプト例\n\nGitLab Duo Agent Platformのチャットで以下のプロンプトをお試しください。\n\n```text\nList all the unassigned issues in JIRA for project GITLAB\n```\n\n```text\nSuggest the two top issues to prioritize and summarize them. Assign them to me.\n```\n\n### インタラクティブウォークスルー：プロジェクトプランニング\n\n\u003Ciframe src=\"https://player.vimeo.com/video/1170005462?badge=0&amp;autopause=0&amp;player_id=0&amp;app_id=58479\" frameborder=\"0\" allow=\"autoplay; fullscreen; picture-in-picture; clipboard-write; encrypted-media; web-share\" referrerpolicy=\"strict-origin-when-cross-origin\" style=\"position:absolute;top:0;left:0;width:100%;height:100%;\" title=\"Project Planning\">\u003C/iframe>\u003Cscript src=\"https://player.vimeo.com/api/player. js\">\u003C/script>\n\n### コードからの課題トリアージと作成\n\n**シナリオ：** コードレビュー中にバグを発見し、IDEを離れることなく、関連するコンテキストに沿ってJiraの課題を作成したい場合です。\n\nこのデモでは以下の手順を紹介します。\n\n* コーディング中のバグの特定\n* 自然言語を使ったJira課題の詳細な作成\n* コードのコンテキストに沿った課題フィールドの自動入力\n* 現在のブランチへの課題のリンク\n\n#### プロンプト例\n\n```text\nSearch in JIRA for a bug related to: Null pointer exception in PaymentService.processRefund().\nIf it does not exist create it with all the context needed from the code. Find possible blockers that this bug may cause.\n```\n\n```text\nCreate a new branch called issue-gitlab-18, checkout, and link it to the issue we just created. Assign the JIRA issue to me and mark it as in-progress.\n```\n\n### インタラクティブウォークスルー：バグレビューとタスク自動化\n\n\u003Ciframe src=\"https://player.vimeo.com/video/1170005368?badge=0&amp;autopause=0&amp; player_id=0&amp;app_id=58479\" frameborder=\"0\" allow=\"autoplay; fullscreen; picture-in-picture; clipboard-write; encrypted-media; web-share\" referrerpolicy=\"strict-origin-when-cross-origin\" style=\"position:absolute;top:0;left:0;width:100%;height:100%;\" title=\"Bug Review\">\u003C/iframe>\u003Cscript src=\"https://player.vimeo.com/api/player.js\">\u003C/script>\n\n### クロスシステムのインシデント調査\n\n**シナリオ：** 本番環境でインシデントが発生し、Jira（インシデントチケット）、GitLabプロジェクト管理、コードベース、マージリクエストからの情報を照合して根本原因を特定する必要があります。\n\nこのデモでは以下を実演します。\n\n* Jiraからのインシデント詳細の取得\n* GitLabの最近のマージリクエストとの照合\n* 関連する可能性のあるコード変更の特定\n* インシデントタイムラインの生成\n* 修正計画の設計とGitLabのワークアイテムとしての作成\n\n#### プロンプト例\n\n```text\n\"We have a production incident INC-1 about checkout failures. Can you help me investigate with all available context?\"\n```\n\n```text\nCreate a timeline of events for incident INC-1 including related Jira issues and recent deployments\n```\n\n```text\nPropose a remediation plan\n```\n\n### インタラクティブウォークスルー：クロスシステムのトラブルシューティングと修正\n\n\u003Ciframe src=\"https://player.vimeo.com/video/1170005413?badge=0&amp;autopause=0&amp; player_id=0&amp;app_id=58479\" frameborder=\"0\" allow=\"autoplay; fullscreen; picture-in-picture; clipboard-write; encrypted-media; web-share\" referrerpolicy=\"strict-origin-when-cross-origin\" style=\"position:absolute;top:0;left:0;width:100%;height:100%;\" title=\"Cross System Investigation\">\u003C/iframe>\u003Cscript src=\"https://player.vimeo.com/api/player.js\">\u003C/script>\n\n## トラブルシューティング\n\nよくあるセットアップの問題と解決策を以下にまとめます。\n\n| 問題 | 解決策 |\n| ----- | ----- |\n| 「MCP server not found」 | `mcp.json` ファイルが正しい場所にあり、適切にフォーマットされていることを確認してください。 |\n| 「Authentication failed」 | OAuth認証情報を再確認し、Atlassianでスコープが正しく設定されていることを確認してください。 |\n| 「No Jira tools available」 | `mcp.json` を更新後にVS Codeを再起動し、GitLabでMCPが有効になっていることを確認してください。 |\n| 「Connection timeout」 | `mcp.atlassian.com` へのネットワーク接続を確認してください。 |\n\n\u003Cbr/> 詳細なトラブルシューティングについては、[GitLab MCPクライアントのドキュメント](https://docs.gitlab.com/ja-jp/user/gitlab_duo/model_context_protocol/mcp_clients/)をご参照ください。\n\n\n## セキュリティに関する考慮事項\n\nJiraをGitLab Duo Agent Platformと統合する際は、以下の点にご注意ください。\n\n* **OAuthトークン** — 認証情報を安全に管理してください。\n* **最小権限の原則** — Jiraスコープは必要最小限のみ付与してください。\n* **トークンのローテーション** — セキュリティ管理の一環として、OAuth認証情報を定期的にローテーションしてください。\n\n\n## まとめ\n\nMCPを通じてGitLab Duo Agent Platformをさまざまなツールに接続することで、開発ライフサイクルとのインタラクションが大きく変わります。この記事では、以下の方法を学びました。\n\n* **自然言語による課題の照会** — バックログ、スプリント、インシデントについて自然言語で質問できます。\n* **DevSecOps環境全体での課題の作成と更新** — IDEを離れることなくバグを報告し、チケットを更新できます。\n* **システム間の情報照合** — JiraのデータをGitLabのプロジェクト管理、マージリクエスト、パイプラインと組み合わせることで、全体的な可視性が得られます。\n* **コンテキスト切り替えの削減** — プロジェクト管理とのつながりを維持しながら、コードに集中できます。\n\nこの統合は、MCPの可能性を体現するものです。AIを通じてツールへの標準化されたセキュアなアクセスを提供し、ガバナンスやセキュリティを損なうことなく、デベロッパーがより効率的に作業できる環境を実現します。\n\n\n## 関連リソース\n\n* [Model Context Protocol統合](https://about.gitlab.com/ja-jp/blog/duo-agent-platform-with-mcp/)\n\n* [Model Context Protocolとは](https://about.gitlab.com/topics/ai/model-context-protocol/)\n\n* [エージェント型AIに関するガイドとリソース](https://about.gitlab.com/ja-jp/blog/agentic-ai-guides-and-resources/)\n\n* [GitLab MCPクライアントのドキュメント](https://docs.gitlab.com/ja-jp/user/gitlab_duo/model_context_protocol/mcp_clients/)\n\n* [GitLab Duo Agent Platformを始める：完全ガイド](https://about.gitlab.com/ja-jp/blog/gitlab-duo-agent-platform-complete-getting-started-guide/)",[747],"Albert Rabassa","2026-03-30","2026-03-05","MCPであらゆるツールを接続してGitLab Duo Agent Platformを拡張",[724,24],"MCPを使用して外部ツールをGitLab Duo Agent Platformに接続する方法を解説します。3つの実践的なワークフローデモを含むステップバイステップのセットアップガイドです。",{"featured":14,"template":15,"slug":754},"extend-gitlab-duo-agent-platform-connect-any-tool-with-mcp",{"promotions":756},[757,770,781,793],{"id":758,"categories":759,"header":760,"text":761,"button":762,"image":767},"ai-modernization",[11],"Is AI achieving its promise at scale?","Quiz will take 5 minutes or less",{"text":763,"config":764},"Get your AI maturity score",{"href":765,"dataGaName":766,"dataGaLocation":250},"/assessments/ai-modernization-assessment/","modernization assessment",{"config":768},{"src":769},"https://res.cloudinary.com/about-gitlab-com/image/upload/v1772138786/qix0m7kwnd8x2fh1zq49.png",{"id":771,"categories":772,"header":773,"text":761,"button":774,"image":778},"devops-modernization",[724,40],"Are you just managing tools or shipping innovation?",{"text":775,"config":776},"Get your DevOps maturity score",{"href":777,"dataGaName":766,"dataGaLocation":250},"/assessments/devops-modernization-assessment/",{"config":779},{"src":780},"https://res.cloudinary.com/about-gitlab-com/image/upload/v1772138785/eg818fmakweyuznttgid.png",{"id":782,"categories":783,"header":785,"text":761,"button":786,"image":790},"security-modernization",[784],"security","Are you trading speed for security?",{"text":787,"config":788},"Get your security maturity score",{"href":789,"dataGaName":766,"dataGaLocation":250},"/assessments/security-modernization-assessment/",{"config":791},{"src":792},"https://res.cloudinary.com/about-gitlab-com/image/upload/v1772138786/p4pbqd9nnjejg5ds6mdk.png",{"id":794,"paths":795,"header":798,"text":799,"button":800,"image":805},"github-azure-migration",[796,797],"migration-from-azure-devops-to-gitlab","integrating-azure-devops-scm-and-gitlab","Is your team ready for GitHub's Azure move?","GitHub is already rebuilding around Azure. Find out what it means for you.",{"text":801,"config":802},"See how GitLab compares to GitHub",{"href":803,"dataGaName":804,"dataGaLocation":250},"/compare/gitlab-vs-github/github-azure-migration/","github azure migration",{"config":806},{"src":780},{"header":808,"blurb":809,"button":810,"secondaryButton":814},"今すぐ開発をスピードアップ","DevSecOpsに特化したインテリジェントオーケストレーションプラットフォームで実現できることをご確認ください。\n",{"text":52,"config":811},{"href":812,"dataGaName":55,"dataGaLocation":813},"https://gitlab.com/-/trial_registrations/new?glm_content=default-saas-trial&glm_source=about.gitlab.com/ja-jp/","feature",{"text":57,"config":815},{"href":59,"dataGaName":60,"dataGaLocation":813},1777493633798]