[{"data":1,"prerenderedAt":814},["ShallowReactive",2],{"/en-us/blog/learn-advanced-rust-programming-with-a-little-help-from-ai-code-suggestions":3,"navigation-en-us":41,"banner-en-us":451,"footer-en-us":461,"blog-post-authors-en-us-Michael Friedrich":701,"blog-related-posts-en-us-learn-advanced-rust-programming-with-a-little-help-from-ai-code-suggestions":715,"blog-promotions-en-us":752,"next-steps-en-us":804},{"id":4,"title":5,"authorSlugs":6,"body":8,"categorySlug":9,"config":10,"content":14,"description":8,"extension":28,"isFeatured":12,"meta":29,"navigation":30,"path":31,"publishedDate":20,"seo":32,"stem":36,"tagSlugs":37,"__hash__":40},"blogPosts/en-us/blog/learn-advanced-rust-programming-with-a-little-help-from-ai-code-suggestions.yml","Learn Advanced Rust Programming With A Little Help From Ai Code Suggestions",[7],"michael-friedrich",null,"ai-ml",{"slug":11,"featured":12,"template":13},"learn-advanced-rust-programming-with-a-little-help-from-ai-code-suggestions",false,"BlogPost",{"title":15,"description":16,"authors":17,"heroImage":19,"date":20,"body":21,"category":9,"tags":22},"Learn advanced Rust programming with a little help from AI","Use this guided tutorial, along with AI-powered GitLab Duo Code Suggestions, to continue learning advanced Rust programming.",[18],"Michael Friedrich","https://res.cloudinary.com/about-gitlab-com/image/upload/v1749662439/Blog/Hero%20Images/codewithheart.png","2023-10-12","When I started learning a new programming language more than 20 years ago, we had access to the Visual Studio 6 MSDN library, installed from 6 CD-ROMs. Algorithms with pen and paper, design pattern books, and MSDN queries to figure out the correct type were often time-consuming. Learning a new programming language changed fundamentally in the era of remote collaboration and artificial intelligence (AI). Now you can spin up a [remote development workspace](https://about.gitlab.com/blog/quick-start-guide-for-gitlab-workspaces/), share your screen, and engage in a group programming session. With the help of [GitLab Duo Code Suggestions](/gitlab-duo-agent-platform/), you always have an intelligent partner at your fingertips. Code Suggestions can learn from your programming style and experience. They only need input and context to provide you with the most efficient suggestions.\n\nIn this tutorial, we build on the [getting started blog post](/blog/learning-rust-with-a-little-help-from-ai-code-suggestions-getting-started/) and design and create a simple feed reader application.\n\n- [Preparations](#preparations)\n    - [Code Suggestions](#code-suggestions)\n- [Continue learning Rust](#continue-learning-rust)\n    - [Hello, Reader App](#hello-reader-app)\n    - [Initialize project](#initialize-project)\n    - [Define RSS feed URLs](#define-rss-feed-urls)\n- [Modules](#modules)\n    - [Call the module function in main()](#call-the-module-function-in-main)\n- [Crates](#crates)\n    - [feed-rs: parse XML feed](#feed-rs-parse-xml-feed)\n- [Runtime configuration: Program arguments](#runtime-configuration-program-arguments)\n    - [User input error handling](#user-input-error-handling)\n- [Persistence and data storage](#persistence-and-data-storage)\n- [Optimization](#optimization)\n    - [Asynchronous execution](#asynchronous-execution)\n    - [Spawning threads](#spawning-threads)\n    - [Function scopes, threads, and closures](#function-scopes-threads-and-closures)\n- [Parse feed XML into objects](#parse-feed-xml-into-object-types)\n    - [Map generic feed data types](#map-generic-feed-data-types)\n    - [Error handling with Option::unwrap()](#error-handling-with-option-unwrap)\n- [Benchmarks](#benchmarks)\n    - [Sequential vs. Parallel execution benchmark](#sequential-vs-parallel-execution-benchmark)\n    - [CI/CD with Rust caching](#cicd-with-rust-caching)\n- [What is next](#what-is-next)\n    - [Async learning exercises](#async-learning-exercises)\n    - [Share your feedback](#share-your-feedback)\n\n## Preparations\nBefore diving into the source code, make sure to set up [VS Code](/blog/learning-rust-with-a-little-help-from-ai-code-suggestions-getting-started/#vs-code) and [your development environment with Rust](/blog/learning-rust-with-a-little-help-from-ai-code-suggestions-getting-started/#development-environment-for-rust).\n\n### Code Suggestions\nFamiliarize yourself with suggestions before actually verifying the suggestions. GitLab Duo Code Suggestions are provided as you type, so you do not need use specific keyboard shortcuts. To accept a code suggestion, press the `tab` key. Also note that writing new code works more reliably than refactoring existing code. AI is non-deterministic, which means that the same suggestion may not be repeated after deleting the code suggestion. While Code Suggestions is in Beta, we are working on improving the accuracy of generated content overall. Please review the [known limitations](https://docs.gitlab.com/user/project/repository/code_suggestions/#known-limitations), as this could affect your learning experience.\n\n**Tip:** The latest release of Code Suggestions supports multi-line instructions. You can refine the specifications to your needs to get better suggestions.\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\nThe VS Code extension overlay is shown when offering a suggestion. You can use the `tab` key to accept the suggested line(s), or `cmd cursor right` to accept one word. Additionally, the three dots menu allows you to always show the toolbar.\n\n![VS Code GitLab Duo Code Suggestions overlay with instructions](https://about.gitlab.com/images/blogimages/learn-rust-with-ai-code-suggestions-advanced-programming/vs_code_code_suggestions_options_overlay_keep_toolbar.png)\n\n## Continue learning Rust\nNow, let us continue learning Rust, which is one of the [supported languages in Code Suggestions](https://docs.gitlab.com/user/project/repository/code_suggestions/#supported-languages). [Rust by Example](https://doc.rust-lang.org/rust-by-example/) provides an excellent tutorial for beginners, together with the official [Rust book](https://doc.rust-lang.org/book/). Both resources are referenced throughout this blog post.\n\n### Hello, Reader App\nThere are many ways to create an application and learn Rust. Some of them involve using existing Rust libraries - so-called `Crates`. We will use them a bit further into the blog post. For example, you could create a command-line app that processes images and writes the result to a file. Solving a classic maze or writing a Sudoku solver can also be a fun challenge. Game development is another option. The book [Hands-on Rust](https://hands-on-rust.com/) provides a thorough learning path by creating a dungeon crawler game. My colleague Fatima Sarah Khalid started the [Dragon Realm in C++ with a little help from AI](/blog/building-a-text-adventure-using-cplusplus-and-code-suggestions/) -- check it out, too.\n\nHere is a real use case that helps solve an actual problem: Collecting important information from different sources into RSS feeds for (security) releases, blog posts, and social discussion forums like Hacker News. Often, we want to filter for specific keywords or versions mentioned in the updates. These requirements allow us to formulate a requirements list for our application:\n\n1. Fetch data from different sources (HTTP websites, REST API, RSS feeds). RSS feeds in the first iteration.\n1. Parse the data.\n1. Present the data to the user, or write it to disk.\n1. Optimize performance.\n\nThe following example application output will be available after the learning steps in this blog post:\n\n![VS Code Terminal, cargo run with formatted feed entries output](https://about.gitlab.com/images/blogimages/learn-rust-with-ai-code-suggestions-advanced-programming/vs_code_terminal_cargo_run_formatted_output_final.png)\n\nThe application should be modular and build the foundation to add more data types, filters, and hooks to trigger actions at a later point.\n\n### Initialize project\nReminder: `cargo init` in the project root creates the file structure, including the `main()` entrypoint. Therefore, we will learn how to create and use Rust modules in the next step.\n\nCreate a new directory called `learn-rust-ai-app-reader`, change into it and run `cargo init`. This command implicitly runs `git init` to initialize a new Git repository locally. The remaining step is to configure the Git remote repository path, for example, `https://gitlab.com/gitlab-da/use-cases/ai/learn-with-ai/learn-rust-ai-app-reader`. Please adjust the path for your namespace. Pushing the Git repository [automatically creates a new private project in GitLab](https://docs.gitlab.com/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\nOpen VS Code from the newly created directory. The `code` CLI will spawn a new VS Code window on macOS.\n\n```shell\ncode .\n```\n\n### Define RSS feed URLs\nAdd a new hashmap to store the RSS feed URLs inside the `src/main.rs` file in the `main()` function. You can instruct GitLab Duo Code Suggestions with a multi-line comment to create a [`HashMap`](https://doc.rust-lang.org/stable/std/collections/struct.HashMap.html) object, and initialize it with default values for Hacker News, and TechCrunch. Note: Verify that the URLs are correct when you get suggestions.\n\n```rust\nfn main() {\n    // Define RSS feed URLs in the variable rss_feeds\n    // Use a HashMap\n    // Add Hacker News and TechCrunch\n    // Ensure to use String as type\n\n}\n```\n\nNote that the code comment provides instructions for:\n\n1. The variable name `rss_feeds`.\n2. The `HashMap` type.\n3. Initial seed key/value pairs.\n4. String as type (can be seen with `to_string()` calls).\n\nOne possible suggested path can be as follows:\n\n```rust\nuse std::collections::HashMap;\n\nfn main() {\n    // Define RSS feed URLs in the variable rss_feeds\n    // Use a HashMap\n    // Add Hacker News and TechCrunch\n    // Ensure to use String as type\n    let rss_feeds = HashMap::from([\n        (\"Hacker News\".to_string(), \"https://news.ycombinator.com/rss\".to_string()),\n        (\"TechCrunch\".to_string(), \"https://techcrunch.com/feed/\".to_string()),\n    ]);\n\n}\n```\n\n![VS Code with Code Suggestions for RSS feed URLs for Hacker News and TechCrunch](https://about.gitlab.com/images/blogimages/learn-rust-with-ai-code-suggestions-advanced-programming/vs_code_main_array_rss_feed_urls_suggested.png)\n\nOpen a new terminal in VS Code (cmd shift p - search for `terminal`), and run `cargo build` to build the changes. The error message instructs you to add the `use std::collections::HashMap;` import.\n\nThe next step is to do something with the RSS feed URLs. [The previous blog post](/blog/learning-rust-with-a-little-help-from-ai-code-suggestions-getting-started/) taught us to split code into functions. We want to organize the code more modularly for our reader application, and use Rust modules.\n\n## Modules\n[Modules](https://doc.rust-lang.org/rust-by-example/mod.html) help with organizing code. They can also be used to hide functions into the module scope, limiting access to them from the main() scope. In our reader application, we want to fetch the RSS feed content, and parse the XML response. The `main()` caller should only be able to access the `get_feeds()` function, while other functionality is only available in the module.\n\nCreate a new file `feed_reader.rs` in the `src/` directory. Instruct Code Suggestions to create a public module named `feed_reader`, and a public function `get_feeds()` with a String HashMap as input. Important: The file and module names need to be the same, following the [Rust module structure](https://doc.rust-lang.org/book/ch07-02-defining-modules-to-control-scope-and-privacy.html).\n\n![Code Suggestions: Create public module, with function and input types](https://about.gitlab.com/images/blogimages/learn-rust-with-ai-code-suggestions-advanced-programming/code_suggestions_rust_public_module_function_input.png)\n\nInstructing Code Suggestions with the input variable name and type will also import the required `std::collections::HashMap` module. Tip: Experiment with the comments, and refine the variable types to land the best results. Passing function parameters as object references is considered best practice in Rust, for example.\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 {\n    use std::collections::HashMap;\n\n    pub fn get_feeds(rss_feeds: &HashMap\u003CString, String>) {\n        // Do something with the RSS feeds\n    }\n}\n```\n\n![Code Suggestions: Public module with `get_feeds()` function, and suggested input variable](https://about.gitlab.com/images/blogimages/learn-rust-with-ai-code-suggestions-advanced-programming/code_suggestions_rust_public_module_function_input.png)\n\nInside the function, continue to instruct Code Suggestions with the following steps:\n\n1. `// Iterate over the RSS feed URLs`\n2. `// Fetch URL content`\n3. `// Parse XML body`\n4. `// Print the result`\n\n![Code Suggestions: Public module with `get_feeds()` function, step 1: Iterate](https://about.gitlab.com/images/blogimages/learn-rust-with-ai-code-suggestions-advanced-programming/code_suggestions_rust_module_function_01_iterate.png)\n\n![Code Suggestions: Public module with `get_feeds()` function, step 2: Fetch URL content](https://about.gitlab.com/images/blogimages/learn-rust-with-ai-code-suggestions-advanced-programming/code_suggestions_rust_module_function_02_fetch_content.png)\n\n![Code Suggestions: Public module with `get_feeds()` function, step 3: Parse XML body](https://about.gitlab.com/images/blogimages/learn-rust-with-ai-code-suggestions-advanced-programming/code_suggestions_rust_module_function_03_parse_body.png)\n\n![Code Suggestions: Public module with `get_feeds()` function, step 4: Print the results](https://about.gitlab.com/images/blogimages/learn-rust-with-ai-code-suggestions-advanced-programming/code_suggestions_rust_module_function_04_print_result.png)\n\nThe following code can be suggested:\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 {\n    use std::collections::HashMap;\n\n    pub fn get_feeds(rss_feeds: &HashMap\u003CString, String>) {\n        // Iterate over the RSS feed URLs\n        for (name, url) in rss_feeds {\n            println!(\"{}: {}\", name, url);\n\n            // Fetch URL content\n            let body = reqwest::blocking::get(url).unwrap().text().unwrap();\n\n            // Parse XML body\n            let parsed_body = roxmltree::Document::parse(&body).unwrap();\n\n            // Print the result\n            println!(\"{:#?}\", parsed_body);\n        }\n    }\n}\n```\n\nYou see a new keyword here: [`unwrap()`](https://doc.rust-lang.org/rust-by-example/error/option_unwrap.html). Rust does not support `null` values, and uses the [`Option` type](https://doc.rust-lang.org/rust-by-example/std/option.html) for any value. If you are certain to use a specific wrapped type, for example, `Text` or `String`, you can call the `unwrap()` method to get the value. The `unwrap()` method will panic if the value is `None`.\n\n**Note** Code Suggestions referred to the `reqwest::blocking::get` function for the `// Fetch URL content` comment instruction. The [`reqwest` crate](https://docs.rs/reqwest/latest/reqwest/) name is intentional and not a typo. It provides a convenient, higher-level HTTP client for async and blocking requests.\n\nParsing the XML body is tricky - you might get different results, and the schema is not the same for every RSS feed URL. Let us try to call the `get_feeds()` function, and then work on improving the code.\n\n### Call the module function in main()\n\nThe main() function does not know about the `get_feeds()` function yet, so we need to import its module. In other programming languages, you might have seen the keywords `include` or `import`. The Rust module system is different.\n\nModules are organized in path directories. In our example, both source files exist on the same directory level. `feed_reader.rs` is interpreted as crate, containing one module called `feed_reader`, which defines the function `get_feeds()`.\n\n```text\nsrc/\n  main.rs\n  feed_reader.rs\n\n```\n\nIn order to access `get_feeds()` from the `feed_reader.rs` file, we need to [bring module path](https://doc.rust-lang.org/book/ch07-04-bringing-paths-into-scope-with-the-use-keyword.html) into the `main.rs` scope first, and then call the full function path.\n\n```rust\nmod feed_reader;\n\nfn main() {\n\n    feed_reader::feed_reader::get_feeds(&rss_feeds);\n\n```\n\nAlternatively, we can import the full function path with the `use` keyword, and later use the short function name.\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**Tip:** I highly recommend reading the [Clear explanation of the Rust module system blog post](https://www.sheshbabu.com/posts/rust-module-system/) to get a better visual understanding.\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() {\n    // Define RSS feed URLs in the variable rss_feeds\n    // Use a HashMap\n    // Add Hacker News and TechCrunch\n    // Ensure to use String as type\n    let rss_feeds = HashMap::from([\n        (\"Hacker News\".to_string(), \"https://news.ycombinator.com/rss\".to_string()),\n        (\"TechCrunch\".to_string(), \"https://techcrunch.com/feed/\".to_string()),\n    ]);\n\n    // Call get_feeds() from feed_reader module\n    feed_reader::feed_reader::get_feeds(&rss_feeds);\n    // Alternative: Imported full path, use short path here.\n    //get_feeds(&rss_feeds);\n}\n```\n\nRun `cargo build` in the terminal again to build the code.\n\n```shell\ncargo build\n```\n\nPotential build errors when Code Suggestions refer to common code and libraries for HTTP requests, and XML parsing:\n\n1. Error: `could not find blocking in reqwest`. Solution: Enable the `blocking` feature for the crate in `Config.toml`: `reqwest = { version = \"0.11.20\", features = [\"blocking\"] }`.\n2. Error: `failed to resolve: use of undeclared crate or module reqwest`. Solution: Add the `reqwest` crate.\n3. Error: `failed to resolve: use of undeclared crate or module roxmltree`. Solution: Add the `roxmltree` crate.\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**Tip:** Copy the error message string, with a leading `Rust \u003Cerror message>` into your preferred browser to check whether a missing crate is available. Usually this search leads to a result on crates.io and you can add the missing dependencies.\n\nWhen the build is successful, run the code with `cargo run` and inspect the Hacker News RSS feed output.\n\n![VS Code terminal, cargo run to fetch Hacker News XML feed](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\nWhat is next with parsing the XML body into human-readable format? In the next section, we will learn about existing solutions and how Rust crates come into play.\n\n## Crates\nRSS feeds share a common set of protocols and specifications. It feels like reinventing the wheel to parse XML items and understand the lower object structure. Recommendation for these types of tasks: Look whether someone else had the same problem already and might have created code to solve the problem.\n\nReusable library code in Rust is organized in so-called [`Crates`](https://doc.rust-lang.org/rust-by-example/crates.html), and made available in packages, and the package registry on crates.io. You can add these dependencies to your project by editing the `Config.toml` in the `[dependencies]` section, or using `cargo add \u003Cname>`.\n\nFor the reader app, we want to use the [feed-rs crate](https://crates.io/crates/feed-rs). Open a new terminal, and run the following command:\n\n```shell\ncargo add feed-rs\n```\n\n![VS Code Terminal Terminal: Add crate, verify in 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: parse XML feed\nNavigate into `src/feed_reader.rs` and modify the part where we parse the XML body. Code Suggestions understands how to call the `feed-rs` crate `parser::parse` function -- there is only one specialty here: `feed-rs` [expects string input as raw bytes](https://docs.rs/feed-rs/latest/feed_rs/parser/fn.parse_with_uri.html) to determine the encoding itself. We can provide instructions in the comment to get the expected result though.\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![Code Suggestions: Public module with `get_feeds()` function, step 5: Modify XML parser to 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\nThe benefit of using `feed-rs` is not immediately visible until you see the printed output with `cargo run`: All keys and values are mapped to their respective Rust object types, and can be used for further operations.\n\n![VS Code terminal, cargo run to fetch Hacker News XML feed](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\n## Runtime configuration: Program arguments\nUntil now, we have run the program with hard-coded RSS feed values compiled into the binary. The next step is allowing to configure the RSS feeds at runtime.\n\nRust provides [program arguments](https://doc.rust-lang.org/rust-by-example/std_misc/arg.html) in the standard misc library. [Parsing the arguments](https://doc.rust-lang.org/rust-by-example/std_misc/arg/matching.html) provides a better and faster learning experience than aiming for advanced program argument parsers (for example, the [clap](https://docs.rs/clap/latest/clap/) crate), or moving the program parameters into a configuration file and format ([TOML](https://toml.io/en/), YAML). You are reading these lines after I tried and failed with different routes for the best learning experience. This should not stop you from taking the challenge to configure RSS feeds in alternative ways.\n\nAs a boring solution, the command parameters can be passed as `\"name,url\"` string value pairs, and then are split by the `,` character to extract the name and URL values. The comment instructs Code Suggestions to perform these operations and extend the `rss_feeds` HashMap with the new values. Note that the variable might not be mutable, and, therefore, needs to be modified to `let mut rss_feeds`.\n\nNavigate into `src/main.rs` and add the following code to the `main()` function after the `rss_feeds` variable. Start with a comment to define the program arguments, and check the suggested code snippets.\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![Code suggestions for program arguments, and splitting name,URL values for the rss_feeds variable](https://about.gitlab.com/images/blogimages/learn-rust-with-ai-code-suggestions-advanced-programming/code_suggestions_rust_program_args_boring_solution.png)\n\nThe full code example can look like the following:\n\n```rust\nfn main() {\n    // Define RSS feed URLs in the variable rss_feeds\n    // Use a HashMap\n    // Add Hacker News and TechCrunch\n    // Ensure to use String as type\n    let mut rss_feeds = HashMap::from([\n        (\"Hacker News\".to_string(), \"https://news.ycombinator.com/rss\".to_string()),\n        (\"TechCrunch\".to_string(), \"https://techcrunch.com/feed/\".to_string()),\n    ]);\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        rss_feeds.insert(name.to_string(), url.to_string());\n    }\n\n    // Call get_feeds() from feed_reader module\n    feed_reader::feed_reader::get_feeds(&rss_feeds);\n    // Alternative: Imported full path, use short path here.\n    //get_feeds(&rss_feeds);\n}\n```\n\nYou can pass program arguments directly to the `cargo run` command, preceding the arguments with `--`. Enclose all arguments with double quotes, put the name followed by a comma and the RSS feed URL as argument. Separate all arguments with whitespaces.\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 terminal, RSS feed output example for the GitLab blog](https://about.gitlab.com/images/blogimages/learn-rust-with-ai-code-suggestions-advanced-programming/vs_code_terminal_gitlab_blog_rss_feed_example.png)\n\n### User input error handling\nIf the provided user input does not match the program expectation, we need to [throw an error](https://doc.rust-lang.org/rust-by-example/error.html) and help the caller to fix the program arguments. For example, passing a malformed URL format should be treated as a runtime error. Instruct Code Suggestions with a code comment to throw an error if the URL is not valid.\n\n```rust\n\n    // Ensure that URL contains a valid format, otherwise throw an error\n\n```\n\nOne possible solution is to check if the `url` variable starts with `http://` or `https://`. If not, throw an error using the [panic! macro](https://doc.rust-lang.org/rust-by-example/std/panic.html). The full code example looks like the following:\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\nTest the error handling with removing a `:` in one of the URL strings. Add the `RUST_BACKTRACE=full` environment variable to get more verbose output when the `panic()` call happens.\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![VS Code Terminal with wrong URL format, panic error backtrace](https://about.gitlab.com/images/blogimages/learn-rust-with-ai-code-suggestions-advanced-programming/vs_code_terminal_url_format_error_panic_backtrace.png)\n\n## Persistence and data storage\nThe boring solution for storing the feed data is to dump the parsed body into a new file. Instruct Code Suggestions to use a pattern that includes the RSS feed name, and the current ISO date.\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\nA possible suggestion will include using the [chrono crate](https://crates.io/crates/chrono). Add it using `cargo add chrono` and then invoke `cargo build` and `cargo run` again.\n\nThe files are written into the same directory where `cargo run` was executed. If you are executing the binary direcly in the `target/debug/` directory, all files will be dumped there.\n\n![VS Code with CNCF RSS feed content file, saved on disk](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## Optimization\nThe entries in the `rss_feeds` variable are executed sequentially. Imagine having a list of 100+ URLs configured - this could take a long time to fetch and process. What if we could execute multiple fetch requests in parallel?\n\n### Asynchronous execution\nRust provides [threads](https://doc.rust-lang.org/book/ch16-01-threads.html) for asynchronous execution.\n\nThe simplest solution will be spawning a thread for each RSS feed URL. We will discuss optimization strategies later. Before you continue with parallel execution, measure the sequential code execution time by preceding the `time` command with `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\nNote that this exercise could require more manual code work. It is recommended to persist the sequential working state in a new Git commit and branch `sequential-exec`, to better compare the impact of parallel execution.\n\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### Spawning threads\nOpen `src/feed_reader.rs` and refactor the `get_feeds()` function. Start with a Git commit for the current state, and then delete the contents of the function scope. Add the following code comments with instructions for Code Suggestions:\n\n1. `// Store threads in vector`: Store thread handles in a vector, so we can wait for them to finish at the end of the function call.\n2. `// Loop over rss_feeds and spawn threads`: Create boilerplate code for iterating over all RSS feeds, and spawn a new thread.\n\nAdd the following `use` statements to work with the `thread` and `time` modules.\n\n```rust\n\n    use std::thread;\n    use std::time::Duration;\n\n```\n\nContinue writing the code, and close the for loop. Code Suggestions will automatically propose adding the thread handle in the `threads` vector variable, and offer to join the threads at the end of the function.\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\nAdd the `thread` crate, build and run the code again.\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\nAt this stage, no data is processed or printed. Before we continue re-adding the functionality, let us learn about the newly introduced keywords here.\n\n### Function scopes, threads, and closures\nThe suggested code brings new keywords and design patterns to learn. The thread handle is of the type `thread::JoinHandle`, indicating that we can use it to wait for the threads to finish ([join()](https://doc.rust-lang.org/book/ch16-01-threads.html#waiting-for-all-threads-to-finish-using-join-handles)).\n\n`thread::spawn()` spawns a new thread, where we can pass a function object. In this case, a [closure](https://doc.rust-lang.org/book/ch13-01-closures.html) expression is passed as anonymous function. Closure inputs are passed using the `||` syntax. You will recognize the [`move` Closure](https://doc.rust-lang.org/book/ch16-01-threads.html#using-move-closures-with-threads), which moves the function scoped variables into the thread scope. This avoids manually specifying which variables need to be passed into the new function/closure scope.\n\nThere is a limitation though: `rss_feeds` is a reference `&`, passed as parameter by the `get_feeds()` function caller. The variable is only valid in the function scope. Use the following code snippet to provoke this error:\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 (key, value) in rss_feeds {\n        let thread = thread::spawn(move || {\n            println!(\"{}\", key);\n        });\n    }\n}\n```\n\n![VS Code Terminal, variable scope error with references and thread move closure](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\nAlthough the `key` variable was created in the function scope, it references the `rss_feeds` variable, and therefore, it cannot be moved into the thread scope. Any values accessed from the function parameter `rss_feeds` hash map will require a local copy with `clone()`.\n\n![VS Code Terminal, thread spawn with clone](https://about.gitlab.com/images/blogimages/learn-rust-with-ai-code-suggestions-advanced-programming/code_suggestions_rust_thread_spawn_clone.png)\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## Parse feed XML into object types\nThe next step is to repeat the RSS feed parsing steps in the thread closure. Add the following code comments with instructions for Code Suggestions:\n\n1. `// Parse XML body with feed_rs parser, input in bytes` to tell Code Suggestions that we want to fetch the RSS feed URL content, and parse it with the `feed_rs` crate functions.\n2. `// Check feed_type attribute feed_rs::model::FeedType::RSS2 or Atom and print its name`: Extract the feed type by comparing the `feed_type` attribute with the [`feed_rs::model::FeedType`](https://docs.rs/feed-rs/latest/feed_rs/model/enum.FeedType.html). This needs more direct instructions for Code Suggestions telling it about the exact Enum values to match against.\n\n![Instruct Code Suggestions to match against specific feed types](https://about.gitlab.com/images/blogimages/learn-rust-with-ai-code-suggestions-advanced-programming/code_suggestions_feed_rs_type_condition.png)\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\nBuild and run the program again, and verify its output.\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\nLet us verify this output by opening the feed URLs in the browser, or inspecting the previously downloaded files.\n\nHacker News supports RSS version 2.0, with `channel(title,link,description,item(title,link,pubDate,comments))`. TechCrunch and the CNCF blog follow a similar structure.\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\nThe GitLab blog uses the [Atom](https://datatracker.ietf.org/doc/html/rfc4287) feed format similar to RSS, but still requires different parsing logic.\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### Map generic feed data types\nUsing [`roxmltree::Document::parse`](https://docs.rs/roxmltree/latest/roxmltree/struct.Document.html) would require us to understand the XML node tree and its specific tag names. Fortunately, [feed_rs::model::Feed](https://docs.rs/feed-rs/latest/feed_rs/model/struct.Feed.html) provides a combined model for RSS and Atom feeds, therefore let us continue using the `feed_rs` crate.\n\n1. Atom: Feed->Feed, Entry->Entry\n2. RSS: Channel->Feed, Item->Entry\n\nIn addition to the mapping above, we need to extract the required attributes, and map their data types. It is helpful to open the [feed_rs::model documentation](https://docs.rs/feed-rs/latest/feed_rs/model/index.html) to understand the structs and their fields and implementations. Otherwise, some suggestions would result in type conversion errors and compilation failures, that are specific to the `feed_rs` implementation.\n\nA [`Feed`](https://docs.rs/feed-rs/latest/feed_rs/model/struct.Feed.html) struct provides the `title`, type `Option\u003CText>` (either a value is set, or nothing). An [`Entry`](https://docs.rs/feed-rs/latest/feed_rs/model/struct.Entry.html) struct provides:\n\n1. `title`: `Option\u003CText>`with [`Text`](https://docs.rs/feed-rs/latest/feed_rs/model/struct.Text.html) and the `content` field as `String`.\n2. `updated`: `Option\u003CDateTime\u003CUtc>>` with [`DateTime`](https://docs.rs/chrono/latest/chrono/struct.DateTime.html) with the [`format()` method](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) and the `content` field as `String`.\n4. `links`: `Vec\u003CLink>`, vector with [`Link`](https://docs.rs/feed-rs/latest/feed_rs/model/struct.Link.html) items. The `href` attribute provides the raw URL string.\n\nUse this knowledge to extract the required data from the feed entries. Reminder that all `Option` types need to call `unwrap()`, which requires more raw instructions for Code Suggestions.\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![Code suggestions to print feed entry types, with specific requirements](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\n### Error handling with Option unwrap()\nContinue iterating on the multi-line instructions after building and running the program again. Spoiler: `unwrap()` will call the `panic!` macro and crash the program when it encounters empty values. This can happen if a field like `summary` is not set in the feed data.\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\nA potential solution is to use [`std::Option::unwrap_or_else`](https://doc.rust-lang.org/std/option/enum.Option.html#method.unwrap_or_else) and set an empty string as default value. The syntax requires a closure that returns an empty `Text` struct instantiation.\n\nSolving the problem required many attempts to find the correct initialization, passing just an empty string did not work with the custom types. I will show you all my endeavors, including the research paths.\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\nThis approach did not feel satisfying, since the code line is complicated to read, and required manual work without help from Code Suggestions. Taking a step back, I reviewed what brought me there - if `Option` is `none`, `unwrap()` will throw an error. Maybe there is an easier way to handle this? I asked Code Suggestions in a new comment:\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![Code suggestions asked for alternative with 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\nIncreased readability, less CPU cycles wasted on `unwrap()`, and a great learning curve from solving a complex problem to using a boring solution. Win-win.\n\nBefore we forget: Re-add storing the XML data on disk to complete the reader app again.\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\nBuild and run the program to verify the output.\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 Terminal, cargo run with formatted feed entries output](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## Benchmarks\n\n### Sequential vs. Parallel execution benchmark\nCompare the execution time benchmarks by creating five samples each.\n\n1. Sequential execution. [Example source code MR](https://gitlab.com/gitlab-da/use-cases/ai/learn-with-ai/learn-rust-ai-app-reader/-/merge_requests/1)\n2. Parallel exeuction. [Example source code MR](https://gitlab.com/gitlab-da/use-cases/ai/learn-with-ai/learn-rust-ai-app-reader/-/merge_requests/3)\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\nThe CPU usage increased for parallel execution of four RSS feed threads, but it nearly halved the total time compared to sequential execution. With that in mind, we can continue learning Rust and optimize the code and functionality.\n\nNote that we are running the debug build through Cargo, and not the optimized released builds yet. There are caveats with parallel execution though: Some HTTP endpoints put rate limits in place, where parallelism could hit these thresholds easier.\n\nThe system executing multiple threads in parallel might get overloaded too – threads require context switching in the Kernel, assigning resources to each thread. While one thread gets computing resources, other threads are put to sleep. If there are too many threads spawned, this might slow down the system, rather than speeding up the operations. Solutions include design patterns such as [work queues](https://docs.rs/work-queue/latest/work_queue/), where the caller adds a task into a queue, and a defined number of worker threads pick up the tasks for asynchronous execution.\n\nRust also provides data synchronisation between threads, so-called [channels](https://doc.rust-lang.org/rust-by-example/std_misc/channels.html). To ensure concurrent data access, [mutexes](https://doc.rust-lang.org/std/sync/struct.Mutex.html) are available to provide safe locks.\n\n### CI/CD with Rust caching\nAdd the following CI/CD configuration into the `.gitlab-ci.yml` file. The `run-latest` job calls `cargo run` with RSS feed URL examples, and measures the execution time continuously.\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![GitLab CI/CD pipelines for Rust, cargo run output](https://about.gitlab.com/images/blogimages/learn-rust-with-ai-code-suggestions-advanced-programming/gitlab_cicd_pipeline_rust_cargo_run_output.png)\n\n## What is next\nThis blog post was challenging to create, with both learning advanced Rust programming techniques myself, and finding a good learning curve with Code Suggestions. The latter greatly helps with quickly generating code, not just boilerplate snippets – it understands the local context, and better understands the purpose and scope of the algorithm, the more code you write. After reading this blog post, you know of a few challenges and turnarounds. The example solution code for the reader app is available in [the learn-rust-ai-app-reader project](https://gitlab.com/gitlab-da/use-cases/ai/learn-with-ai/learn-rust-ai-app-reader).\n\nParsing RSS feeds is challenging since it involves data structures, with external HTTP requests and parallel optimizations. As an experienced Rust user, you might have wondered: `Why not use the std::rss crate?` -- It is optimized for advanced asynchronous execution, and does not allow to show and explain the different Rust functionalities, explained in this blog post. As an async exercise, try to rewrite the code using the [`rss` crate](https://docs.rs/rss/latest/rss/).\n\n### Async learning exercises\nThe lessons learned in this blog post also lay the foundation for future exploration with persistent storage and presenting the data. Here are a few ideas where you can continue learning Rust and optimize the reader app:\n\n1. Data storage: Use a database like sqlite, and RSS feed update tracking.\n2. Notifications: Spawn child processes to trigger notifications into Telegram, etc.\n3. Functionality: Extend the reader types to REST APIs\n4. Configuration: Add support for configuration files for RSS feeds, APIs, etc.\n5. Efficiency: Add support for filters, and subscribed tags.\n6. Deployments: Use a webserver, collect Prometheus metrics, and deploy to Kubernetes.\n\nIn a future blog post, we will discuss some of these ideas, and how to implement them. Dive into existing RSS feed implementations, and learn how you can refactor the existing code into leveraging more Rust libraries (`crates`).\n\n### Share your feedback\nWhen you use [GitLab Duo](/gitlab-duo-agent-platform/) Code Suggestions, please [share your thoughts in the feedback issue](https://gitlab.com/gitlab-org/gitlab/-/issues/405152).\n",[23,24,25,26,27],"DevSecOps","careers","tutorial","workflow","AI/ML","yml",{},true,"/en-us/blog/learn-advanced-rust-programming-with-a-little-help-from-ai-code-suggestions",{"title":15,"description":16,"ogTitle":15,"ogDescription":16,"noIndex":12,"ogImage":19,"ogUrl":33,"ogSiteName":34,"ogType":35,"canonicalUrls":33},"https://about.gitlab.com/blog/learn-advanced-rust-programming-with-a-little-help-from-ai-code-suggestions","https://about.gitlab.com","article","en-us/blog/learn-advanced-rust-programming-with-a-little-help-from-ai-code-suggestions",[38,24,25,26,39],"devsecops","aiml","dPD16ZBrRt2ajARwJk_X2_a58Hrdib9JRof_Fg4dpFQ",{"data":42},{"logo":43,"freeTrial":48,"sales":53,"login":58,"items":63,"search":371,"minimal":402,"duo":421,"switchNav":430,"pricingDeployment":441},{"config":44},{"href":45,"dataGaName":46,"dataGaLocation":47},"/","gitlab logo","header",{"text":49,"config":50},"Get free trial",{"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},"Talk to sales",{"href":56,"dataGaName":57,"dataGaLocation":47},"/sales/","sales",{"text":59,"config":60},"Sign in",{"href":61,"dataGaName":62,"dataGaLocation":47},"https://gitlab.com/users/sign_in/","sign in",[64,91,186,191,292,352],{"text":65,"config":66,"cards":68},"Platform",{"dataNavLevelOne":67},"platform",[69,75,83],{"title":65,"description":70,"link":71},"The intelligent orchestration platform for DevSecOps",{"text":72,"config":73},"Explore our Platform",{"href":74,"dataGaName":67,"dataGaLocation":47},"/platform/",{"title":76,"description":77,"link":78},"GitLab Duo Agent Platform","Agentic AI for the entire software lifecycle",{"text":79,"config":80},"Meet GitLab Duo",{"href":81,"dataGaName":82,"dataGaLocation":47},"/gitlab-duo-agent-platform/","gitlab duo agent platform",{"title":84,"description":85,"link":86},"Why GitLab","See the top reasons enterprises choose GitLab",{"text":87,"config":88},"Learn more",{"href":89,"dataGaName":90,"dataGaLocation":47},"/why-gitlab/","why gitlab",{"text":92,"left":30,"config":93,"link":95,"lists":99,"footer":168},"Product",{"dataNavLevelOne":94},"solutions",{"text":96,"config":97},"View all Solutions",{"href":98,"dataGaName":94,"dataGaLocation":47},"/solutions/",[100,124,147],{"title":101,"description":102,"link":103,"items":108},"Automation","CI/CD and automation to accelerate deployment",{"config":104},{"icon":105,"href":106,"dataGaName":107,"dataGaLocation":47},"AutomatedCodeAlt","/solutions/delivery-automation/","automated software delivery",[109,113,116,120],{"text":110,"config":111},"CI/CD",{"href":112,"dataGaLocation":47,"dataGaName":110},"/solutions/continuous-integration/",{"text":76,"config":114},{"href":81,"dataGaLocation":47,"dataGaName":115},"gitlab duo agent platform - product menu",{"text":117,"config":118},"Source Code Management",{"href":119,"dataGaLocation":47,"dataGaName":117},"/solutions/source-code-management/",{"text":121,"config":122},"Automated Software Delivery",{"href":106,"dataGaLocation":47,"dataGaName":123},"Automated software delivery",{"title":125,"description":126,"link":127,"items":132},"Security","Deliver code faster without compromising security",{"config":128},{"href":129,"dataGaName":130,"dataGaLocation":47,"icon":131},"/solutions/application-security-testing/","security and compliance","ShieldCheckLight",[133,137,142],{"text":134,"config":135},"Application Security Testing",{"href":129,"dataGaName":136,"dataGaLocation":47},"Application security testing",{"text":138,"config":139},"Software Supply Chain Security",{"href":140,"dataGaLocation":47,"dataGaName":141},"/solutions/supply-chain/","Software supply chain security",{"text":143,"config":144},"Software Compliance",{"href":145,"dataGaName":146,"dataGaLocation":47},"/solutions/software-compliance/","software compliance",{"title":148,"link":149,"items":154},"Measurement",{"config":150},{"icon":151,"href":152,"dataGaName":153,"dataGaLocation":47},"DigitalTransformation","/solutions/visibility-measurement/","visibility and measurement",[155,159,163],{"text":156,"config":157},"Visibility & Measurement",{"href":152,"dataGaLocation":47,"dataGaName":158},"Visibility and Measurement",{"text":160,"config":161},"Value Stream Management",{"href":162,"dataGaLocation":47,"dataGaName":160},"/solutions/value-stream-management/",{"text":164,"config":165},"Analytics & Insights",{"href":166,"dataGaLocation":47,"dataGaName":167},"/solutions/analytics-and-insights/","Analytics and insights",{"title":169,"items":170},"GitLab for",[171,176,181],{"text":172,"config":173},"Enterprise",{"href":174,"dataGaLocation":47,"dataGaName":175},"/enterprise/","enterprise",{"text":177,"config":178},"Small Business",{"href":179,"dataGaLocation":47,"dataGaName":180},"/small-business/","small business",{"text":182,"config":183},"Public Sector",{"href":184,"dataGaLocation":47,"dataGaName":185},"/solutions/public-sector/","public sector",{"text":187,"config":188},"Pricing",{"href":189,"dataGaName":190,"dataGaLocation":47,"dataNavLevelOne":190},"/pricing/","pricing",{"text":192,"config":193,"link":195,"lists":199,"feature":279},"Resources",{"dataNavLevelOne":194},"resources",{"text":196,"config":197},"View all resources",{"href":198,"dataGaName":194,"dataGaLocation":47},"/resources/",[200,233,251],{"title":201,"items":202},"Getting started",[203,208,213,218,223,228],{"text":204,"config":205},"Install",{"href":206,"dataGaName":207,"dataGaLocation":47},"/install/","install",{"text":209,"config":210},"Quick start guides",{"href":211,"dataGaName":212,"dataGaLocation":47},"/get-started/","quick setup checklists",{"text":214,"config":215},"Learn",{"href":216,"dataGaLocation":47,"dataGaName":217},"https://university.gitlab.com/","learn",{"text":219,"config":220},"Product documentation",{"href":221,"dataGaName":222,"dataGaLocation":47},"https://docs.gitlab.com/","product documentation",{"text":224,"config":225},"Best practice videos",{"href":226,"dataGaName":227,"dataGaLocation":47},"/getting-started-videos/","best practice videos",{"text":229,"config":230},"Integrations",{"href":231,"dataGaName":232,"dataGaLocation":47},"/integrations/","integrations",{"title":234,"items":235},"Discover",[236,241,246],{"text":237,"config":238},"Customer success stories",{"href":239,"dataGaName":240,"dataGaLocation":47},"/customers/","customer success stories",{"text":242,"config":243},"Blog",{"href":244,"dataGaName":245,"dataGaLocation":47},"/blog/","blog",{"text":247,"config":248},"Remote",{"href":249,"dataGaName":250,"dataGaLocation":47},"https://handbook.gitlab.com/handbook/company/culture/all-remote/","remote",{"title":252,"items":253},"Connect",[254,259,264,269,274],{"text":255,"config":256},"GitLab Services",{"href":257,"dataGaName":258,"dataGaLocation":47},"/services/","services",{"text":260,"config":261},"Community",{"href":262,"dataGaName":263,"dataGaLocation":47},"/community/","community",{"text":265,"config":266},"Forum",{"href":267,"dataGaName":268,"dataGaLocation":47},"https://forum.gitlab.com/","forum",{"text":270,"config":271},"Events",{"href":272,"dataGaName":273,"dataGaLocation":47},"/events/","events",{"text":275,"config":276},"Partners",{"href":277,"dataGaName":278,"dataGaLocation":47},"/partners/","partners",{"backgroundColor":280,"textColor":281,"text":282,"image":283,"link":287},"#2f2a6b","#fff","Insights for the future of software development",{"altText":284,"config":285},"the source promo card",{"src":286},"https://res.cloudinary.com/about-gitlab-com/image/upload/v1758208064/dzl0dbift9xdizyelkk4.svg",{"text":288,"config":289},"Read the latest",{"href":290,"dataGaName":291,"dataGaLocation":47},"/the-source/","the source",{"text":293,"config":294,"lists":296},"Company",{"dataNavLevelOne":295},"company",[297],{"items":298},[299,304,310,312,317,322,327,332,337,342,347],{"text":300,"config":301},"About",{"href":302,"dataGaName":303,"dataGaLocation":47},"/company/","about",{"text":305,"config":306,"footerGa":309},"Jobs",{"href":307,"dataGaName":308,"dataGaLocation":47},"/jobs/","jobs",{"dataGaName":308},{"text":270,"config":311},{"href":272,"dataGaName":273,"dataGaLocation":47},{"text":313,"config":314},"Leadership",{"href":315,"dataGaName":316,"dataGaLocation":47},"/company/team/e-group/","leadership",{"text":318,"config":319},"Team",{"href":320,"dataGaName":321,"dataGaLocation":47},"/company/team/","team",{"text":323,"config":324},"Handbook",{"href":325,"dataGaName":326,"dataGaLocation":47},"https://handbook.gitlab.com/","handbook",{"text":328,"config":329},"Investor relations",{"href":330,"dataGaName":331,"dataGaLocation":47},"https://ir.gitlab.com/","investor relations",{"text":333,"config":334},"Trust Center",{"href":335,"dataGaName":336,"dataGaLocation":47},"/security/","trust center",{"text":338,"config":339},"AI Transparency Center",{"href":340,"dataGaName":341,"dataGaLocation":47},"/ai-transparency-center/","ai transparency center",{"text":343,"config":344},"Newsletter",{"href":345,"dataGaName":346,"dataGaLocation":47},"/company/contact/#contact-forms","newsletter",{"text":348,"config":349},"Press",{"href":350,"dataGaName":351,"dataGaLocation":47},"/press/","press",{"text":353,"config":354,"lists":355},"Contact us",{"dataNavLevelOne":295},[356],{"items":357},[358,361,366],{"text":54,"config":359},{"href":56,"dataGaName":360,"dataGaLocation":47},"talk to sales",{"text":362,"config":363},"Support portal",{"href":364,"dataGaName":365,"dataGaLocation":47},"https://support.gitlab.com","support portal",{"text":367,"config":368},"Customer portal",{"href":369,"dataGaName":370,"dataGaLocation":47},"https://customers.gitlab.com/customers/sign_in/","customer portal",{"close":372,"login":373,"suggestions":380},"Close",{"text":374,"link":375},"To search repositories and projects, login to",{"text":376,"config":377},"gitlab.com",{"href":61,"dataGaName":378,"dataGaLocation":379},"search login","search",{"text":381,"default":382},"Suggestions",[383,385,389,391,395,399],{"text":76,"config":384},{"href":81,"dataGaName":76,"dataGaLocation":379},{"text":386,"config":387},"Code Suggestions (AI)",{"href":388,"dataGaName":386,"dataGaLocation":379},"/solutions/code-suggestions/",{"text":110,"config":390},{"href":112,"dataGaName":110,"dataGaLocation":379},{"text":392,"config":393},"GitLab on AWS",{"href":394,"dataGaName":392,"dataGaLocation":379},"/partners/technology-partners/aws/",{"text":396,"config":397},"GitLab on Google Cloud",{"href":398,"dataGaName":396,"dataGaLocation":379},"/partners/technology-partners/google-cloud-platform/",{"text":400,"config":401},"Why GitLab?",{"href":89,"dataGaName":400,"dataGaLocation":379},{"freeTrial":403,"mobileIcon":408,"desktopIcon":413,"secondaryButton":416},{"text":404,"config":405},"Start free trial",{"href":406,"dataGaName":52,"dataGaLocation":407},"https://gitlab.com/-/trials/new/","nav",{"altText":409,"config":410},"Gitlab Icon",{"src":411,"dataGaName":412,"dataGaLocation":407},"https://res.cloudinary.com/about-gitlab-com/image/upload/v1758203874/jypbw1jx72aexsoohd7x.svg","gitlab icon",{"altText":409,"config":414},{"src":415,"dataGaName":412,"dataGaLocation":407},"https://res.cloudinary.com/about-gitlab-com/image/upload/v1758203875/gs4c8p8opsgvflgkswz9.svg",{"text":417,"config":418},"Get Started",{"href":419,"dataGaName":420,"dataGaLocation":407},"https://gitlab.com/-/trial_registrations/new?glm_source=about.gitlab.com/get-started/","get started",{"freeTrial":422,"mobileIcon":426,"desktopIcon":428},{"text":423,"config":424},"Learn more about GitLab Duo",{"href":81,"dataGaName":425,"dataGaLocation":407},"gitlab duo",{"altText":409,"config":427},{"src":411,"dataGaName":412,"dataGaLocation":407},{"altText":409,"config":429},{"src":415,"dataGaName":412,"dataGaLocation":407},{"button":431,"mobileIcon":436,"desktopIcon":438},{"text":432,"config":433},"/switch",{"href":434,"dataGaName":435,"dataGaLocation":407},"#contact","switch",{"altText":409,"config":437},{"src":411,"dataGaName":412,"dataGaLocation":407},{"altText":409,"config":439},{"src":440,"dataGaName":412,"dataGaLocation":407},"https://res.cloudinary.com/about-gitlab-com/image/upload/v1773335277/ohhpiuoxoldryzrnhfrh.png",{"freeTrial":442,"mobileIcon":447,"desktopIcon":449},{"text":443,"config":444},"Back to pricing",{"href":189,"dataGaName":445,"dataGaLocation":407,"icon":446},"back to pricing","GoBack",{"altText":409,"config":448},{"src":411,"dataGaName":412,"dataGaLocation":407},{"altText":409,"config":450},{"src":415,"dataGaName":412,"dataGaLocation":407},{"title":452,"button":453,"config":458},"See how agentic AI transforms software delivery",{"text":454,"config":455},"Watch GitLab Transcend now",{"href":456,"dataGaName":457,"dataGaLocation":47},"/events/transcend/virtual/","transcend event",{"layout":459,"icon":460,"disabled":30},"release","AiStar",{"data":462},{"text":463,"source":464,"edit":470,"contribute":475,"config":480,"items":485,"minimal":690},"Git is a trademark of Software Freedom Conservancy and our use of 'GitLab' is under license",{"text":465,"config":466},"View page source",{"href":467,"dataGaName":468,"dataGaLocation":469},"https://gitlab.com/gitlab-com/marketing/digital-experience/about-gitlab-com/","page source","footer",{"text":471,"config":472},"Edit this page",{"href":473,"dataGaName":474,"dataGaLocation":469},"https://gitlab.com/gitlab-com/marketing/digital-experience/about-gitlab-com/-/blob/main/content/","web ide",{"text":476,"config":477},"Please contribute",{"href":478,"dataGaName":479,"dataGaLocation":469},"https://gitlab.com/gitlab-com/marketing/digital-experience/about-gitlab-com/-/blob/main/CONTRIBUTING.md/","please contribute",{"twitter":481,"facebook":482,"youtube":483,"linkedin":484},"https://twitter.com/gitlab","https://www.facebook.com/gitlab","https://www.youtube.com/channel/UCnMGQ8QHMAnVIsI3xJrihhg","https://www.linkedin.com/company/gitlab-com",[486,533,585,629,656],{"title":187,"links":487,"subMenu":502},[488,492,497],{"text":489,"config":490},"View plans",{"href":189,"dataGaName":491,"dataGaLocation":469},"view plans",{"text":493,"config":494},"Why Premium?",{"href":495,"dataGaName":496,"dataGaLocation":469},"/pricing/premium/","why premium",{"text":498,"config":499},"Why Ultimate?",{"href":500,"dataGaName":501,"dataGaLocation":469},"/pricing/ultimate/","why ultimate",[503],{"title":504,"links":505},"Contact Us",[506,509,511,513,518,523,528],{"text":507,"config":508},"Contact sales",{"href":56,"dataGaName":57,"dataGaLocation":469},{"text":362,"config":510},{"href":364,"dataGaName":365,"dataGaLocation":469},{"text":367,"config":512},{"href":369,"dataGaName":370,"dataGaLocation":469},{"text":514,"config":515},"Status",{"href":516,"dataGaName":517,"dataGaLocation":469},"https://status.gitlab.com/","status",{"text":519,"config":520},"Terms of use",{"href":521,"dataGaName":522,"dataGaLocation":469},"/terms/","terms of use",{"text":524,"config":525},"Privacy statement",{"href":526,"dataGaName":527,"dataGaLocation":469},"/privacy/","privacy statement",{"text":529,"config":530},"Cookie preferences",{"dataGaName":531,"dataGaLocation":469,"id":532,"isOneTrustButton":30},"cookie preferences","ot-sdk-btn",{"title":92,"links":534,"subMenu":543},[535,539],{"text":536,"config":537},"DevSecOps platform",{"href":74,"dataGaName":538,"dataGaLocation":469},"devsecops platform",{"text":540,"config":541},"AI-Assisted Development",{"href":81,"dataGaName":542,"dataGaLocation":469},"ai-assisted development",[544],{"title":545,"links":546},"Topics",[547,552,557,562,567,570,575,580],{"text":548,"config":549},"CICD",{"href":550,"dataGaName":551,"dataGaLocation":469},"/topics/ci-cd/","cicd",{"text":553,"config":554},"GitOps",{"href":555,"dataGaName":556,"dataGaLocation":469},"/topics/gitops/","gitops",{"text":558,"config":559},"DevOps",{"href":560,"dataGaName":561,"dataGaLocation":469},"/topics/devops/","devops",{"text":563,"config":564},"Version Control",{"href":565,"dataGaName":566,"dataGaLocation":469},"/topics/version-control/","version control",{"text":23,"config":568},{"href":569,"dataGaName":38,"dataGaLocation":469},"/topics/devsecops/",{"text":571,"config":572},"Cloud Native",{"href":573,"dataGaName":574,"dataGaLocation":469},"/topics/cloud-native/","cloud native",{"text":576,"config":577},"AI for Coding",{"href":578,"dataGaName":579,"dataGaLocation":469},"/topics/devops/ai-for-coding/","ai for coding",{"text":581,"config":582},"Agentic AI",{"href":583,"dataGaName":584,"dataGaLocation":469},"/topics/agentic-ai/","agentic ai",{"title":586,"links":587},"Solutions",[588,590,592,597,601,604,608,611,613,616,619,624],{"text":134,"config":589},{"href":129,"dataGaName":134,"dataGaLocation":469},{"text":123,"config":591},{"href":106,"dataGaName":107,"dataGaLocation":469},{"text":593,"config":594},"Agile development",{"href":595,"dataGaName":596,"dataGaLocation":469},"/solutions/agile-delivery/","agile delivery",{"text":598,"config":599},"SCM",{"href":119,"dataGaName":600,"dataGaLocation":469},"source code management",{"text":548,"config":602},{"href":112,"dataGaName":603,"dataGaLocation":469},"continuous integration & delivery",{"text":605,"config":606},"Value stream management",{"href":162,"dataGaName":607,"dataGaLocation":469},"value stream management",{"text":553,"config":609},{"href":610,"dataGaName":556,"dataGaLocation":469},"/solutions/gitops/",{"text":172,"config":612},{"href":174,"dataGaName":175,"dataGaLocation":469},{"text":614,"config":615},"Small business",{"href":179,"dataGaName":180,"dataGaLocation":469},{"text":617,"config":618},"Public sector",{"href":184,"dataGaName":185,"dataGaLocation":469},{"text":620,"config":621},"Education",{"href":622,"dataGaName":623,"dataGaLocation":469},"/solutions/education/","education",{"text":625,"config":626},"Financial services",{"href":627,"dataGaName":628,"dataGaLocation":469},"/solutions/finance/","financial services",{"title":192,"links":630},[631,633,635,637,640,642,644,646,648,650,652,654],{"text":204,"config":632},{"href":206,"dataGaName":207,"dataGaLocation":469},{"text":209,"config":634},{"href":211,"dataGaName":212,"dataGaLocation":469},{"text":214,"config":636},{"href":216,"dataGaName":217,"dataGaLocation":469},{"text":219,"config":638},{"href":221,"dataGaName":639,"dataGaLocation":469},"docs",{"text":242,"config":641},{"href":244,"dataGaName":245,"dataGaLocation":469},{"text":237,"config":643},{"href":239,"dataGaName":240,"dataGaLocation":469},{"text":247,"config":645},{"href":249,"dataGaName":250,"dataGaLocation":469},{"text":255,"config":647},{"href":257,"dataGaName":258,"dataGaLocation":469},{"text":260,"config":649},{"href":262,"dataGaName":263,"dataGaLocation":469},{"text":265,"config":651},{"href":267,"dataGaName":268,"dataGaLocation":469},{"text":270,"config":653},{"href":272,"dataGaName":273,"dataGaLocation":469},{"text":275,"config":655},{"href":277,"dataGaName":278,"dataGaLocation":469},{"title":293,"links":657},[658,660,662,664,666,668,670,674,679,681,683,685],{"text":300,"config":659},{"href":302,"dataGaName":295,"dataGaLocation":469},{"text":305,"config":661},{"href":307,"dataGaName":308,"dataGaLocation":469},{"text":313,"config":663},{"href":315,"dataGaName":316,"dataGaLocation":469},{"text":318,"config":665},{"href":320,"dataGaName":321,"dataGaLocation":469},{"text":323,"config":667},{"href":325,"dataGaName":326,"dataGaLocation":469},{"text":328,"config":669},{"href":330,"dataGaName":331,"dataGaLocation":469},{"text":671,"config":672},"Sustainability",{"href":673,"dataGaName":671,"dataGaLocation":469},"/sustainability/",{"text":675,"config":676},"Diversity, inclusion and belonging (DIB)",{"href":677,"dataGaName":678,"dataGaLocation":469},"/diversity-inclusion-belonging/","Diversity, inclusion and belonging",{"text":333,"config":680},{"href":335,"dataGaName":336,"dataGaLocation":469},{"text":343,"config":682},{"href":345,"dataGaName":346,"dataGaLocation":469},{"text":348,"config":684},{"href":350,"dataGaName":351,"dataGaLocation":469},{"text":686,"config":687},"Modern Slavery Transparency Statement",{"href":688,"dataGaName":689,"dataGaLocation":469},"https://handbook.gitlab.com/handbook/legal/modern-slavery-act-transparency-statement/","modern slavery transparency statement",{"items":691},[692,695,698],{"text":693,"config":694},"Terms",{"href":521,"dataGaName":522,"dataGaLocation":469},{"text":696,"config":697},"Cookies",{"dataGaName":531,"dataGaLocation":469,"id":532,"isOneTrustButton":30},{"text":699,"config":700},"Privacy",{"href":526,"dataGaName":527,"dataGaLocation":469},[702],{"id":703,"title":18,"body":8,"config":704,"content":706,"description":8,"extension":28,"meta":710,"navigation":30,"path":711,"seo":712,"stem":713,"__hash__":714},"blogAuthors/en-us/blog/authors/michael-friedrich.yml",{"template":705},"BlogAuthor",{"name":18,"config":707},{"headshot":708,"ctfId":709},"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",[716,729,741],{"content":717,"config":727},{"title":718,"description":719,"authors":720,"heroImage":722,"date":723,"body":724,"category":9,"tags":725},"GitLab and Anthropic: Governed AI for enterprise development","GitLab deepens its Anthropic Claude integration, bringing governed AI, access to new models, and cloud flexibility to enterprise software development.",[721],"Stuart Moncada","https://res.cloudinary.com/about-gitlab-com/image/upload/v1776457632/llddiylsgwuze0u1rjks.png","2026-04-28","For enterprise and public sector leaders, the tension is familiar: Software teams need to move faster with AI, while security, compliance, and regulatory expectations only get more stringent. GitLab deepens its Anthropic Claude integration so organizations get access to newly released Claude models inside GitLab’s intelligent orchestration platform where governance, compliance, and auditability already run.\n\nClaude powers capabilities across GitLab Duo Agent Platform as the default model out of the box, across a variety of use cases from code generation and review to agentic chat and vulnerability resolution. If you've used GitLab Duo, you've already experienced how Duo agents automate workflows across the entire software development lifecycle (SDLC).\n\nThis accelerates the integration of Claude’s capabilities into GitLab, broadens how enterprises can deploy them, and reinforces what makes GitLab fundamentally different as a platform for software development and engineering: governance, compliance, and auditability built into every AI interaction.\n\n> \"GitLab Duo has accelerated how our teams plan, build, and ship software. The combination of Anthropic's Claude and GitLab's platform means we're getting more capable AI without changing how we work or how it is governed.\"\n>\n> – Mans Booijink, Operations Manager, Cube\n\n## The real differentiator: Governed AI\n\nWith GitLab, governance controls and auditing are built into the SDLC. When Claude suggests a code change through the GitLab Duo Agent Platform, that suggestion flows through the same merge request process, the same approval rules, the same security scanning, and the same audit trail as every other change. AI doesn't get a shortcut around your controls. It operates within them.\n\nAs GitLab moves deeper into agentic software development, where AI autonomously handles well-defined tasks, the governance layer becomes more important. An AI agent that can open a merge request, help resolve a vulnerability, or refactor a service needs to be auditable, attributable, and subject to the same policy enforcement as a human developer. That requirement is an architectural decision GitLab made from the start, and one that grows more consequential as AI agents take on broader responsibilities.\n\n## Enterprise deployment flexibility\n\nThis also expands how organizations access the latest Claude models through GitLab. Claude is available within GitLab through Google Cloud's Vertex AI and Amazon Bedrock, which means enterprises can route AI workloads through the hyperscaler commitments and cloud governance frameworks they already have in place. No separate vendor contract. No new data residency questions. Your existing Google Cloud or AWS relationship is the on-ramp. \n\nGitLab is now also available in the [Claude Marketplace](https://claude.com/platform/marketplace), allowing customers to purchase GitLab Credits and apply them toward existing Anthropic spending commitments – consolidating AI spend and simplifying how teams discover and procure GitLab alongside their Anthropic investments.\n\n## Advancing an agentic future\n\nGitLab's vision for agentic software development, where AI handles defined tasks autonomously across planning, coding, testing, securing, and deploying, requires models with strong reasoning, reliability, and safety characteristics. It also requires a platform where those autonomous actions are fully governed.\n\nAgentic workflows demand models with strong reasoning, reliability, and safety characteristics, criteria that guide how GitLab selects and integrates AI model partners. And GitLab's governance framework helps ensure that as AI agents assume more advanced development work, enterprises maintain full visibility and control over what those agents do, when they do it, and how changes are tracked.\n\n## What this means for GitLab customers\n\nIf you're already using GitLab Duo Agent Platform, you'll get access to Claude models and deeper AI assistance across your software development lifecycle, all within the governance framework you already rely on.\n\nIf you're evaluating AI-powered software development platforms, you shouldn't have to choose between advanced AI capabilities and enterprise control. This strategic integration is built to deliver both.\n\n> Want to learn more about GitLab Duo Agent Platform? [Get a demo or start a free trial today](https://about.gitlab.com/gitlab-duo-agent-platform/).",[27,726,278],"product",{"featured":30,"template":13,"slug":728},"gitlab-and-anthropic-governed-ai-for-enterprise-development",{"content":730,"config":739},{"title":731,"description":732,"authors":733,"heroImage":735,"date":736,"body":737,"category":9,"tags":738},"Give your AI agent direct, structured GitLab access with glab CLI","The GitLab CLI (glab) provides AI agents structured, reliable access to projects via the MCP, eliminating friction. This tutorial shows how you can speed up code review and issue triage.",[734],"Kai Armstrong","https://res.cloudinary.com/about-gitlab-com/image/upload/v1776347152/unw3mzatkd5xyfbzcnni.png","2026-04-27","\nWhen teams use GitLab Duo, Claude, Cursor, and other AI assistants, more of the development workflow runs through an AI agent acting on your behalf — reading issues, reviewing merge requests, running pipelines, and helping you ship faster. Most developers are already using the GitLab CLI (`glab`) from the terminal to interact with GitLab. Combining the two is a natural next step.\n\n\nThe problem is that without the right tools, AI agents are essentially guessing when it comes to your GitLab projects. They might hallucinate the details of an issue they've never seen, summarize a merge request based on stale training data rather than its actual state, or require you to manually copy context from a browser tab and paste it into a chat window just to get started. Every one of those workarounds is friction: it slows you down, introduces the possibility of error, and puts a hard ceiling on what your agent can actually do on your behalf. `glab` changes that by giving agents a direct, reliable interface to your projects.\n\n\nWith `glab`, your agent fetches what it needs directly from GitLab, acts on it, and reports back — so you spend less time relaying information and more time on the work that matters.\n\n\nIn this tutorial, you'll learn how to use `glab` to give AI agents structured, reliable access to your GitLab projects. You'll also discover how that unlocks a faster, more capable development workflow.\n\n\n## How to connect your AI agent to GitLab through MCP\n\n\nThe most direct way to supercharge your AI workflow is to give your AI agent native access to `glab` through Model Context Protocol ([MCP](https://about.gitlab.com/topics/ai/model-context-protocol/)).\n\n\n MCP is an open standard that lets AI tools discover and use external capabilities at runtime. Once connected, your AI assistant can read issues, comment on merge requests, check pipeline status, and write back to GitLab, all without copying anything from the UI or writing a single API call yourself.\n\n\n To get started, run:\n\n\n ```shell\n # Start the glab MCP server\n glab mcp serve\n ```\n\n\n Once your MCP client is configured, your AI can answer questions like *\"What's the status of my open MRs?\"* or *\"Are there any failing pipelines on main?\"* by querying GitLab directly, not scraping the web UI, not relying on stale training data. See the [full setup docs](https://docs.gitlab.com/cli/) for configuration steps for Claude Code, Cursor, and other editors.\n\n\n One detail worth knowing: `glab` automatically adds `--output json` when invoked through MCP, for any command that supports it. Your agent gets clean, structured data without you needing to think about output formats. And because `glab` uses the official MCP SDK, it stays compatible as the\n protocol evolves.\n\n\n We've also been deliberate about *which* commands are exposed through MCP. Commands that require interactive terminal input are intentionally\n excluded, so your agent never gets stuck waiting for input that will never come. What's exposed is what actually works reliably in an agent context.\n\n\n ## Let your AI participate in code review\n\n\n Most developers have a backlog of MRs waiting for review. It's one of the most time-consuming parts of the job and one of the best places to put\n AI to work. With `glab`, your agent doesn't just observe your review queue, it can work through it with you.\n\n\n ### See exactly what still needs addressing\n\n\n Start with this:\n\n\n ```shell\n glab mr view 2677 --comments --unresolved --output json\n ```\n\n\n This input returns the full MR: metadata, description, and every\n unresolved discussion, as a single structured JSON payload. Hand that to\n your AI and it has everything it needs: which threads are open, what the\n reviewer asked for, and in what context. No tab-switching, no copy-pasting\n individual comments.\n\n\n \n ```json\n {\n   \"id\": 2677,\n   \"title\": \"feat: add OAuth2 support\",\n   \"state\": \"opened\",\n   \"author\": { \"username\": \"jdwick\" },\n   \"labels\": [\"backend\", \"needs-review\"],\n   \"blocking_discussions_resolved\": false,\n   \"discussions\": [\n     {\n       \"id\": \"3107030349\",\n       \"resolved\": false,\n       \"notes\": [\n         {\n           \"author\": { \"username\": \"dmurphy\" },\n           \"body\": \"This error handling will swallow panics — consider wrapping with recover()\",\n           \"created_at\": \"2026-03-14T09:23:11.000Z\"\n         }\n       ]\n     },\n     {\n       \"id\": \"3107030412\",\n       \"resolved\": false,\n       \"notes\": [\n         {\n           \"author\": { \"username\": \"sreeves\" },\n           \"body\": \"Token refresh logic needs a test for the expired token case\",\n           \"created_at\": \"2026-03-14T10:05:44.000Z\"\n         }\n       ]\n     }\n   ]\n }\n ```\n\n\n Instead of reading through every thread yourself, you ask your agent  *\"what do I still need to fix in MR 2677?\"* and get back a prioritized summary with suggested changes. This all happens from a single command.\n\n\n ### Close the loop programmatically\n\n\n Once your AI has helped you address the feedback, it can resolve\n discussions:\n\n\n ```shell\n # List all discussions — structured, ready for the agent to process\n glab mr note list 456 --output json\n\n # Resolve a discussion once the feedback is addressed\n glab mr note resolve 456 3107030349\n\n # Reopen if something needs another look\n glab mr note reopen 456 3107030349\n ```\n\n\n\n ```json\n [\n   {\n     \"id\": 3107030349,\n     \"body\": \"This error handling will swallow panics — consider wrapping with recover()\",\n     \"author\": { \"username\": \"dmurphy\" },\n     \"resolved\": false,\n     \"resolvable\": true\n   },\n   {\n     \"id\": 3107030412,\n     \"body\": \"Token refresh logic needs a test for the expired token case\",\n     \"author\": { \"username\": \"sreeves\" },\n     \"resolved\": false,\n     \"resolvable\": true\n   }\n ]\n ```\n\n\n\n Note IDs are visible directly in the GitLab UI and API, no extra lookup needed. Your agent can work through the full list, verify each fix, and\n resolve as it goes.\n\n\n ## Talk to your AI about your code more effectively\n\n\n Even if you're not running an MCP server, there's a simpler shift that makes a huge difference: using `glab` to feed your AI better information.\n\n\n Think about the last time you asked an AI assistant to help triage issues or debug a failing pipeline. You probably copied some text from the GitLab UI and pasted it into the chat. Here's what your agent is actually\n working with when you do that:\n\n\n ```text\n open issues: 12 • milestone: 17.10 • label: bug, needs-triage ...\n ```\n\n\n Compare that to what it gets with `glab`:\n\n\n \n ```json\n [\n   {\n     \"iid\": 902,\n     \"title\": \"Pipeline fails on merge to main\",\n     \"labels\": [\"bug\", \"needs-triage\"],\n     \"milestone\": { \"title\": \"17.10\" },\n     \"assignees\": []\n   },\n   ...\n ]\n ```\n\n\n Structured, typed, complete; no ambiguity, no parsing guesswork. That's the difference between an agent that can act and one that has to ask\n follow-up questions.\n\n\n If you're using the MCP server, you get this automatically: `glab` adds `--output json` for any command that supports it. If you're working directly\n from the terminal, just add the flag yourself:\n\n\n ```shell\n # Pull open issues for triage\n glab issue list --label \"needs-triage\" --output json\n\n # Check pipeline status\n glab ci status --output json\n\n # Get full MR details\n glab mr view 456 --output json\n ```\n\n\n We've significantly expanded JSON output support in recent releases. It now covers CI status, milestones, labels, releases, schedules, cluster agents, work items, MR approvers, repo contributors, and more. If `glab` can\n retrieve it, your AI can consume it cleanly.\n\n\n ### A real workflow\n\n\n ```shell\n $ glab issue list --label \"needs-triage\" --milestone \"17.10\"\n --output json\n ```\n\n\n ```text\n Agent: I found 2 unassigned bugs in the 17.10 milestone that need triage:\n 1. #902 — Pipeline fails on merge to main (opened 5 days ago)\n 2. #903 — Auth token not refreshing on expiry (opened 4 days ago)\n Both are unassigned. Want me to draft triage notes and suggest assignees based on recent commit history?\n ```\n\n\n ## Your agent is never limited to built-in commands\n\n\n `glab`'s first-class commands cover the most common workflows, but your agent is never limited to them. Through `glab api`, it has authenticated access to the full GitLab REST and GraphQL API surface, using the same session, with no extra credentials or configuration required.\n\n\n This is a meaningful differentiator. Most CLI tools stop at what their commands expose. With `glab`, if GitLab's API supports it, your agent can do it. It's always working from a trusted, authenticated context.\n\n\n A practical example: fetching just the list of changed files in an MR before deciding which diffs to pull in full:\n\n\n ```shell\n # Get changed file paths — lightweight, no diff content yet\n glab api \"/projects/$CI_PROJECT_ID/merge_requests/$CI_MERGE_REQUEST_IID/diffs?per_page=100\" \\\n | jq '.[].new_path'\n\n# Then fetch only the specific file your agent needs\nglab api \"/projects/$CI_PROJECT_ID/merge_requests/$CI_MERGE_REQUEST_IID/diffs?per_page=100\" \\\n| jq '.[] | select(.new_path == \"path/to/file.go\")'\n ```\n\n\n ```text\n \"internal/auth/token.go\"\n \"internal/auth/token_test.go\"\n \"internal/oauth/refresh.go\"\n ```\n\n\n For anything the REST API doesn't cover (epics, certain work item queries, complex cross-project data),  `glab api graphql` gives you the full\n GraphQL interface:\n\n\n ```shell\n   glab api graphql -f query='\n {\n   project(fullPath: \"gitlab-org/gitlab\") {\n     mergeRequest(iid: \"12345\") {\n       title\n       reviewers { nodes { username } }\n     }\n   }\n }'\n ```\n\n ```json\n{\n   \"data\": {\n     \"project\": {\n       \"mergeRequest\": {\n         \"title\": \"feat: add OAuth2 support\",\n         \"reviewers\": {\n           \"nodes\": [\n             { \"username\": \"dmurphy\" },\n             { \"username\": \"sreeves\" }\n           ]\n         }\n       }\n     }\n   }\n }\n\n ```\n\n\n Your agent has a single, authenticated entry point to everything GitLab exposes without the token juggling, separate API clients, or configuration\n overhead.\n\n\n ## What's coming and your feedback\n\n\n Two improvements we're actively working on will make `glab` even more useful for agent workflows:\n\n\n **Agent-aware help text.** Today, `--help` output is written for humansvat a terminal. We're updating it to surface the non-interactive alternative\n for every interactive command, flag which commands support `--output json`, and generally make help a useful resource for agents discovering\n capabilities at runtime — not just humans.\n\n\n **Better machine-readable errors.** When something goes wrong today, agents get the same human-readable error messages as terminal users. We're\n changing that so errors in JSON mode return structured output, giving your agent the information it needs to handle failures gracefully, retry intelligently, or surface the right context back to you.\n\n\n Both of these are in active development. If you're already using `glab` with an AI tool, you're exactly the audience we want feedback from.\n\n\n * **What friction are you hitting?** Commands that don't behave well in agent contexts, error messages that aren't actionable, gaps in JSON output\n coverage. We want to know.\n\n * **What workflows have you unlocked?** Real usage patterns help us prioritize what to build next.\n\n\n Join the discussion in [our feedback issue](https://gitlab.com/gitlab-org/cli/-/issues/8177) — that's where we're shaping the roadmap for agent-friendliness, and where your input will have the most direct impact. If you've found a specific gap, [open an issue](https://gitlab.com/gitlab-org/cli/-/issues/new). If you've got a fix in mind, contributions are welcome. Visit [CONTRIBUTING.md](https://gitlab.com/gitlab-org/cli/-/blob/main/CONTRIBUTING.md) to get started.\n\n\n The GitLab CLI has always been about giving developers more control over their workflow. As AI becomes a bigger part of how we all work, that means making `glab` the best possible interface between your AI tools and your GitLab projects. We're just getting started and we'd love to build the next part with you.\n",[27,726,25],{"featured":30,"template":13,"slug":740},"give-your-ai-agent-direct-structured-gitlab-access-with-glab-cli",{"content":742,"config":750},{"title":743,"description":744,"authors":745,"heroImage":735,"date":747,"body":748,"category":9,"tags":749},"GitHub Copilot's new policy for AI training is a governance wake-up call","Learn what GitHub's Copilot policy change means for regulated industries, and why GitLab's commitment to customer data privacy matters.",[746],"Allie Holland","2026-04-20","GitHub recently [announced](https://github.blog/news-insights/company-news/updates-to-github-copilot-interaction-data-usage-policy/) a significant change to how it handles data from Copilot users. Starting April 24, 2026, interaction data from Copilot Free, Pro, and Pro+ users, including inputs, outputs, code snippets, and associated context, will be used to train AI models by default, unless users actively opt out. Copilot Business and Enterprise customers are exempt under existing contract terms.\n\nFor organizations in regulated industries, including finance, healthcare, defense, and public sector, the policy shift raises questions that go beyond individual developer preferences. It forces a harder look at a question that engineering and security leaders should be asking every AI vendor in their stack: Do you train on our code? \n\nGitLab's answer is no. GitLab does not train AI models on customer code at any tier, and AI vendors are contractually prohibited from using customer inputs or outputs for their own purposes. The [GitLab AI Transparency Center](https://about.gitlab.com/ai-transparency-center/) makes that commitment auditable: a single location documenting which models power which features, how data is handled, subprocessor relationships, and data retention periods. The GitLab AI Transparency Center also lists the compliance status of each feature, including confirmation that GitLab's current AI features do not qualify as high-risk systems under the EU AI Act. It's a standard GitLab CEO Bill Staples has consistently [reiterated](https://www.linkedin.com/posts/williamstaples_gitlab-1810-agentic-ai-now-open-to-even-activity-7443280763715985408-aHxf?utm_source=share&utm_medium=member_desktop&rcm=ACoAABsu7EUBcb_a1-JHKS9RC0B5rf8Ye-5XM60) and one reflected in GitLab's mission and [Trust Center](https://trust.gitlab.com/).\n\n## What the policy change actually means\n\nGitHub's announcement also specifies that the data may be shared with GitHub affiliates, including Microsoft, for AI development purposes.\n\nA policy change of this nature forces organizations to re-examine their AI governance posture, audit their Copilot license tiers, and confirm that the right controls are configured across their teams.\n\n## Why AI governance matters in regulated environments\n\nSource code is often among an organization's most sensitive intellectual property. It may contain references to internal systems, reflect proprietary business logic, or touch data flows governed by strict retention and access policies. When that code passes through an AI assistant, questions about training data usage, model vendor relationships, and data residency become compliance concerns.\n\nThe exposure is particularly acute for financial services firms that have invested in proprietary algorithms, fraud detection logic, credit risk models, underwriting rules, trading strategies. When AI tooling processes that code and uses it to train models serving competitors, vendor data practices become an IP concern.\n\nFinancial institutions operating under [the Federal Reserve's Supervisory Guidance on Model Risk Management (SR 11-7) and the](https://www.federalreserve.gov/supervisionreg/srletters/sr1107.htm) [Digital Operational Resilience Act (DORA)](https://eur-lex.europa.eu/eli/reg/2022/2554/oj/eng) are required to maintain documented, auditable oversight of third-party technology providers, including understanding how those providers handle data. Third-party AI tools used in development workflows increasingly fall within the scope of model risk oversight, and material changes to vendor data practices require updated documentation. \n\nIn the public sector, [the National Institute of Standards and Technology Special Publication 800-53 (NIST 800-53)](https://csrc.nist.gov/publications/detail/sp/800-53/rev-5/final) and the [Federal Information Security Modernization Act (FISMA)](https://www.cisa.gov/topics/cyber-threats-and-advisories/federal-information-security-modernization-act) establish that sensitive or classified code must never leave a controlled boundary. For U.S. Department of Defense and intelligence community environments in particular, a vendor's default data posture is an operational concern. In healthcare, [the Health Insurance Portability and Accountability Act (HIPAA)](https://www.hhs.gov/hipaa/index.html) governs how patient-adjacent data is handled by third parties, and development environments that touch clinical systems increasingly fall within that scope.\n\nAcross all of these contexts, the common thread is the same: A vendor policy that changes data usage defaults, requires individual opt-out, and offers different protections depending on account tier introduces exactly the kind of uncontrolled variable that compliance teams cannot afford.\n\n## What regulated industries actually need from AI vendors\n\nRegulated organizations have largely moved past debating whether to adopt AI in development workflows. The focus now is on doing so in a way they can defend to regulators, boards, and customers. That shift has surfaced a consistent set of requirements regardless of sector.\n\n**Contractual certainty.** Regulated firms need to know, with specificity, what happens to their data. A clear, documented, unconditional commitment is what's required, not something that varies by plan or requires action before a deadline.\n\n**Auditability.** Model risk management frameworks require organizations to understand and validate the AI systems they deploy, including the training data behind those models and the third parties involved in their development. Vendors who cannot answer these questions create documentation risk for the organizations relying on them.\n\n**Separation from vendor incentives.** When an AI vendor trains models on customer usage data, code and workflows become inputs to a system that also serves competitors. For institutions with proprietary trading logic, underwriting models, or fraud detection systems, that's a genuine IP exposure.\n\n## GitLab's position on AI data governance\n\nGitLab does not use customer code to train AI models. This commitment applies at every tier, and AI vendors are contractually prohibited from using inputs or outputs associated with GitLab customers for their own purposes.\n\nThis is a deliberate architectural and policy choice, not a feature of a particular pricing tier. As GitLab's [post on enterprise independence](https://about.gitlab.com/blog/why-enterprise-independence-matters-more-than-ever-in-devsecops/) notes, data governance has become \"an increasingly critical factor in enterprise technology decisions, driven by a complex web of national and regional data protection laws and growing concern about control over sensitive intellectual property.\"\n\nGitLab is also cloud-neutral and model-neutral while supporting self-hosted deployments, not commercially tied to any single cloud provider or large language model (LLM). That i[ndependence matters](https://about.gitlab.com/blog/why-enterprise-independence-matters-more-than-ever-in-devsecops/) for regulated organizations evaluating vendor concentration risk. The [AI Continuity Plan](https://handbook.gitlab.com/handbook/product/ai/continuity-plan/) documents how vendor changes are managed, including material changes to how AI vendors treat customer data, a direct response to the governance requirements under frameworks like [DORA](https://handbook.gitlab.com/handbook/legal/dora/). \n\n## The governance gap AI teams need to close\n\nGitHub's policy update is a reminder that for organizations in regulated industries, understanding exactly how an AI tool handles data is a prerequisite for using it at all. That means asking vendors for clear, documented answers: Is our data used for model training? Who are your AI model subprocessors? What happens if a vendor changes its data practices? Can we deploy in a way that keeps all AI processing within our own infrastructure? What indemnification do you offer for AI-generated output?\n\nVendors who can answer those questions clearly, and document those answers in an auditable form, are vendors you can build on. **Those who cannot will create compliance debt every time they ship a policy update.** And when a vendor can change its data practices with 30 days notice, that's not a partnership built for regulated industries. That's a liability.\n\n> Learn more about GitLab's approach to AI governance at the [GitLab AI Transparency Center](https://about.gitlab.com/ai-transparency-center/).",[27,726],{"featured":12,"template":13,"slug":751},"github-copilots-new-policy-for-ai-training-is-a-governance-wake-up-call",{"promotions":753},[754,767,778,790],{"id":755,"categories":756,"header":757,"text":758,"button":759,"image":764},"ai-modernization",[9],"Is AI achieving its promise at scale?","Quiz will take 5 minutes or less",{"text":760,"config":761},"Get your AI maturity score",{"href":762,"dataGaName":763,"dataGaLocation":245},"/assessments/ai-modernization-assessment/","modernization assessment",{"config":765},{"src":766},"https://res.cloudinary.com/about-gitlab-com/image/upload/v1772138786/qix0m7kwnd8x2fh1zq49.png",{"id":768,"categories":769,"header":770,"text":758,"button":771,"image":775},"devops-modernization",[726,38],"Are you just managing tools or shipping innovation?",{"text":772,"config":773},"Get your DevOps maturity score",{"href":774,"dataGaName":763,"dataGaLocation":245},"/assessments/devops-modernization-assessment/",{"config":776},{"src":777},"https://res.cloudinary.com/about-gitlab-com/image/upload/v1772138785/eg818fmakweyuznttgid.png",{"id":779,"categories":780,"header":782,"text":758,"button":783,"image":787},"security-modernization",[781],"security","Are you trading speed for security?",{"text":784,"config":785},"Get your security maturity score",{"href":786,"dataGaName":763,"dataGaLocation":245},"/assessments/security-modernization-assessment/",{"config":788},{"src":789},"https://res.cloudinary.com/about-gitlab-com/image/upload/v1772138786/p4pbqd9nnjejg5ds6mdk.png",{"id":791,"paths":792,"header":795,"text":796,"button":797,"image":802},"github-azure-migration",[793,794],"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":798,"config":799},"See how GitLab compares to GitHub",{"href":800,"dataGaName":801,"dataGaLocation":245},"/compare/gitlab-vs-github/github-azure-migration/","github azure migration",{"config":803},{"src":777},{"header":805,"blurb":806,"button":807,"secondaryButton":812},"Start building faster today","See what your team can do with the intelligent orchestration platform for DevSecOps.\n",{"text":808,"config":809},"Get your free trial",{"href":810,"dataGaName":52,"dataGaLocation":811},"https://gitlab.com/-/trial_registrations/new?glm_content=default-saas-trial&glm_source=about.gitlab.com/","feature",{"text":507,"config":813},{"href":56,"dataGaName":57,"dataGaLocation":811},1777573318771]