[{"data":1,"prerenderedAt":705},["ShallowReactive",2],{"/ja-jp/blog/learn-advanced-rust-programming-with-a-little-help-from-ai-code-suggestions/":3,"navigation-ja-jp":39,"banner-ja-jp":455,"footer-ja-jp":467,"Michael Friedrich":677,"next-steps-ja-jp":690},{"_path":4,"_dir":5,"_draft":6,"_partial":6,"_locale":7,"seo":8,"content":16,"config":29,"_id":32,"_type":33,"title":34,"_source":35,"_file":36,"_stem":37,"_extension":38},"/ja-jp/blog/learn-advanced-rust-programming-with-a-little-help-from-ai-code-suggestions","blog",false,"",{"title":9,"description":10,"ogTitle":9,"ogDescription":10,"noIndex":6,"ogImage":11,"ogUrl":12,"ogSiteName":13,"ogType":14,"canonicalUrls":12,"schema":15},"AIを活用して学ぶ、Rustの高度なプログラミング","このガイド付きチュートリアルでは、AIを搭載したGitLab Duoのコード提案を活用しながら、Rustの高度なプログラミングを学ぶことができます。","https://res.cloudinary.com/about-gitlab-com/image/upload/v1749662439/Blog/Hero%20Images/codewithheart.png","https://about.gitlab.com/blog/learn-advanced-rust-programming-with-a-little-help-from-ai-code-suggestions","https://about.gitlab.com","article","\n                        {\n        \"@context\": \"https://schema.org\",\n        \"@type\": \"Article\",\n        \"headline\": \"AIを活用して学ぶ、Rustの高度なプログラミング\",\n        \"author\": [{\"@type\":\"Person\",\"name\":\"Michael Friedrich\"}],\n        \"datePublished\": \"2023-10-12\",\n      }",{"title":9,"description":10,"authors":17,"heroImage":11,"date":19,"body":20,"category":21,"tags":22,"updatedDate":28},[18],"Michael Friedrich","2023-10-12","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/)を使用すれば、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    // 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\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/ee/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-de/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/ee/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-de/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```\nsrc/\n  main.rs\n  feed_reader.rs\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\n\nfn main() {\n    // ...\n\n    // Print feed_reader get_feeds() output\n    println!(\"{}\", feed_reader::get_feeds(&rss_feeds));\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            // 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![コード提案：`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    // Program args, format \"name,url\"\n    // Split value by , into name, url and add to rss_feeds\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```\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    // Ensure that URL contains a valid format, otherwise throw an error\n```\n\nひとつの解決策として、`url` 変数が `http://` または `https://` で始まっているかを確認し、そうでなければ [panic! マクロ](https://doc.rust-lang.org/rust-by-example/std/panic.html)を使ってエラーをスローする方法があります。コード全体の例は次のようになります。\n\n```rust\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任意のURL文字列から `:` を削除してエラーハンドリングをテストします。`RUST_BACKTRACE=full` 環境変数を追加すると、`panic()` 呼び出しが発生した際に詳細な出力を取得できます。\n\n```\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    // 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この処理の一例として、[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```\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    use std::thread;\n    use std::time::Duration;\n```\n\nその後、forループを閉じるまでコードを書き進めます。コード提案は、自動的にスレッドハンドルを `threads` ベクター変数に追加し、関数の最後でスレッドを結合（join）するよう提案します。\n\n```rust\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次に、`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## フィードの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            // 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```\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                // 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![特定の要件に基づいてフィードエントリの型を出力するためのコード提案](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```\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![コード提案による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                // 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```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```\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![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/)のコード提案をご使用になった方は、ぜひ[ご意見をフィードバックイシューにお寄せください](https://gitlab.com/gitlab-org/gitlab/-/issues/405152)。\n\n*監修：佐々木 直晴 [@naosasaki](https://gitlab.com/naosasaki) \u003Cbr>\n（GitLab合同会社 ソリューションアーキテクト本部 シニアソリューションアーキテクト）*","ai-ml",[23,24,25,26,27],"DevSecOps","careers","tutorial","workflow","AI/ML","2025-01-24",{"slug":30,"featured":6,"template":31},"learn-advanced-rust-programming-with-a-little-help-from-ai-code-suggestions","BlogPost","content:ja-jp:blog:learn-advanced-rust-programming-with-a-little-help-from-ai-code-suggestions.yml","yaml","Learn Advanced Rust Programming With A Little Help From Ai Code Suggestions","content","ja-jp/blog/learn-advanced-rust-programming-with-a-little-help-from-ai-code-suggestions.yml","ja-jp/blog/learn-advanced-rust-programming-with-a-little-help-from-ai-code-suggestions","yml",{"_path":40,"_dir":41,"_draft":6,"_partial":6,"_locale":7,"data":42,"_id":451,"_type":33,"title":452,"_source":35,"_file":453,"_stem":454,"_extension":38},"/shared/ja-jp/main-navigation","ja-jp",{"logo":43,"freeTrial":48,"sales":53,"login":58,"items":63,"search":395,"minimal":429,"duo":442},{"config":44},{"href":45,"dataGaName":46,"dataGaLocation":47},"/ja-jp/","gitlab logo","header",{"text":49,"config":50},"無料トライアルを開始",{"href":51,"dataGaName":52,"dataGaLocation":47},"https://gitlab.com/-/trial_registrations/new?glm_source=about.gitlab.com&glm_content=default-saas-trial/","free trial",{"text":54,"config":55},"お問い合わせ",{"href":56,"dataGaName":57,"dataGaLocation":47},"/ja-jp/sales/","sales",{"text":59,"config":60},"サインイン",{"href":61,"dataGaName":62,"dataGaLocation":47},"https://gitlab.com/users/sign_in/","sign in",[64,108,207,212,317,377],{"text":65,"config":66,"cards":68,"footer":91},"プラットフォーム",{"dataNavLevelOne":67},"platform",[69,75,83],{"title":65,"description":70,"link":71},"最も包括的かつAIで強化されたDevSecOpsプラットフォーム",{"text":72,"config":73},"プラットフォームを詳しく見る",{"href":74,"dataGaName":67,"dataGaLocation":47},"/ja-jp/platform/",{"title":76,"description":77,"link":78},"GitLab Duo（AI）","開発のすべてのステージでAIを活用し、ソフトウェアをより迅速にビルド",{"text":79,"config":80},"GitLab Duoのご紹介",{"href":81,"dataGaName":82,"dataGaLocation":47},"/ja-jp/gitlab-duo/","gitlab duo ai",{"title":84,"description":85,"link":86},"GitLabが選ばれる理由","GitLabが大企業に選ばれる理由10選",{"text":87,"config":88},"詳細はこちら",{"href":89,"dataGaName":90,"dataGaLocation":47},"/ja-jp/why-gitlab/","why gitlab",{"title":92,"items":93},"利用を開始：",[94,99,104],{"text":95,"config":96},"プラットフォームエンジニアリング",{"href":97,"dataGaName":98,"dataGaLocation":47},"/ja-jp/solutions/platform-engineering/","platform engineering",{"text":100,"config":101},"開発者の経験",{"href":102,"dataGaName":103,"dataGaLocation":47},"/ja-jp/developer-experience/","Developer experience",{"text":105,"config":106},"MLOps",{"href":107,"dataGaName":105,"dataGaLocation":47},"/ja-jp/topics/devops/the-role-of-ai-in-devops/",{"text":109,"left":110,"config":111,"link":113,"lists":117,"footer":189},"製品",true,{"dataNavLevelOne":112},"solutions",{"text":114,"config":115},"すべてのソリューションを表示",{"href":116,"dataGaName":112,"dataGaLocation":47},"/ja-jp/solutions/",[118,144,167],{"title":119,"description":120,"link":121,"items":126},"自動化","CI/CDと自動化でデプロイを加速",{"config":122},{"icon":123,"href":124,"dataGaName":125,"dataGaLocation":47},"AutomatedCodeAlt","/ja-jp/solutions/delivery-automation/","automated software delivery",[127,131,135,140],{"text":128,"config":129},"CI/CD",{"href":130,"dataGaLocation":47,"dataGaName":128},"/ja-jp/solutions/continuous-integration/",{"text":132,"config":133},"AIアシストによる開発",{"href":81,"dataGaLocation":47,"dataGaName":134},"AI assisted development",{"text":136,"config":137},"ソースコード管理",{"href":138,"dataGaLocation":47,"dataGaName":139},"/ja-jp/solutions/source-code-management/","Source Code Management",{"text":141,"config":142},"自動化されたソフトウェアデリバリー",{"href":124,"dataGaLocation":47,"dataGaName":143},"Automated software delivery",{"title":145,"description":146,"link":147,"items":152},"セキュリティ","セキュリティを損なうことなくコードをより迅速に完成",{"config":148},{"href":149,"dataGaName":150,"dataGaLocation":47,"icon":151},"/ja-jp/solutions/security-compliance/","security and compliance","ShieldCheckLight",[153,157,162],{"text":154,"config":155},"セキュリティとコンプライアンス",{"href":149,"dataGaLocation":47,"dataGaName":156},"Security & Compliance",{"text":158,"config":159},"ソフトウェアサプライチェーンの安全性",{"href":160,"dataGaLocation":47,"dataGaName":161},"/ja-jp/solutions/supply-chain/","Software supply chain security",{"text":163,"config":164},"コンプライアンスとガバナンス",{"href":165,"dataGaLocation":47,"dataGaName":166},"/ja-jp/solutions/continuous-software-compliance/","Compliance and governance",{"title":168,"link":169,"items":174},"測定",{"config":170},{"icon":171,"href":172,"dataGaName":173,"dataGaLocation":47},"DigitalTransformation","/ja-jp/solutions/visibility-measurement/","visibility and measurement",[175,179,184],{"text":176,"config":177},"可視性と測定",{"href":172,"dataGaLocation":47,"dataGaName":178},"Visibility and Measurement",{"text":180,"config":181},"バリューストリーム管理",{"href":182,"dataGaLocation":47,"dataGaName":183},"/ja-jp/solutions/value-stream-management/","Value Stream Management",{"text":185,"config":186},"分析とインサイト",{"href":187,"dataGaLocation":47,"dataGaName":188},"/ja-jp/solutions/analytics-and-insights/","Analytics and insights",{"title":190,"items":191},"GitLabが活躍する場所",[192,197,202],{"text":193,"config":194},"Enterprise",{"href":195,"dataGaLocation":47,"dataGaName":196},"/ja-jp/enterprise/","enterprise",{"text":198,"config":199},"スモールビジネス",{"href":200,"dataGaLocation":47,"dataGaName":201},"/ja-jp/small-business/","small business",{"text":203,"config":204},"公共機関",{"href":205,"dataGaLocation":47,"dataGaName":206},"/ja-jp/solutions/public-sector/","public sector",{"text":208,"config":209},"価格",{"href":210,"dataGaName":211,"dataGaLocation":47,"dataNavLevelOne":211},"/ja-jp/pricing/","pricing",{"text":213,"config":214,"link":216,"lists":220,"feature":304},"関連リソース",{"dataNavLevelOne":215},"resources",{"text":217,"config":218},"すべてのリソースを表示",{"href":219,"dataGaName":215,"dataGaLocation":47},"/ja-jp/resources/",[221,254,276],{"title":222,"items":223},"はじめに",[224,229,234,239,244,249],{"text":225,"config":226},"インストール",{"href":227,"dataGaName":228,"dataGaLocation":47},"/ja-jp/install/","install",{"text":230,"config":231},"クイックスタートガイド",{"href":232,"dataGaName":233,"dataGaLocation":47},"/ja-jp/get-started/","quick setup checklists",{"text":235,"config":236},"学ぶ",{"href":237,"dataGaLocation":47,"dataGaName":238},"https://university.gitlab.com/","learn",{"text":240,"config":241},"製品ドキュメント",{"href":242,"dataGaName":243,"dataGaLocation":47},"https://docs.gitlab.com/","product documentation",{"text":245,"config":246},"ベストプラクティスビデオ",{"href":247,"dataGaName":248,"dataGaLocation":47},"/ja-jp/getting-started-videos/","best practice videos",{"text":250,"config":251},"インテグレーション",{"href":252,"dataGaName":253,"dataGaLocation":47},"/ja-jp/integrations/","integrations",{"title":255,"items":256},"検索する",[257,262,266,271],{"text":258,"config":259},"お客様成功事例",{"href":260,"dataGaName":261,"dataGaLocation":47},"/ja-jp/customers/","customer success stories",{"text":263,"config":264},"ブログ",{"href":265,"dataGaName":5,"dataGaLocation":47},"/ja-jp/blog/",{"text":267,"config":268},"リモート",{"href":269,"dataGaName":270,"dataGaLocation":47},"https://handbook.gitlab.com/handbook/company/culture/all-remote/","remote",{"text":272,"config":273},"TeamOps",{"href":274,"dataGaName":275,"dataGaLocation":47},"/ja-jp/teamops/","teamops",{"title":277,"items":278},"つなげる",[279,284,289,294,299],{"text":280,"config":281},"GitLabサービス",{"href":282,"dataGaName":283,"dataGaLocation":47},"/ja-jp/services/","services",{"text":285,"config":286},"コミュニティ",{"href":287,"dataGaName":288,"dataGaLocation":47},"/community/","community",{"text":290,"config":291},"フォーラム",{"href":292,"dataGaName":293,"dataGaLocation":47},"https://forum.gitlab.com/","forum",{"text":295,"config":296},"イベント",{"href":297,"dataGaName":298,"dataGaLocation":47},"/events/","events",{"text":300,"config":301},"パートナー",{"href":302,"dataGaName":303,"dataGaLocation":47},"/ja-jp/partners/","partners",{"backgroundColor":305,"textColor":306,"text":307,"image":308,"link":312},"#2f2a6b","#fff","ソフトウェア開発の未来への洞察",{"altText":309,"config":310},"ソースプロモカード",{"src":311},"/images/navigation/the-source-promo-card.svg",{"text":313,"config":314},"最新情報を読む",{"href":315,"dataGaName":316,"dataGaLocation":47},"/ja-jp/the-source/","the source",{"text":318,"config":319,"lists":321},"Company",{"dataNavLevelOne":320},"company",[322],{"items":323},[324,329,335,337,342,347,352,357,362,367,372],{"text":325,"config":326},"GitLabについて",{"href":327,"dataGaName":328,"dataGaLocation":47},"/ja-jp/company/","about",{"text":330,"config":331,"footerGa":334},"採用情報",{"href":332,"dataGaName":333,"dataGaLocation":47},"/jobs/","jobs",{"dataGaName":333},{"text":295,"config":336},{"href":297,"dataGaName":298,"dataGaLocation":47},{"text":338,"config":339},"経営陣",{"href":340,"dataGaName":341,"dataGaLocation":47},"/company/team/e-group/","leadership",{"text":343,"config":344},"チーム",{"href":345,"dataGaName":346,"dataGaLocation":47},"/company/team/","team",{"text":348,"config":349},"ハンドブック",{"href":350,"dataGaName":351,"dataGaLocation":47},"https://handbook.gitlab.com/","handbook",{"text":353,"config":354},"投資家向け情報",{"href":355,"dataGaName":356,"dataGaLocation":47},"https://ir.gitlab.com/","investor relations",{"text":358,"config":359},"トラストセンター",{"href":360,"dataGaName":361,"dataGaLocation":47},"/ja-jp/security/","trust center",{"text":363,"config":364},"AI Transparency Center",{"href":365,"dataGaName":366,"dataGaLocation":47},"/ja-jp/ai-transparency-center/","ai transparency center",{"text":368,"config":369},"ニュースレター",{"href":370,"dataGaName":371,"dataGaLocation":47},"/company/contact/","newsletter",{"text":373,"config":374},"プレス",{"href":375,"dataGaName":376,"dataGaLocation":47},"/press/","press",{"text":54,"config":378,"lists":379},{"dataNavLevelOne":320},[380],{"items":381},[382,385,390],{"text":54,"config":383},{"href":56,"dataGaName":384,"dataGaLocation":47},"talk to sales",{"text":386,"config":387},"サポートを受ける",{"href":388,"dataGaName":389,"dataGaLocation":47},"/support/","get help",{"text":391,"config":392},"カスタマーポータル",{"href":393,"dataGaName":394,"dataGaLocation":47},"https://customers.gitlab.com/customers/sign_in/","customer portal",{"close":396,"login":397,"suggestions":404},"閉じる",{"text":398,"link":399},"リポジトリとプロジェクトを検索するには、次にログインします",{"text":400,"config":401},"GitLab.com",{"href":61,"dataGaName":402,"dataGaLocation":403},"search login","search",{"text":405,"default":406},"提案",[407,410,415,417,421,425],{"text":76,"config":408},{"href":81,"dataGaName":409,"dataGaLocation":403},"GitLab Duo (AI)",{"text":411,"config":412},"コード提案（AI）",{"href":413,"dataGaName":414,"dataGaLocation":403},"/ja-jp/solutions/code-suggestions/","Code Suggestions (AI)",{"text":128,"config":416},{"href":130,"dataGaName":128,"dataGaLocation":403},{"text":418,"config":419},"GitLab on AWS",{"href":420,"dataGaName":418,"dataGaLocation":403},"/ja-jp/partners/technology-partners/aws/",{"text":422,"config":423},"GitLab on Google Cloud",{"href":424,"dataGaName":422,"dataGaLocation":403},"/ja-jp/partners/technology-partners/google-cloud-platform/",{"text":426,"config":427},"GitLabを選ぶ理由",{"href":89,"dataGaName":428,"dataGaLocation":403},"Why GitLab?",{"freeTrial":430,"mobileIcon":434,"desktopIcon":439},{"text":49,"config":431},{"href":432,"dataGaName":52,"dataGaLocation":433},"https://gitlab.com/-/trials/new/","nav",{"altText":435,"config":436},"GitLabアイコン",{"src":437,"dataGaName":438,"dataGaLocation":433},"/images/brand/gitlab-logo-tanuki.svg","gitlab icon",{"altText":435,"config":440},{"src":441,"dataGaName":438,"dataGaLocation":433},"/images/brand/gitlab-logo-type.svg",{"freeTrial":443,"mobileIcon":447,"desktopIcon":449},{"text":444,"config":445},"GitLab Duoの詳細について",{"href":81,"dataGaName":446,"dataGaLocation":433},"gitlab duo",{"altText":435,"config":448},{"src":437,"dataGaName":438,"dataGaLocation":433},{"altText":435,"config":450},{"src":441,"dataGaName":438,"dataGaLocation":433},"content:shared:ja-jp:main-navigation.yml","Main Navigation","shared/ja-jp/main-navigation.yml","shared/ja-jp/main-navigation",{"_path":456,"_dir":41,"_draft":6,"_partial":6,"_locale":7,"title":457,"button":458,"config":462,"_id":464,"_type":33,"_source":35,"_file":465,"_stem":466,"_extension":38},"/shared/ja-jp/banner","GitLab Duo Agent Platformがパブリックベータ版で利用可能になりました！",{"text":87,"config":459},{"href":460,"dataGaName":461,"dataGaLocation":47},"/ja-jp/gitlab-duo/agent-platform/","duo banner",{"layout":463},"release","content:shared:ja-jp:banner.yml","shared/ja-jp/banner.yml","shared/ja-jp/banner",{"_path":468,"_dir":41,"_draft":6,"_partial":6,"_locale":7,"data":469,"_id":673,"_type":33,"title":674,"_source":35,"_file":675,"_stem":676,"_extension":38},"/shared/ja-jp/main-footer",{"text":470,"source":471,"edit":477,"contribute":482,"config":487,"items":492,"minimal":665},"GitはSoftware Freedom Conservancyの商標です。当社は「GitLab」をライセンスに基づいて使用しています",{"text":472,"config":473},"ページのソースを表示",{"href":474,"dataGaName":475,"dataGaLocation":476},"https://gitlab.com/gitlab-com/marketing/digital-experience/about-gitlab-com/","page source","footer",{"text":478,"config":479},"このページを編集",{"href":480,"dataGaName":481,"dataGaLocation":476},"https://gitlab.com/gitlab-com/marketing/digital-experience/about-gitlab-com/-/blob/main/content/","web ide",{"text":483,"config":484},"ご協力をお願いします",{"href":485,"dataGaName":486,"dataGaLocation":476},"https://gitlab.com/gitlab-com/marketing/digital-experience/about-gitlab-com/-/blob/main/CONTRIBUTING.md/","please contribute",{"twitter":488,"facebook":489,"youtube":490,"linkedin":491},"https://twitter.com/gitlab","https://www.facebook.com/gitlab","https://www.youtube.com/channel/UCnMGQ8QHMAnVIsI3xJrihhg","https://www.linkedin.com/company/gitlab-com",[493,516,570,603,637],{"title":65,"links":494,"subMenu":499},[495],{"text":496,"config":497},"DevSecOpsプラットフォーム",{"href":74,"dataGaName":498,"dataGaLocation":476},"devsecops platform",[500],{"title":208,"links":501},[502,506,511],{"text":503,"config":504},"プランの表示",{"href":210,"dataGaName":505,"dataGaLocation":476},"view plans",{"text":507,"config":508},"Premiumを選ぶ理由",{"href":509,"dataGaName":510,"dataGaLocation":476},"/ja-jp/pricing/premium/","why premium",{"text":512,"config":513},"Ultimateを選ぶ理由",{"href":514,"dataGaName":515,"dataGaLocation":476},"/ja-jp/pricing/ultimate/","why ultimate",{"title":517,"links":518},"ソリューション",[519,524,527,529,534,539,543,546,549,554,556,558,560,565],{"text":520,"config":521},"デジタルトランスフォーメーション",{"href":522,"dataGaName":523,"dataGaLocation":476},"/ja-jp/topics/digital-transformation/","digital transformation",{"text":154,"config":525},{"href":149,"dataGaName":526,"dataGaLocation":476},"security & compliance",{"text":141,"config":528},{"href":124,"dataGaName":125,"dataGaLocation":476},{"text":530,"config":531},"アジャイル開発",{"href":532,"dataGaName":533,"dataGaLocation":476},"/ja-jp/solutions/agile-delivery/","agile delivery",{"text":535,"config":536},"クラウドトランスフォーメーション",{"href":537,"dataGaName":538,"dataGaLocation":476},"/ja-jp/topics/cloud-native/","cloud transformation",{"text":540,"config":541},"SCM",{"href":138,"dataGaName":542,"dataGaLocation":476},"source code management",{"text":128,"config":544},{"href":130,"dataGaName":545,"dataGaLocation":476},"continuous integration & delivery",{"text":180,"config":547},{"href":182,"dataGaName":548,"dataGaLocation":476},"value stream management",{"text":550,"config":551},"GitOps",{"href":552,"dataGaName":553,"dataGaLocation":476},"/ja-jp/solutions/gitops/","gitops",{"text":193,"config":555},{"href":195,"dataGaName":196,"dataGaLocation":476},{"text":198,"config":557},{"href":200,"dataGaName":201,"dataGaLocation":476},{"text":203,"config":559},{"href":205,"dataGaName":206,"dataGaLocation":476},{"text":561,"config":562},"教育",{"href":563,"dataGaName":564,"dataGaLocation":476},"/ja-jp/solutions/education/","education",{"text":566,"config":567},"金融サービス",{"href":568,"dataGaName":569,"dataGaLocation":476},"/ja-jp/solutions/finance/","financial services",{"title":213,"links":571},[572,574,576,578,581,583,587,589,591,593,595,597,599,601],{"text":225,"config":573},{"href":227,"dataGaName":228,"dataGaLocation":476},{"text":230,"config":575},{"href":232,"dataGaName":233,"dataGaLocation":476},{"text":235,"config":577},{"href":237,"dataGaName":238,"dataGaLocation":476},{"text":240,"config":579},{"href":242,"dataGaName":580,"dataGaLocation":476},"docs",{"text":263,"config":582},{"href":265,"dataGaName":5},{"text":584,"config":585},"お客様の成功事例",{"href":586,"dataGaLocation":476},"/customers/",{"text":258,"config":588},{"href":260,"dataGaName":261,"dataGaLocation":476},{"text":267,"config":590},{"href":269,"dataGaName":270,"dataGaLocation":476},{"text":280,"config":592},{"href":282,"dataGaName":283,"dataGaLocation":476},{"text":272,"config":594},{"href":274,"dataGaName":275,"dataGaLocation":476},{"text":285,"config":596},{"href":287,"dataGaName":288,"dataGaLocation":476},{"text":290,"config":598},{"href":292,"dataGaName":293,"dataGaLocation":476},{"text":295,"config":600},{"href":297,"dataGaName":298,"dataGaLocation":476},{"text":300,"config":602},{"href":302,"dataGaName":303,"dataGaLocation":476},{"title":318,"links":604},[605,607,609,611,613,615,617,621,626,628,630,632],{"text":325,"config":606},{"href":327,"dataGaName":320,"dataGaLocation":476},{"text":330,"config":608},{"href":332,"dataGaName":333,"dataGaLocation":476},{"text":338,"config":610},{"href":340,"dataGaName":341,"dataGaLocation":476},{"text":343,"config":612},{"href":345,"dataGaName":346,"dataGaLocation":476},{"text":348,"config":614},{"href":350,"dataGaName":351,"dataGaLocation":476},{"text":353,"config":616},{"href":355,"dataGaName":356,"dataGaLocation":476},{"text":618,"config":619},"Sustainability",{"href":620,"dataGaName":618,"dataGaLocation":476},"/sustainability/",{"text":622,"config":623},"ダイバーシティ、インクルージョン、ビロンギング（DIB）",{"href":624,"dataGaName":625,"dataGaLocation":476},"/ja-jp/diversity-inclusion-belonging/","Diversity, inclusion and belonging",{"text":358,"config":627},{"href":360,"dataGaName":361,"dataGaLocation":476},{"text":368,"config":629},{"href":370,"dataGaName":371,"dataGaLocation":476},{"text":373,"config":631},{"href":375,"dataGaName":376,"dataGaLocation":476},{"text":633,"config":634},"現代奴隷制の透明性に関する声明",{"href":635,"dataGaName":636,"dataGaLocation":476},"https://handbook.gitlab.com/handbook/legal/modern-slavery-act-transparency-statement/","modern slavery transparency statement",{"title":54,"links":638},[639,641,643,645,650,655,660],{"text":54,"config":640},{"href":56,"dataGaName":57,"dataGaLocation":476},{"text":386,"config":642},{"href":388,"dataGaName":389,"dataGaLocation":476},{"text":391,"config":644},{"href":393,"dataGaName":394,"dataGaLocation":476},{"text":646,"config":647},"ステータス",{"href":648,"dataGaName":649,"dataGaLocation":476},"https://status.gitlab.com/","status",{"text":651,"config":652},"利用規約",{"href":653,"dataGaName":654,"dataGaLocation":476},"/terms/","terms of use",{"text":656,"config":657},"プライバシーに関する声明",{"href":658,"dataGaName":659,"dataGaLocation":476},"/ja-jp/privacy/","privacy statement",{"text":661,"config":662},"Cookieの設定",{"dataGaName":663,"dataGaLocation":476,"id":664,"isOneTrustButton":110},"cookie preferences","ot-sdk-btn",{"items":666},[667,669,671],{"text":651,"config":668},{"href":653,"dataGaName":654,"dataGaLocation":476},{"text":656,"config":670},{"href":658,"dataGaName":659,"dataGaLocation":476},{"text":661,"config":672},{"dataGaName":663,"dataGaLocation":476,"id":664,"isOneTrustButton":110},"content:shared:ja-jp:main-footer.yml","Main Footer","shared/ja-jp/main-footer.yml","shared/ja-jp/main-footer",[678],{"_path":679,"_dir":680,"_draft":6,"_partial":6,"_locale":7,"content":681,"config":685,"_id":687,"_type":33,"title":18,"_source":35,"_file":688,"_stem":689,"_extension":38},"/en-us/blog/authors/michael-friedrich","authors",{"name":18,"config":682},{"headshot":683,"ctfId":684},"https://res.cloudinary.com/about-gitlab-com/image/upload/v1749659879/Blog/Author%20Headshots/dnsmichi-headshot.jpg","dnsmichi",{"template":686},"BlogAuthor","content:en-us:blog:authors:michael-friedrich.yml","en-us/blog/authors/michael-friedrich.yml","en-us/blog/authors/michael-friedrich",{"_path":691,"_dir":41,"_draft":6,"_partial":6,"_locale":7,"header":692,"eyebrow":693,"blurb":694,"button":695,"secondaryButton":699,"_id":701,"_type":33,"title":702,"_source":35,"_file":703,"_stem":704,"_extension":38},"/shared/ja-jp/next-steps","より優れたソフトウェアをより速く提供","フォーチュン100企業の50%以上がGitLabを信頼","インテリジェントなDevSecOpsプラットフォームで\n\n\nチームの可能性を広げましょう。\n",{"text":49,"config":696},{"href":697,"dataGaName":52,"dataGaLocation":698},"https://gitlab.com/-/trial_registrations/new?glm_content=default-saas-trial&glm_source=about.gitlab.com/","feature",{"text":54,"config":700},{"href":56,"dataGaName":57,"dataGaLocation":698},"content:shared:ja-jp:next-steps.yml","Next Steps","shared/ja-jp/next-steps.yml","shared/ja-jp/next-steps",1753475405967]